diff options
author | IOhannes m zmölnig <zmoelnig@umlautS.umlaeute.mur.at> | 2022-03-25 14:18:52 +0100 |
---|---|---|
committer | IOhannes m zmölnig <zmoelnig@umlautS.umlaeute.mur.at> | 2022-03-25 14:18:52 +0100 |
commit | c921b1f0f07d22da2e231808e18bed30219d7cdc (patch) | |
tree | f54ea253ee0f99bc5ea38b3a823b15d43f6ca99e | |
parent | 8aedadd869752a59c32d6c69a346aaeab52fe8fa (diff) |
New upstream version 3.0.4+dfsg
92 files changed, 17899 insertions, 10681 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 8ae74ff..4f32afe 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,87 +4,136 @@ branches: only: - master -# Also includes VS2013 pre-installed -image: - - Visual Studio 2015 - - Visual Studio 2017 - - Visual Studio 2019 - environment: matrix: - # Visual Studio 2013, 32-bit Release, Asio driver - - AUDIO_DRIVER: Asio + - APPVEYOR_BUILD_WORKER_IMAGE: macos-mojave + CONFIGURATION: Release + XCODE_VERSION: 9.4.1 + - APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina + CONFIGURATION: Release + XCODE_VERSION: 11.7 + - APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina + CONFIGURATION: Debug + XCODE_VERSION: 12.3 + - APPVEYOR_BUILD_WORKER_IMAGE: macos-monterey + CONFIGURATION: Release + XCODE_VERSION: 12.5.1 + - APPVEYOR_BUILD_WORKER_IMAGE: macos-monterey CONFIGURATION: Release - GENERATOR: Visual Studio 12 2013 - # Visual Studio 2013, 64-bit Release, Asio driver - - AUDIO_DRIVER: Asio + XCODE_VERSION: 13.2.1 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 + AUDIO_DRIVER: Jack + CONFIGURATION: Debug + GENERATOR: Ninja + CXX: clang++-11 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 + AUDIO_DRIVER: Alsa + CONFIGURATION: Release + GENERATOR: Ninja + CXX: clang++-10 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 + AUDIO_DRIVER: Jack + CONFIGURATION: Debug + GENERATOR: Ninja + CXX: clang++-9 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 + AUDIO_DRIVER: Alsa CONFIGURATION: Release - GENERATOR: Visual Studio 12 2013 Win64 - # Visual Studio 2013, 64-bit Debug, Wasapi driver - - AUDIO_DRIVER: Wasapi + GENERATOR: Ninja + CXX: g++-9 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 + AUDIO_DRIVER: Jack CONFIGURATION: Debug - GENERATOR: Visual Studio 12 2013 Win64 - # Visual Studio 2015, 32-bit Release, Asio driver - - AUDIO_DRIVER: Asio + GENERATOR: Ninja + CXX: g++-8 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 + AUDIO_DRIVER: Alsa + CONFIGURATION: Release + GENERATOR: Ninja + CXX: g++-7 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + AUDIO_DRIVER: Asio CONFIGURATION: Release GENERATOR: Visual Studio 14 2015 - # Visual Studio 2015, 64-bit Debug, Asio driver - - AUDIO_DRIVER: Asio + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + AUDIO_DRIVER: Asio CONFIGURATION: Debug GENERATOR: Visual Studio 14 2015 Win64 - # Visual Studio 2015, 64-bit Release, Asio driver - - AUDIO_DRIVER: Asio + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + AUDIO_DRIVER: Asio CONFIGURATION: Release GENERATOR: Visual Studio 14 2015 Win64 - # Visual Studio 2015, 64-bit Release, Wasapi driver - - AUDIO_DRIVER: Wasapi + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + AUDIO_DRIVER: Wasapi CONFIGURATION: Release GENERATOR: Visual Studio 14 2015 Win64 - # Visual Studio 2017, 64-bit Release, Asio driver - - AUDIO_DRIVER: Asio + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + AUDIO_DRIVER: Asio CONFIGURATION: Release GENERATOR: Visual Studio 15 2017 Win64 - # Visual Studio 2019, 64-bit Release, Asio driver - - AUDIO_DRIVER: Asio + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + AUDIO_DRIVER: Asio CONFIGURATION: Release GENERATOR: Visual Studio 16 2019 - -matrix: - exclude: - - image: Visual Studio 2015 - GENERATOR: Visual Studio 15 2017 Win64 - - image: Visual Studio 2015 - GENERATOR: Visual Studio 16 2019 - - image: Visual Studio 2017 - GENERATOR: Visual Studio 12 2013 - - image: Visual Studio 2017 - GENERATOR: Visual Studio 12 2013 Win64 - - image: Visual Studio 2017 - GENERATOR: Visual Studio 14 2015 - - image: Visual Studio 2017 - GENERATOR: Visual Studio 14 2015 Win64 - - image: Visual Studio 2017 - GENERATOR: Visual Studio 16 2019 - - image: Visual Studio 2019 - GENERATOR: Visual Studio 12 2013 - - image: Visual Studio 2019 - GENERATOR: Visual Studio 12 2013 Win64 - - image: Visual Studio 2019 - GENERATOR: Visual Studio 14 2015 - - image: Visual Studio 2019 - GENERATOR: Visual Studio 14 2015 Win64 - - image: Visual Studio 2019 - GENERATOR: Visual Studio 15 2017 - - image: Visual Studio 2019 - GENERATOR: Visual Studio 15 2017 Win64 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + AUDIO_DRIVER: Wasapi + CONFIGURATION: Debug + GENERATOR: Visual Studio 17 2022 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 + ESP_IDF: true + IDF_RELEASE: v4.3.1 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu2004 + FORMATTING: true install: - git submodule update --init --recursive -build_script: - - python ci/configure.py -a %AUDIO_DRIVER% -g "%GENERATOR%" - - python ci/build.py --configuration %CONFIGURATION% - -test_script: - - python ci/run-tests.py --target LinkCoreTest - - python ci/run-tests.py --target LinkDiscoveryTest +for: + - matrix: + only: + - APPVEYOR_BUILD_WORKER_IMAGE: macos-mojave + - APPVEYOR_BUILD_WORKER_IMAGE: macos-catalina + - APPVEYOR_BUILD_WORKER_IMAGE: macos-monterey + build_script: + - sudo xcode-select -s /Applications/Xcode-$XCODE_VERSION.app + - python3 ci/configure.py --generator Xcode + - python3 ci/build.py --configuration $CONFIGURATION + test_script: + - python3 ci/run-tests.py --target LinkCoreTest + - python3 ci/run-tests.py --target LinkDiscoveryTest + - matrix: + only: + # Ubuntu2004 but not ESP_IDF or FORMATTING + - GENERATOR: Ninja + install: + - git submodule update --init --recursive + - sudo apt-get update + - sudo apt-get install -y libjack-dev portaudio19-dev valgrind + build_script: + - python3 ci/configure.py --audio-driver $AUDIO_DRIVER --generator "$GENERATOR" --configuration $CONFIGURATION + - python3 ci/build.py + test_script: + - python3 ci/run-tests.py --target LinkCoreTest --valgrind + - python3 ci/run-tests.py --target LinkDiscoveryTest --valgrind + - matrix: + only: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + build_script: + - py -3 ci/configure.py --audio-driver %AUDIO_DRIVER% --generator "%GENERATOR%" --flags="-DCMAKE_SYSTEM_VERSION=10.0.18362.0" + - py -3 ci/build.py --configuration %CONFIGURATION% + test_script: + - py -3 ci/run-tests.py --target LinkCoreTest + - py -3 ci/run-tests.py --target LinkDiscoveryTest + - matrix: + only: + - ESP_IDF: true + build_script: + - docker run --rm -v $APPVEYOR_BUILD_FOLDER:/link -w /link/examples/esp32 -e LC_ALL=C.UTF-8 espressif/idf:$IDF_RELEASE idf.py build + - matrix: + only: + - FORMATTING: true + build_script: + - docker run -v $APPVEYOR_BUILD_FOLDER:/link dalg24/clang-format:18.04.0 python /link/ci/check-formatting.py -c /usr/bin/clang-format-6.0 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bfb93cc..0000000 --- a/.travis.yml +++ /dev/null @@ -1,167 +0,0 @@ -# Copyright (c) 2016 Ableton. All Rights Reserved. - -language: cpp -sudo: required -branches: - only: - - master - -matrix: - include: - ########################################################################### - # Build with the main configuration on all the supported compilers - ########################################################################### - # Mac OS X / XCode 7.3, 64-bit Debug - - os: osx - env: WORDSIZE=64 CONFIGURATION=Debug - osx_image: xcode7.3 - compiler: clang - - # Mac OS X / XCode 7.3, 64-bit Release - - os: osx - env: WORDSIZE=64 CONFIGURATION=Release - osx_image: xcode7.3 - compiler: clang - - # Mac OS X / XCode 7.3, 32-bit Release - - os: osx - env: WORDSIZE=32 CONFIGURATION=Release - osx_image: xcode7.3 - compiler: clang - - # Mac OS X / XCode 9.0, 64-bit Debug - - os: osx - env: WORDSIZE=64 CONFIGURATION=Debug - osx_image: xcode9 - compiler: clang - - # Linux with Clang 3.6, 64-bit Debug, Alsa - - os: linux - dist: trusty - compiler: clang - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['clang-3.6', 'g++-5', 'valgrind', 'portaudio19-dev'] - env: COMPILER=clang++-3.6 WORDSIZE=64 CONFIGURATION=Debug AUDIO=Alsa - - # Linux with Clang 3.8, 64-bit Release, Jack - - os: linux - dist: trusty - compiler: clang - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['clang-3.8', 'g++-5', 'valgrind', 'portaudio19-dev'] - env: COMPILER=clang++-3.8 WORDSIZE=64 CONFIGURATION=Release AUDIO=Jack - - # Linux with Clang 5.0, 64-bit Debug, Alsa - - os: linux - dist: bionic - compiler: clang - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['clang-5.0', 'g++-5', 'valgrind', 'portaudio19-dev'] - env: COMPILER=clang++-5.0 WORDSIZE=64 CONFIGURATION=Debug AUDIO=Alsa - - # Linux with GCC 5.x, 64-bit Release, Alsa - - os: linux - dist: bionic - compiler: gcc - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-5', 'valgrind', 'portaudio19-dev'] - env: COMPILER=g++-5 WORDSIZE=64 CONFIGURATION=Release AUDIO=Alsa - - # Linux with GCC 6.x, 64-bit Release, Alsa - - os: linux - dist: bionic - compiler: gcc - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-6', 'valgrind', 'portaudio19-dev'] - env: COMPILER=g++-6 WORDSIZE=64 CONFIGURATION=Release AUDIO=Alsa - - # ESP32 - - os: linux - dist: bionic - compiler: clang - addons: - apt: - sources: ['llvm-toolchain-trusty-5.0', 'ubuntu-toolchain-r-test'] - packages: ['wget', 'flex', 'bison', 'gperf', 'python-pip', 'ninja-build', 'ccache'] - env: IDF_RELEASE=v4.0-rc IDF_PATH=${HOME}/esp-idf-${IDF_RELEASE} COMPILER=clang - - # Code formatting verification - - os: linux - dist: bionic - compiler: clang - env: CONFIGURATION=Formatting LLVM_VERSION=6.0 - services: - - docker - -before_install: - # Do indentation check - - | - if [ "$CONFIGURATION" = "Formatting" ]; then - docker pull dalg24/clang-format:18.04.0 - docker run -v $TRAVIS_BUILD_DIR:/ws dalg24/clang-format python /ws/ci/check-formatting.py -c /usr/bin/clang-format-6.0 - exit $? - fi - - # Override Travis' CXX Flag - - CXX=$COMPILER - -install: - - git submodule update --init --recursive - -script: - - | - set -e - if [ ! -z "$IDF_PATH" ]; then - mkdir -p ${IDF_PATH} - wget -P ${HOME} https://github.com/espressif/esp-idf/releases/download/${IDF_RELEASE}/esp-idf-${IDF_RELEASE}.zip - unzip -o ${HOME}/esp-idf-${IDF_RELEASE}.zip -d ${HOME} - ${IDF_PATH}/install.sh - . ${IDF_PATH}/export.sh - . ${IDF_PATH}/add_path.sh - PATH=${IDF_PATH}/tools:${PATH} - cd examples/esp32 - idf.py build - else - if [ "$TRAVIS_OS_NAME" = "osx" ]; then - if [ "$WORDSIZE" -eq 64 ]; then - python ci/configure.py --configuration $CONFIGURATION - else - python ci/configure.py --configuration $CONFIGURATION --flags "\-DCMAKE_OSX_ARCHITECTURES=i386" - fi - python ci/build.py --configuration $CONFIGURATION - else - if [ "$WORDSIZE" -eq 64 ]; then - python ci/configure.py --configuration $CONFIGURATION -a $AUDIO - else - python ci/configure.py --configuration $CONFIGURATION -a $AUDIO --flags "\-DCMAKE_CXX_FLAGS=\"\-m32\"" - fi - python ci/build.py --configuration $CONFIGURATION - fi - fi - set +e - - # Build Tests and run with Valgrind (Linux 64-bit only). Mac OSX supports - # valgrind via homebrew, but there is no bottle formula, so it must be - # compiled by brew and this takes way too much time on the build server. - - | - set -e - if [ -z "$IDF_PATH" ]; then - if [ "$TRAVIS_OS_NAME" = "linux" ]; then - python ci/run-tests.py --target LinkCoreTest --valgrind - python ci/run-tests.py --target LinkDiscoveryTest --valgrind - else - python ci/run-tests.py --target LinkCoreTest - python ci/run-tests.py --target LinkDiscoveryTest - fi - fi - set +e diff --git a/AbletonLinkConfig.cmake b/AbletonLinkConfig.cmake index c34d8e3..43b66e7 100644 --- a/AbletonLinkConfig.cmake +++ b/AbletonLinkConfig.cmake @@ -31,7 +31,7 @@ elseif(WIN32) INTERFACE_COMPILE_DEFINITIONS LINK_PLATFORM_WINDOWS=1 ) -elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") +elseif(CMAKE_SYSTEM_NAME MATCHES "Linux|kFreeBSD|GNU") set_property(TARGET Ableton::Link APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS LINK_PLATFORM_LINUX=1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 100fe2c..5924722 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,10 +33,12 @@ endif() include(cmake_include/ConfigureCompileFlags.cmake) include(cmake_include/CatchConfig.cmake) include(AbletonLinkConfig.cmake) +include(extensions/abl_link/abl_link.cmake) add_subdirectory(include) add_subdirectory(src) add_subdirectory(examples) +add_subdirectory(extensions/abl_link) # ____ # / ___| _ _ _ __ ___ _ __ ___ __ _ _ __ _ _ @@ -80,8 +80,8 @@ insight as to the compiler flags needed to build Link. | Platform | Minimum Required | Optional (only required for examples) | |----------|----------------------|---------------------------------------| -| Windows | MSVC 2013 | Steinberg ASIO SDK 2.3 | -| Mac | Xcode 7.0 | | +| Windows | MSVC 2015 | Steinberg ASIO SDK 2.3 | +| Mac | Xcode 9.4.1 | | | Linux | Clang 3.6 or GCC 5.2 | libportaudio19-dev | diff --git a/TEST-PLAN.md b/TEST-PLAN.md index 3b00d61..5a6de37 100644 --- a/TEST-PLAN.md +++ b/TEST-PLAN.md @@ -2,7 +2,9 @@ Below are a set of user interactions that are expected to work consistently across all Link-enabled apps. In order to provide the best user experience, it's important that apps -behave consistently with respect to these test cases. ## Tempo Changes +behave consistently with respect to these test cases. + +## Tempo Changes ### TEMPO-1: Tempo changes should be transmitted between connected apps. diff --git a/ci/check-formatting.py b/ci/check-formatting.py index 2e18a4e..d95eb10 100755 --- a/ci/check-formatting.py +++ b/ci/check-formatting.py @@ -24,7 +24,7 @@ def parse_args(): def parse_clang_xml(xml): for line in xml.splitlines(): - if line.startswith('<replacement '): + if line.startswith(b'<replacement '): return False return True @@ -45,7 +45,7 @@ def check_files_in_path(args, path): for (path, dirs, files) in os.walk(path): for file in files: - if file.endswith('cpp') or file.endswith('hpp') or file.endswith('.ipp'): + if file.endswith(('.c', '.cpp', '.h', '.hpp', '.ipp')): file_absolute_path = path + os.path.sep + file clang_format_args = [ args.clang_format, '-style=file', @@ -73,8 +73,8 @@ def check_files_in_path(args, path): def check_formatting(args): errors_found = False script_dir = os.path.dirname(os.path.realpath(__file__)) - for path in ['examples', 'include', 'src']: - subdir_abs_path = os.path.join(script_dir, path) + for path in ['../examples', '../include', '../src', '../extensions/abl_link']: + subdir_abs_path = os.path.abspath(os.path.join(script_dir, path)) if check_files_in_path(args, subdir_abs_path): errors_found = True diff --git a/cmake_include/CatchConfig.cmake b/cmake_include/CatchConfig.cmake index 19d96b8..2470e18 100644 --- a/cmake_include/CatchConfig.cmake +++ b/cmake_include/CatchConfig.cmake @@ -2,5 +2,5 @@ add_library(Catch::Catch IMPORTED INTERFACE) set_property(TARGET Catch::Catch APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES - ${CMAKE_SOURCE_DIR}/third_party/catch + ${CMAKE_CURRENT_LIST_DIR}/../third_party/catch ) diff --git a/cmake_include/ConfigureCompileFlags.cmake b/cmake_include/ConfigureCompileFlags.cmake index 4cf5140..689cd90 100644 --- a/cmake_include/ConfigureCompileFlags.cmake +++ b/cmake_include/ConfigureCompileFlags.cmake @@ -32,6 +32,7 @@ if(UNIX) "-Wno-disabled-macro-expansion" "-Wno-exit-time-destructors" "-Wno-padded" + "-Wno-poison-system-directories" "-Wno-reserved-id-macro" "-Wno-unknown-warning-option" "-Wno-unused-member-function" diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b85107f..f75b00b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -11,9 +11,9 @@ project(LinkExamples) if(WIN32) function(configure_asio asio_sdk_path_OUT) # ASIO-related path/file variables - set(asio_download_root "https://www.steinberg.net/sdk_downloads") - set(asio_file_name "asiosdk2.3.zip") - set(asio_dir_name "ASIOSDK2.3") + set(asio_download_root "https:/download.steinberg.net/sdk_downloads") + set(asio_file_name "asiosdk_2.3.3_2019-06-14.zip") + set(asio_dir_name "asiosdk_2.3.3_2019-06-14") set(asio_working_dir "${CMAKE_BINARY_DIR}/modules") set(asio_output_path "${asio_working_dir}/${asio_file_name}") @@ -71,7 +71,7 @@ elseif(WIN32) linkaudio/AudioPlatform_Wasapi.cpp ) endif() -elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") +elseif(CMAKE_SYSTEM_NAME MATCHES "Linux|kFreeBSD|GNU") if(LINK_BUILD_JACK) set(linkhut_audio_SOURCES linkaudio/AudioPlatform_Jack.hpp @@ -96,7 +96,7 @@ source_group("Audio Sources" FILES ${linkhut_audio_SOURCES}) # function(configure_linkhut_executable target) - if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + if(CMAKE_SYSTEM_NAME MATCHES "Linux|kFreeBSD|GNU") target_link_libraries(${target} atomic pthread) endif() @@ -109,7 +109,7 @@ function(configure_linkhut_audio_sources target) target_compile_definitions(${target} PRIVATE -DLINKHUT_AUDIO_PLATFORM_COREAUDIO=1 ) - elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + elseif(CMAKE_SYSTEM_NAME MATCHES "Linux|kFreeBSD|GNU") if(LINK_BUILD_JACK) target_link_libraries(${target} jack) target_compile_definitions(${target} PRIVATE diff --git a/examples/esp32/README.md b/examples/esp32/README.md index 9647af4..3379db6 100644 --- a/examples/esp32/README.md +++ b/examples/esp32/README.md @@ -1,6 +1,6 @@ # *E X P E R I M E N T A L* -*Tested with esp-idf [v4.0-beta2](https://github.com/espressif/esp-idf/releases/tag/v4.0-rc)* +*Tested with esp-idf [v4.3.1](https://github.com/espressif/esp-idf/releases/tag/v4.3.1)* ## Building and Running the Example diff --git a/examples/esp32/main/main.cpp b/examples/esp32/main/main.cpp index 6c8892f..b44f459 100644 --- a/examples/esp32/main/main.cpp +++ b/examples/esp32/main/main.cpp @@ -73,6 +73,7 @@ void printTask(void* userParam) void tickTask(void* userParam) { + SemaphoreHandle_t handle = static_cast<SemaphoreHandle_t>(userParam); ableton::Link link(120.0f); link.enable(true); @@ -85,7 +86,7 @@ void tickTask(void* userParam) while (true) { - xSemaphoreTake(userParam, portMAX_DELAY); + xSemaphoreTake(handle, portMAX_DELAY); const auto state = link.captureAudioSessionState(); const auto phase = state.phaseAtTime(link.clock().micros(), 1.); @@ -97,7 +98,7 @@ void tickTask(void* userParam) extern "C" void app_main() { ESP_ERROR_CHECK(nvs_flash_init()); - tcpip_adapter_init(); + esp_netif_init(); ESP_ERROR_CHECK(esp_event_loop_create_default()); ESP_ERROR_CHECK(example_connect()); diff --git a/examples/linkaudio/AudioEngine.cpp b/examples/linkaudio/AudioEngine.cpp index 323d634..45df3a2 100644 --- a/examples/linkaudio/AudioEngine.cpp +++ b/examples/linkaudio/AudioEngine.cpp @@ -148,7 +148,8 @@ void AudioEngine::renderMetronomeIntoBuffer(const Link::SessionState sessionStat { double amplitude = 0.; // Compute the host time for this sample and the last. - const auto hostTime = beginHostTime + microseconds(llround(static_cast<double>(i) * microsPerSample)); + const auto hostTime = + beginHostTime + microseconds(llround(static_cast<double>(i) * microsPerSample)); const auto lastSampleHostTime = hostTime - microseconds(llround(microsPerSample)); // Only make sound for positive beat magnitudes. Negative beat diff --git a/examples/linkaudio/AudioPlatform_Asio.cpp b/examples/linkaudio/AudioPlatform_Asio.cpp index 40aa20a..8e22845 100644 --- a/examples/linkaudio/AudioPlatform_Asio.cpp +++ b/examples/linkaudio/AudioPlatform_Asio.cpp @@ -220,10 +220,10 @@ void AudioPlatform::createAsioBuffers() << ", output latency: " << outputLatency << "usec" << std::endl; const double bufferSize = driverInfo.preferredSize / driverInfo.sampleRate; - auto outputLatencyMicros = + auto outputLatencyMicros = std::chrono::microseconds(llround(outputLatency / driverInfo.sampleRate)); outputLatencyMicros += std::chrono::microseconds(llround(1.0e6 * bufferSize)); - + mEngine.mOutputLatency.store(outputLatencyMicros); std::clog << "Total latency: " << outputLatencyMicros.count() << "usec" << std::endl; diff --git a/examples/linkaudio/AudioPlatform_CoreAudio.cpp b/examples/linkaudio/AudioPlatform_CoreAudio.cpp index 8ec07ed..d4b4552 100644 --- a/examples/linkaudio/AudioPlatform_CoreAudio.cpp +++ b/examples/linkaudio/AudioPlatform_CoreAudio.cpp @@ -50,7 +50,8 @@ OSStatus AudioPlatform::audioCallback(void* inRefCon, AudioEngine* engine = static_cast<AudioEngine*>(inRefCon); const auto bufferBeginAtOutput = - engine->mLink.clock().ticksToMicros(inTimeStamp->mHostTime) + engine->mOutputLatency.load(); + engine->mLink.clock().ticksToMicros(inTimeStamp->mHostTime) + + engine->mOutputLatency.load(); engine->audioCallback(bufferBeginAtOutput, inNumberFrames); diff --git a/examples/linkaudio/AudioPlatform_Portaudio.cpp b/examples/linkaudio/AudioPlatform_Portaudio.cpp index 17e7e05..c35f100 100644 --- a/examples/linkaudio/AudioPlatform_Portaudio.cpp +++ b/examples/linkaudio/AudioPlatform_Portaudio.cpp @@ -57,7 +57,7 @@ int AudioPlatform::audioCallback(const void* /*inputBuffer*/, const auto hostTime = platform.mHostTimeFilter.sampleTimeToHostTime(platform.mSampleTime); - platform.mSampleTime += inNumFrames; + platform.mSampleTime += static_cast<double>(inNumFrames); const auto bufferBeginAtOutput = hostTime + engine.mOutputLatency.load(); @@ -79,7 +79,7 @@ void AudioPlatform::initialize() { std::cerr << "Could not initialize Audio Engine. " << result << std::endl; std::terminate(); - }; + } PaStreamParameters outputParameters; outputParameters.device = Pa_GetDefaultOutputDevice(); diff --git a/examples/linkhut/main.cpp b/examples/linkhut/main.cpp index 7bd0fcf..92cb504 100644 --- a/examples/linkhut/main.cpp +++ b/examples/linkhut/main.cpp @@ -126,59 +126,60 @@ void input(State& state) { char in; -#if defined(LINK_PLATFORM_WINDOWS) - HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); - DWORD numCharsRead; - INPUT_RECORD inputRecord; - do + for (;;) { - ReadConsoleInput(stdinHandle, &inputRecord, 1, &numCharsRead); - } while ((inputRecord.EventType != KEY_EVENT) || inputRecord.Event.KeyEvent.bKeyDown); - in = inputRecord.Event.KeyEvent.uChar.AsciiChar; +#if defined(LINK_PLATFORM_WINDOWS) + HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); + DWORD numCharsRead; + INPUT_RECORD inputRecord; + do + { + ReadConsoleInput(stdinHandle, &inputRecord, 1, &numCharsRead); + } while ((inputRecord.EventType != KEY_EVENT) || inputRecord.Event.KeyEvent.bKeyDown); + in = inputRecord.Event.KeyEvent.uChar.AsciiChar; #elif defined(LINK_PLATFORM_UNIX) - in = static_cast<char>(std::cin.get()); + in = static_cast<char>(std::cin.get()); #endif - const auto tempo = state.link.captureAppSessionState().tempo(); - auto& engine = state.audioPlatform.mEngine; + const auto tempo = state.link.captureAppSessionState().tempo(); + auto& engine = state.audioPlatform.mEngine; - switch (in) - { - case 'q': - state.running = false; - clearLine(); - return; - case 'a': - state.link.enable(!state.link.isEnabled()); - break; - case 'w': - engine.setTempo(tempo - 1); - break; - case 'e': - engine.setTempo(tempo + 1); - break; - case 'r': - engine.setQuantum(engine.quantum() - 1); - break; - case 't': - engine.setQuantum(std::max(1., engine.quantum() + 1)); - break; - case 's': - engine.setStartStopSyncEnabled(!engine.isStartStopSyncEnabled()); - break; - case ' ': - if (engine.isPlaying()) + switch (in) { - engine.stopPlaying(); + case 'q': + state.running = false; + clearLine(); + return; + case 'a': + state.link.enable(!state.link.isEnabled()); + break; + case 'w': + engine.setTempo(tempo - 1); + break; + case 'e': + engine.setTempo(tempo + 1); + break; + case 'r': + engine.setQuantum(engine.quantum() - 1); + break; + case 't': + engine.setQuantum(std::max(1., engine.quantum() + 1)); + break; + case 's': + engine.setStartStopSyncEnabled(!engine.isStartStopSyncEnabled()); + break; + case ' ': + if (engine.isPlaying()) + { + engine.stopPlaying(); + } + else + { + engine.startPlaying(); + } + break; } - else - { - engine.startPlaying(); - } - break; } - - input(state); } } // namespace diff --git a/extensions/abl_link/.clang-format b/extensions/abl_link/.clang-format new file mode 100644 index 0000000..8fb4cfc --- /dev/null +++ b/extensions/abl_link/.clang-format @@ -0,0 +1,44 @@ +AccessModifierOffset: -2 +AlignAfterOpenBracket: DontAlign +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Allman +BreakBeforeTernaryOperators: true +ColumnLimit: 90 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +DerivePointerAlignment: false +IndentCaseLabels: false +IndentFunctionDeclarationAfterType: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 2 +PenaltyBreakBeforeFirstCallParameter: 0 +PenaltyReturnTypeOnItsOwnLine: 1000 +PointerAlignment: Right +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +UseTab: Never diff --git a/extensions/abl_link/CMakeLists.txt b/extensions/abl_link/CMakeLists.txt new file mode 100644 index 0000000..bccb597 --- /dev/null +++ b/extensions/abl_link/CMakeLists.txt @@ -0,0 +1,19 @@ +# _ _ _ _ _ +# | (_)_ __ | | __ | |__ _ _| |_ +# | | | '_ \| |/ / | '_ \| | | | __| +# | | | | | | < | | | | |_| | |_ +# |_|_|_| |_|_|\_\___|_| |_|\__,_|\__| +# |_____| + +set(link_hut_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/examples/link_hut/main.c +) + +source_group("link_hut" FILES ${link_hut_SOURCES}) + +add_executable(link_hut ${link_hut_SOURCES}) + +set_property(TARGET link_hut PROPERTY C_STANDARD 11) + +target_link_libraries(link_hut abl_link) + diff --git a/extensions/abl_link/README.md b/extensions/abl_link/README.md new file mode 100644 index 0000000..70f60a2 --- /dev/null +++ b/extensions/abl_link/README.md @@ -0,0 +1,17 @@ +# abl_link + +Plain C 11 wrapper for Ableton Link. + +# Building and Running abl_link Examples + +The `abl_link` library and the `link_hut` example application are built as part of the main CMake project in this repository. + +# Integrating abl_link into CMake-based Projects + +If you are using CMake, you can integrate `abl_link` by including both, the `Ableton::Link` and the `abl_link` configs: + +```cmake +include($PATH_TO_LINK/AbletonLinkConfig.cmake) +include($PATH_TO_LINK/extensions/abl_link/abl_link.cmake) +target_link_libraries($YOUR_TARGET abl_link) +``` diff --git a/extensions/abl_link/abl_link.cmake b/extensions/abl_link/abl_link.cmake new file mode 100644 index 0000000..00efd1e --- /dev/null +++ b/extensions/abl_link/abl_link.cmake @@ -0,0 +1,19 @@ +if(CMAKE_VERSION VERSION_LESS 3.0) + message(FATAL_ERROR "CMake 3.0 or greater is required") +endif() + +add_library(abl_link STATIC + ${CMAKE_CURRENT_LIST_DIR}/src/abl_link.cpp +) + +target_include_directories(abl_link PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include +) + +set_property(TARGET abl_link PROPERTY C_STANDARD 11) + +target_link_libraries(abl_link Ableton::Link) + +if(CMAKE_SYSTEM_NAME MATCHES "Linux|kFreeBSD|GNU") + target_link_libraries(abl_link atomic pthread) +endif() diff --git a/extensions/abl_link/examples/link_hut/main.c b/extensions/abl_link/examples/link_hut/main.c new file mode 100644 index 0000000..df7d9d8 --- /dev/null +++ b/extensions/abl_link/examples/link_hut/main.c @@ -0,0 +1,269 @@ +/* Copyright 2021, Ableton AG, Berlin. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * If you would like to incorporate Link into a proprietary software application, + * please contact <link-devs@ableton.com>. + */ + +#include <inttypes.h> +#include <math.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#if defined(LINK_PLATFORM_UNIX) +#include <sys/select.h> +#include <termios.h> +#include <unistd.h> +#elif defined(LINK_PLATFORM_WINDOWS) +#pragma warning(push, 0) +#pragma warning(disable : 4255) // 'no function prototype given' in winuser.h +#pragma warning(disable : 4668) // undefined preprocessor macro in winioctl.h +#pragma warning(disable : 5105) // "/wd5105" # "macro expansion producing 'defined' has + // undefined behavior" in winbase.h +#include <windows.h> +#pragma warning(pop) +#pragma warning(disable : 4100) // unreferenced formal parameter in main +#endif + +#include <abl_link.h> + +typedef struct state +{ + abl_link link; + abl_link_session_state session_state; + bool running; + double quantum; +} state; + +struct state *new_state(void) +{ + struct state *s = malloc(sizeof(state)); + s->link = abl_link_create(120); + s->session_state = abl_link_create_session_state(); + s->running = true; + s->quantum = 4; + return s; +} + +void delete_state(state *state) +{ + abl_link_destroy_session_state(state->session_state); + abl_link_destroy(state->link); + free(state); +} + +void disable_buffered_input(void) +{ +#if defined(LINK_PLATFORM_UNIX) + struct termios t; + tcgetattr(STDIN_FILENO, &t); + t.c_lflag &= ~ICANON; + tcsetattr(STDIN_FILENO, TCSANOW, &t); +#endif +} + +void enable_buffered_input(void) +{ +#if defined(LINK_PLATFORM_UNIX) + struct termios t; + tcgetattr(STDIN_FILENO, &t); + t.c_lflag |= ICANON; + tcsetattr(STDIN_FILENO, TCSANOW, &t); +#endif +} + +bool wait_for_input(void) +{ +#if defined(LINK_PLATFORM_UNIX) + fd_set selectset; + struct timeval timeout = {0, 50000}; + int ret; + FD_ZERO(&selectset); + FD_SET(0, &selectset); + ret = select(1, &selectset, NULL, NULL, &timeout); + if (ret > 0) + { + return true; + } +#elif (LINK_PLATFORM_WINDOWS) + HANDLE handle = GetStdHandle(STD_INPUT_HANDLE); + if (WaitForSingleObject(handle, 50) == WAIT_OBJECT_0) + { + return true; + } +#else +#error "Missing implementation" +#endif + return false; +} + +void clear_line(void) +{ + printf(" \r"); + fflush(stdout); +} + +void clear_input(void) +{ +#if defined(LINK_PLATFORM_WINDOWS) + { + HANDLE handle = GetStdHandle(STD_INPUT_HANDLE); + INPUT_RECORD r[512]; + DWORD read; + ReadConsoleInput(handle, r, 512, &read); + } +#endif +} + +void print_help(void) +{ + printf("\n\n < L I N K H U T >\n\n"); + printf("usage:\n"); + printf(" enable / disable Link: a\n"); + printf(" start / stop: space\n"); + printf(" decrease / increase tempo: w / e\n"); + printf(" decrease / increase quantum: r / t\n"); + printf(" enable / disable start stop sync: s\n"); + printf(" quit: q\n"); +} + +void print_state_header(void) +{ + printf( + "\nenabled | num peers | quantum | start stop sync | tempo | beats | metro\n"); +} + +void print_state(state *state) +{ + abl_link_capture_app_session_state(state->link, state->session_state); + const uint64_t time = abl_link_clock_micros(state->link); + const char *enabled = abl_link_is_enabled(state->link) ? "yes" : "no"; + const uint64_t num_peers = abl_link_num_peers(state->link); + const char *start_stop = + abl_link_is_start_stop_sync_enabled(state->link) ? "yes" : " no"; + const char *playing = + abl_link_is_playing(state->session_state) ? "[playing]" : "[stopped]"; + const double tempo = abl_link_tempo(state->session_state); + const double beats = abl_link_beat_at_time(state->session_state, time, state->quantum); + const double phase = abl_link_phase_at_time(state->session_state, time, state->quantum); + printf("%7s | ", enabled); + printf("%9" PRIu64 " | ", num_peers); + printf("%7.f | ", state->quantum); + printf("%3s %11s | ", start_stop, playing); + printf("%7.2f | ", tempo); + printf("%7.2f | ", beats); + for (int i = 0; i < ceil(state->quantum); ++i) + { + if (i < phase) + { + printf("X"); + } + else + { + printf("O"); + } + } +} + +void input(state *state) +{ + char in; + +#if defined(LINK_PLATFORM_UNIX) + in = (char)fgetc(stdin); +#elif defined(LINK_PLATFORM_WINDOWS) + HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); + DWORD numCharsRead; + INPUT_RECORD inputRecord; + do + { + ReadConsoleInput(stdinHandle, &inputRecord, 1, &numCharsRead); + } while ((inputRecord.EventType != KEY_EVENT) || inputRecord.Event.KeyEvent.bKeyDown); + in = inputRecord.Event.KeyEvent.uChar.AsciiChar; +#else +#error "Missing implementation" +#endif + + abl_link_capture_app_session_state(state->link, state->session_state); + const double tempo = abl_link_tempo(state->session_state); + const uint64_t timestamp = abl_link_clock_micros(state->link); + const bool enabled = abl_link_is_enabled(state->link); + switch (in) + { + case 'q': + state->running = false; + clear_line(); + return; + case 'a': + abl_link_enable(state->link, !enabled); + break; + case 'w': + abl_link_set_tempo(state->session_state, tempo - 1, timestamp); + break; + case 'e': + abl_link_set_tempo(state->session_state, tempo + 1, timestamp); + break; + case 'r': + state->quantum -= 1; + break; + case 't': + state->quantum += 1; + break; + case 's': + abl_link_enable_start_stop_sync( + state->link, !abl_link_is_start_stop_sync_enabled(state->link)); + break; + case ' ': + if (abl_link_is_playing(state->session_state)) + { + abl_link_set_is_playing( + state->session_state, false, abl_link_clock_micros(state->link)); + } + else + { + abl_link_set_is_playing_and_request_beat_at_time(state->session_state, true, + abl_link_clock_micros(state->link), 0, state->quantum); + } + break; + } + abl_link_commit_app_session_state(state->link, state->session_state); +} + +int main(int nargs, char **args) +{ + state *state = new_state(); + + print_help(); + print_state_header(); + disable_buffered_input(); + clear_input(); + + while (state->running) + { + clear_line(); + if (wait_for_input()) + { + input(state); + } + else + { + print_state(state); + } + } + + enable_buffered_input(); + delete_state(state); + return 0; +} diff --git a/extensions/abl_link/include/abl_link.h b/extensions/abl_link/include/abl_link.h new file mode 100644 index 0000000..34ac71b --- /dev/null +++ b/extensions/abl_link/include/abl_link.h @@ -0,0 +1,352 @@ +/* Copyright 2021, Ableton AG, Berlin. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * If you would like to incorporate Link into a proprietary software application, + * please contact <link-devs@ableton.com>. + */ + +#include <stdbool.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + + /*! + * @discussion Each abl_link instance has its own session state which + * represents a beat timeline and a transport start/stop state. The + * timeline starts running from beat 0 at the initial tempo when + * constructed. The timeline always advances at a speed defined by + * its current tempo, even if transport is stopped. Synchronizing to the + * transport start/stop state of Link is optional for every peer. + * The transport start/stop state is only shared with other peers when + * start/stop synchronization is enabled. + * + * An abl_link instance is initially disabled after construction, which + * means that it will not communicate on the network. Once enabled, + * an abl_link instance initiates network communication in an effort to + * discover other peers. When peers are discovered, they immediately + * become part of a shared Link session. + * + * Each function documents its thread-safety and + * realtime-safety properties. When a function is marked thread-safe, + * it means it is safe to call from multiple threads + * concurrently. When a function is marked realtime-safe, it means that + * it does not block and is appropriate for use in the thread that + * performs audio IO. + * + * One session state capture/commit function pair for use + * in the audio thread and one for all other application contexts is provided. + * In general, modifying the session state should be done in the audio + * thread for the most accurate timing results. The ability to modify + * the session state from application threads should only be used in + * cases where an application's audio thread is not actively running + * or if it doesn't generate audio at all. Modifying the Link session + * state from both the audio thread and an application thread + * concurrently is not advised and will potentially lead to unexpected + * behavior. + */ + + /*! @brief The representation of an abl_link instance*/ + typedef struct abl_link + { + void *impl; + } abl_link; + + /*! @brief Construct a new abl_link instance with an initial tempo. + * Thread-safe: yes + * Realtime-safe: no + */ + abl_link abl_link_create(double bpm); + + /*! @brief Delete an abl_link instance. + * Thread-safe: yes + * Realtime-safe: no + */ + void abl_link_destroy(abl_link link); + + /*! @brief Is Link currently enabled? + * Thread-safe: yes + * Realtime-safe: yes + */ + bool abl_link_is_enabled(abl_link link); + + /*! @brief Enable/disable Link. + * Thread-safe: yes + * Realtime-safe: no + */ + void abl_link_enable(abl_link link, bool enable); + + /*! @brief: Is start/stop synchronization enabled? + * Thread-safe: yes + * Realtime-safe: no + */ + bool abl_link_is_start_stop_sync_enabled(abl_link link); + + /*! @brief: Enable start/stop synchronization. + * Thread-safe: yes + * Realtime-safe: no + */ + void abl_link_enable_start_stop_sync(abl_link link, bool enabled); + + /*! @brief How many peers are currently connected in a Link session? + * Thread-safe: yes + * Realtime-safe: yes + */ + uint64_t abl_link_num_peers(abl_link link); + + /*! @brief Register a callback to be notified when the number of + * peers in the Link session changes. + * Thread-safe: yes + * Realtime-safe: no + * + * @discussion The callback is invoked on a Link-managed thread. + */ + typedef void (*abl_link_num_peers_callback)(uint64_t num_peers, void *context); + void abl_link_set_num_peers_callback( + abl_link link, abl_link_num_peers_callback callback, void *context); + + /*! @brief Register a callback to be notified when the session + * tempo changes. + * Thread-safe: yes + * Realtime-safe: no + * + * @discussion The callback is invoked on a Link-managed thread. + */ + typedef void (*abl_link_tempo_callback)(double tempo, void *context); + void abl_link_set_tempo_callback( + abl_link link, abl_link_tempo_callback callback, void *context); + + /*! brief: Register a callback to be notified when the state of + * start/stop isPlaying changes. + * Thread-safe: yes + * Realtime-safe: no + * + * @discussion The callback is invoked on a Link-managed thread. + */ + typedef void (*abl_link_start_stop_callback)(bool is_playing, void *context); + void abl_link_set_start_stop_callback( + abl_link link, abl_link_start_stop_callback callback, void *context); + + /*! brief: Get the current link clock time in microseconds. + * Thread-safe: yes + * Realtime-safe: yes + */ + int64_t abl_link_clock_micros(abl_link link); + + /*! @brief The representation of the current local state of a client in a Link Session + * + * @discussion A session state represents a timeline and the start/stop + * state. The timeline is a representation of a mapping between time and + * beats for varying quanta. The start/stop state represents the user + * intention to start or stop transport at a specific time. Start stop + * synchronization is an optional feature that allows to share the user + * request to start or stop transport between a subgroup of peers in a + * Link session. When observing a change of start/stop state, audio + * playback of a peer should be started or stopped the same way it would + * have happened if the user had requested that change at the according + * time locally. The start/stop state can only be changed by the user. + * This means that the current local start/stop state persists when + * joining or leaving a Link session. After joining a Link session + * start/stop change requests will be communicated to all connected peers. + */ + typedef struct abl_link_session_state + { + void *impl; + } abl_link_session_state; + + /*! @brief Create a new session_state instance. + * Thread-safe: yes + * Realtime-safe: no + * + * @discussion The session_state is to be used with the abl_link_capture... and + * abl_link_commit... functions to capture snapshots of the current link state and pass + * changes to the link session. + */ + abl_link_session_state abl_link_create_session_state(void); + + /*! @brief Delete a session_state instance. + * Thread-safe: yes + * Realtime-safe: no + */ + void abl_link_destroy_session_state(abl_link_session_state abl_link_session_state); + + /*! @brief Capture the current Link Session State from the audio thread. + * Thread-safe: no + * Realtime-safe: yes + * + * @discussion This function should ONLY be called in the audio thread and must not be + * accessed from any other threads. After capturing the session_state holds a snapshot + * of the current Link Session State, so it should be used in a local scope. The + * session_state should not be created on the audio thread. + */ + void abl_link_capture_audio_session_state( + abl_link link, abl_link_session_state session_state); + + /*! @brief Commit the given Session State to the Link session from the + * audio thread. + * Thread-safe: no + * Realtime-safe: yes + * + * @discussion This function should ONLY be called in the audio thread. The given + * session_state will replace the current Link state. Modifications will be + * communicated to other peers in the session. + */ + void abl_link_commit_audio_session_state( + abl_link link, abl_link_session_state session_state); + + /*! @brief Capture the current Link Session State from an application thread. + * Thread-safe: no + * Realtime-safe: yes + * + * @discussion Provides a mechanism for capturing the Link Session State from an + * application thread (other than the audio thread). After capturing the session_state + * contains a snapshot of the current Link state, so it should be used in a local + * scope. + */ + void abl_link_capture_app_session_state( + abl_link link, abl_link_session_state session_state); + + /*! @brief Commit the given Session State to the Link session from an + * application thread. + * Thread-safe: yes + * Realtime-safe: no + * + * @discussion The given session_state will replace the current Link Session State. + * Modifications of the Session State will be communicated to other peers in the + * session. + */ + void abl_link_commit_app_session_state( + abl_link link, abl_link_session_state session_state); + + /*! @brief: The tempo of the timeline, in Beats Per Minute. + * + * @discussion This is a stable value that is appropriate for display to the user. Beat + * time progress will not necessarily match this tempo exactly because of clock drift + * compensation. + */ + double abl_link_tempo(abl_link_session_state session_state); + + /*! @brief: Set the timeline tempo to the given bpm value, taking effect at the given + * time. + */ + void abl_link_set_tempo( + abl_link_session_state session_state, double bpm, int64_t at_time); + + /*! @brief: Get the beat value corresponding to the given time for the given quantum. + * + * @discussion: The magnitude of the resulting beat value is unique to this Link + * client, but its phase with respect to the provided quantum is shared among all + * session peers. For non-negative beat values, the following property holds: + * fmod(beatAtTime(t, q), q) == phaseAtTime(t, q) + */ + double abl_link_beat_at_time( + abl_link_session_state session_state, int64_t time, double quantum); + + /*! @brief: Get the session phase at the given time for the given quantum. + * + * @discussion: The result is in the interval [0, quantum). The result is equivalent to + * fmod(beatAtTime(t, q), q) for non-negative beat values. This function is convenient + * if the client application is only interested in the phase and not the beat + * magnitude. Also, unlike fmod, it handles negative beat values correctly. + */ + double abl_link_phase_at_time( + abl_link_session_state session_state, int64_t time, double quantum); + + /*! @brief: Get the time at which the given beat occurs for the given quantum. + * + * @discussion: The inverse of beatAtTime, assuming a constant tempo. + * beatAtTime(timeAtBeat(b, q), q) === b. + */ + int64_t abl_link_time_at_beat( + abl_link_session_state session_state, double beat, double quantum); + + /*! @brief: Attempt to map the given beat to the given time in the context of the given + * quantum. + * + * @discussion: This function behaves differently depending on the state of the + * session. If no other peers are connected, then this abl_link instance is in a + * session by itself and is free to re-map the beat/time relationship whenever it + * pleases. In this case, beatAtTime(time, quantum) == beat after this funtion has been + * called. + * + * If there are other peers in the session, this abl_link instance should not abruptly + * re-map the beat/time relationship in the session because that would lead to beat + * discontinuities among the other peers. In this case, the given beat will be mapped + * to the next time value greater than the given time with the same phase as the given + * beat. + * + * This function is specifically designed to enable the concept of "quantized launch" + * in client applications. If there are no other peers in the session, then an event + * (such as starting transport) happens immediately when it is requested. If there are + * other peers, however, we wait until the next time at which the session phase matches + * the phase of the event, thereby executing the event in-phase with the other peers in + * the session. The client application only needs to invoke this function to achieve + * this behavior and should not need to explicitly check the number of peers. + */ + void abl_link_request_beat_at_time( + abl_link_session_state session_state, double beat, int64_t time, double quantum); + + /*! @brief: Rudely re-map the beat/time relationship for all peers in a session. + * + * @discussion: DANGER: This function should only be needed in certain special + * circumstances. Most applications should not use it. It is very similar to + * requestBeatAtTime except that it does not fall back to the quantizing behavior when + * it is in a session with other peers. Calling this function will unconditionally map + * the given beat to the given time and broadcast the result to the session. This is + * very anti-social behavior and should be avoided. + * + * One of the few legitimate uses of this function is to synchronize a Link session + * with an external clock source. By periodically forcing the beat/time mapping + * according to an external clock source, a peer can effectively bridge that clock into + * a Link session. Much care must be taken at the application layer when implementing + * such a feature so that users do not accidentally disrupt Link sessions that they may + * join. + */ + void abl_link_force_beat_at_time( + abl_link_session_state session_state, double beat, uint64_t time, double quantum); + + /*! @brief: Set if transport should be playing or stopped, taking effect at the given + * time. + */ + void abl_link_set_is_playing( + abl_link_session_state session_state, bool is_playing, uint64_t time); + + /*! @brief: Is transport playing? */ + bool abl_link_is_playing(abl_link_session_state session_state); + + /*! @brief: Get the time at which a transport start/stop occurs */ + uint64_t abl_link_time_for_is_playing(abl_link_session_state session_state); + + /*! @brief: Convenience function to attempt to map the given beat to the time + * when transport is starting to play in context of the given quantum. + * This function evaluates to a no-op if abl_link_is_playing equals false. + */ + void abl_link_request_beat_at_start_playing_time( + abl_link_session_state session_state, double beat, double quantum); + + /*! @brief: Convenience function to start or stop transport at a given time and attempt + * to map the given beat to this time in context of the given quantum. */ + void abl_link_set_is_playing_and_request_beat_at_time( + abl_link_session_state session_state, + bool is_playing, + uint64_t time, + double beat, + double quantum); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/extensions/abl_link/src/abl_link.cpp b/extensions/abl_link/src/abl_link.cpp new file mode 100644 index 0000000..e34f6b5 --- /dev/null +++ b/extensions/abl_link/src/abl_link.cpp @@ -0,0 +1,214 @@ +/* Copyright 2021, Ableton AG, Berlin. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * If you would like to incorporate Link into a proprietary software application, + * please contact <link-devs@ableton.com>. + */ + +#include <abl_link.h> +#include <ableton/Link.hpp> + +extern "C" +{ + abl_link abl_link_create(double bpm) + { + return abl_link{reinterpret_cast<void *>(new ableton::Link(bpm))}; + } + + void abl_link_destroy(abl_link link) + { + delete reinterpret_cast<ableton::Link *>(link.impl); + } + + bool abl_link_is_enabled(abl_link link) + { + return reinterpret_cast<ableton::Link *>(link.impl)->isEnabled(); + } + + void abl_link_enable(abl_link link, bool enabled) + { + reinterpret_cast<ableton::Link *>(link.impl)->enable(enabled); + } + + bool abl_link_is_start_stop_sync_enabled(abl_link link) + { + return reinterpret_cast<ableton::Link *>(link.impl)->isStartStopSyncEnabled(); + } + + void abl_link_enable_start_stop_sync(abl_link link, bool enabled) + { + reinterpret_cast<ableton::Link *>(link.impl)->enableStartStopSync(enabled); + } + + uint64_t abl_link_num_peers(abl_link link) + { + return reinterpret_cast<ableton::Link *>(link.impl)->numPeers(); + } + + void abl_link_set_num_peers_callback( + abl_link link, abl_link_num_peers_callback callback, void *context) + { + reinterpret_cast<ableton::Link *>(link.impl)->setNumPeersCallback( + [callback, context]( + std::size_t numPeers) { (*callback)(static_cast<uint64_t>(numPeers), context); }); + } + + void abl_link_set_tempo_callback( + abl_link link, abl_link_tempo_callback callback, void *context) + { + reinterpret_cast<ableton::Link *>(link.impl)->setTempoCallback( + [callback, context](double tempo) { (*callback)(tempo, context); }); + } + + void abl_link_set_start_stop_callback( + abl_link link, abl_link_start_stop_callback callback, void *context) + { + reinterpret_cast<ableton::Link *>(link.impl)->setStartStopCallback( + [callback, context](bool isPlaying) { (*callback)(isPlaying, context); }); + } + + int64_t abl_link_clock_micros(abl_link link) + { + return reinterpret_cast<ableton::Link *>(link.impl)->clock().micros().count(); + } + + abl_link_session_state abl_link_create_session_state(void) + { + return abl_link_session_state{reinterpret_cast<void *>( + new ableton::Link::SessionState{ableton::link::ApiState{}, {}})}; + } + + void abl_link_destroy_session_state(abl_link_session_state session_state) + { + delete reinterpret_cast<ableton::Link::SessionState *>(session_state.impl); + } + + void abl_link_capture_app_session_state( + abl_link link, abl_link_session_state session_state) + { + *reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) = + reinterpret_cast<ableton::Link *>(link.impl)->captureAppSessionState(); + } + + void abl_link_commit_app_session_state( + abl_link link, abl_link_session_state session_state) + { + reinterpret_cast<ableton::Link *>(link.impl)->commitAppSessionState( + *reinterpret_cast<ableton::Link::SessionState *>(session_state.impl)); + } + + void abl_link_capture_audio_session_state( + abl_link link, abl_link_session_state session_state) + { + *reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) = + reinterpret_cast<ableton::Link *>(link.impl)->captureAudioSessionState(); + } + + void abl_link_commit_audio_session_state( + abl_link link, abl_link_session_state session_state) + { + reinterpret_cast<ableton::Link *>(link.impl)->commitAudioSessionState( + *reinterpret_cast<ableton::Link::SessionState *>(session_state.impl)); + } + + double abl_link_tempo(abl_link_session_state session_state) + { + return reinterpret_cast<ableton::Link::SessionState *>(session_state.impl)->tempo(); + } + + void abl_link_set_tempo( + abl_link_session_state session_state, double bpm, int64_t at_time) + { + reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) + ->setTempo(bpm, std::chrono::microseconds{at_time}); + } + + double abl_link_beat_at_time( + abl_link_session_state session_state, int64_t time, double quantum) + { + auto micros = std::chrono::microseconds{time}; + return reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) + ->beatAtTime(micros, quantum); + } + + double abl_link_phase_at_time( + abl_link_session_state session_state, int64_t time, double quantum) + { + return reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) + ->phaseAtTime(std::chrono::microseconds{time}, quantum); + } + + int64_t abl_link_time_at_beat( + abl_link_session_state session_state, double beat, double quantum) + { + return reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) + ->timeAtBeat(beat, quantum) + .count(); + } + + void abl_link_request_beat_at_time( + abl_link_session_state session_state, double beat, int64_t time, double quantum) + { + reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) + ->requestBeatAtTime(beat, std::chrono::microseconds{time}, quantum); + } + + void abl_link_force_beat_at_time( + abl_link_session_state session_state, double beat, uint64_t time, double quantum) + { + reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) + ->forceBeatAtTime(beat, std::chrono::microseconds{time}, quantum); + } + + void abl_link_set_is_playing( + abl_link_session_state session_state, bool is_playing, uint64_t time) + { + reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) + ->setIsPlaying(is_playing, std::chrono::microseconds(time)); + } + + bool abl_link_is_playing(abl_link_session_state session_state) + { + return reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) + ->isPlaying(); + } + + uint64_t abl_link_time_for_is_playing(abl_link_session_state session_state) + { + return static_cast<uint64_t>( + reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) + ->timeForIsPlaying() + .count()); + } + + void abl_link_request_beat_at_start_playing_time( + abl_link_session_state session_state, double beat, double quantum) + { + reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) + ->requestBeatAtStartPlayingTime(beat, quantum); + } + + void abl_link_set_is_playing_and_request_beat_at_time( + abl_link_session_state session_state, + bool is_playing, + uint64_t time, + double beat, + double quantum) + { + reinterpret_cast<ableton::Link::SessionState *>(session_state.impl) + ->setIsPlayingAndRequestBeatAtTime( + is_playing, std::chrono::microseconds{time}, beat, quantum); + } +} diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 0d9c71e..1c7b5ed 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -11,17 +11,16 @@ project(LinkCore) set(link_core_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ableton/link) set(link_core_HEADERS ${link_core_DIR}/Beats.hpp - ${link_core_DIR}/CircularFifo.hpp ${link_core_DIR}/ClientSessionTimelines.hpp ${link_core_DIR}/Controller.hpp ${link_core_DIR}/Gateway.hpp ${link_core_DIR}/GhostXForm.hpp ${link_core_DIR}/HostTimeFilter.hpp - ${link_core_DIR}/Kalman.hpp ${link_core_DIR}/LinearRegression.hpp ${link_core_DIR}/Measurement.hpp ${link_core_DIR}/MeasurementEndpointV4.hpp ${link_core_DIR}/MeasurementService.hpp + ${link_core_DIR}/Median.hpp ${link_core_DIR}/NodeId.hpp ${link_core_DIR}/NodeState.hpp ${link_core_DIR}/PayloadEntries.hpp @@ -36,6 +35,7 @@ set(link_core_HEADERS ${link_core_DIR}/StartStopState.hpp ${link_core_DIR}/Tempo.hpp ${link_core_DIR}/Timeline.hpp + ${link_core_DIR}/TripleBuffer.hpp ${link_core_DIR}/v1/Messages.hpp PARENT_SCOPE ) @@ -100,16 +100,23 @@ elseif(UNIX) ${link_platform_HEADERS} ${link_platform_DIR}/darwin/Clock.hpp ${link_platform_DIR}/darwin/Darwin.hpp + ${link_platform_DIR}/darwin/ThreadFactory.hpp ${link_platform_DIR}/stl/Random.hpp ) - elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + elseif(CMAKE_SYSTEM_NAME MATCHES "Linux|kFreeBSD|GNU") set(link_platform_HEADERS ${link_platform_HEADERS} ${link_platform_DIR}/linux/Clock.hpp ${link_platform_DIR}/linux/Linux.hpp ${link_platform_DIR}/stl/Clock.hpp ${link_platform_DIR}/stl/Random.hpp - ) + ) + if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set(link_platform_HEADERS + ${link_platform_HEADERS} + ${link_platform_DIR}/linux/ThreadFactory.hpp + ) + endif() endif() elseif(WIN32) set(link_platform_HEADERS @@ -117,6 +124,7 @@ elseif(WIN32) ${link_platform_DIR}/stl/Random.hpp ${link_platform_DIR}/windows/Clock.hpp ${link_platform_DIR}/windows/ScanIpIfAddrs.hpp + ${link_platform_DIR}/windows/ThreadFactory.hpp ${link_platform_DIR}/windows/Windows.hpp ) endif() diff --git a/include/ableton/Link.hpp b/include/ableton/Link.hpp index 9ba8b3a..8b91597 100644 --- a/include/ableton/Link.hpp +++ b/include/ableton/Link.hpp @@ -372,8 +372,7 @@ public: }; private: - using Controller = ableton::link::Controller< - link::PeerCountCallback, + using Controller = ableton::link::Controller<link::PeerCountCallback, link::TempoCallback, link::StartStopStateCallback, Clock, diff --git a/include/ableton/Link.ipp b/include/ableton/Link.ipp index 908f8e8..f8cbce8 100644 --- a/include/ableton/Link.ipp +++ b/include/ableton/Link.ipp @@ -44,7 +44,7 @@ inline link::IncomingClientState toIncomingClientState(const link::ApiState& sta const auto startStopState = originalState.startStopState != state.startStopState ? link::OptionalClientStartStopState{{state.startStopState.isPlaying, - state.startStopState.time, timestamp}} + state.startStopState.time, timestamp}} : link::OptionalClientStartStopState{}; return {timeline, startStopState, timestamp}; } @@ -53,8 +53,7 @@ inline link::IncomingClientState toIncomingClientState(const link::ApiState& sta template <typename Clock> inline BasicLink<Clock>::BasicLink(const double bpm) - : mController( - link::Tempo(bpm), + : mController(link::Tempo(bpm), [this](const std::size_t peers) { std::lock_guard<std::mutex> lock(mCallbackMutex); mPeerCountCallback(peers); @@ -67,8 +66,7 @@ inline BasicLink<Clock>::BasicLink(const double bpm) std::lock_guard<std::mutex> lock(mCallbackMutex); mStartStopCallback(isPlaying); }, - mClock, - util::injectVal(link::platform::IoContext{})) + mClock) { } @@ -133,26 +131,30 @@ inline Clock BasicLink<Clock>::clock() const } template <typename Clock> -inline typename BasicLink<Clock>::SessionState BasicLink<Clock>::captureAudioSessionState() const +inline typename BasicLink<Clock>::SessionState BasicLink< + Clock>::captureAudioSessionState() const { return detail::toSessionState<Clock>(mController.clientStateRtSafe(), numPeers() > 0); } template <typename Clock> -inline void BasicLink<Clock>::commitAudioSessionState(const typename BasicLink<Clock>::SessionState state) +inline void BasicLink<Clock>::commitAudioSessionState( + const typename BasicLink<Clock>::SessionState state) { mController.setClientStateRtSafe( detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros())); } template <typename Clock> -inline typename BasicLink<Clock>::SessionState BasicLink<Clock>::captureAppSessionState() const +inline typename BasicLink<Clock>::SessionState BasicLink<Clock>::captureAppSessionState() + const { return detail::toSessionState<Clock>(mController.clientState(), numPeers() > 0); } template <typename Clock> -inline void BasicLink<Clock>::commitAppSessionState(const typename BasicLink<Clock>::SessionState state) +inline void BasicLink<Clock>::commitAppSessionState( + const typename BasicLink<Clock>::SessionState state) { mController.setClientState( detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros())); diff --git a/include/ableton/discovery/NetworkByteStreamSerializable.hpp b/include/ableton/discovery/NetworkByteStreamSerializable.hpp index 6de27cc..49a1195 100644 --- a/include/ableton/discovery/NetworkByteStreamSerializable.hpp +++ b/include/ableton/discovery/NetworkByteStreamSerializable.hpp @@ -285,7 +285,8 @@ struct Deserialize<std::chrono::microseconds> static std::pair<std::chrono::microseconds, It> fromNetworkByteStream(It begin, It end) { using namespace std; - auto result = Deserialize<int64_t>::fromNetworkByteStream(std::move(begin), std::move(end)); + auto result = + Deserialize<int64_t>::fromNetworkByteStream(std::move(begin), std::move(end)); return make_pair(chrono::microseconds{result.first}, result.second); } }; @@ -360,8 +361,8 @@ struct Deserialize<std::array<T, Size>> { using namespace std; array<T, Size> result{}; - auto resultIt = - detail::deserializeContainer<T>(std::move(begin), std::move(end), std::move(result.begin()), Size); + auto resultIt = detail::deserializeContainer<T>( + std::move(begin), std::move(end), std::move(result.begin()), Size); return make_pair(std::move(result), std::move(resultIt)); } }; @@ -391,8 +392,8 @@ struct Deserialize<std::vector<T, Alloc>> auto result_size = Deserialize<uint32_t>::fromNetworkByteStream(std::move(bytesBegin), bytesEnd); vector<T, Alloc> result; - auto resultIt = detail::deserializeContainer<T>( - std::move(result_size.second), std::move(bytesEnd), back_inserter(result), result_size.first); + auto resultIt = detail::deserializeContainer<T>(std::move(result_size.second), + std::move(bytesEnd), back_inserter(result), result_size.first); return make_pair(std::move(result), std::move(resultIt)); } }; @@ -420,7 +421,8 @@ struct Deserialize<std::tuple<X, Y>> using namespace std; auto xres = Deserialize<X>::fromNetworkByteStream(begin, end); auto yres = Deserialize<Y>::fromNetworkByteStream(xres.second, end); - return make_pair(make_tuple(std::move(xres.first), std::move(yres.first)), std::move(yres.second)); + return make_pair( + make_tuple(std::move(xres.first), std::move(yres.first)), std::move(yres.second)); } }; @@ -450,7 +452,8 @@ struct Deserialize<std::tuple<X, Y, Z>> auto xres = Deserialize<X>::fromNetworkByteStream(begin, end); auto yres = Deserialize<Y>::fromNetworkByteStream(xres.second, end); auto zres = Deserialize<Z>::fromNetworkByteStream(yres.second, end); - return make_pair(make_tuple(std::move(xres.first), std::move(yres.first), std::move(zres.first)), + return make_pair( + make_tuple(std::move(xres.first), std::move(yres.first), std::move(zres.first)), std::move(zres.second)); } }; diff --git a/include/ableton/discovery/Payload.hpp b/include/ableton/discovery/Payload.hpp index 2c93241..1d48caa 100644 --- a/include/ableton/discovery/Payload.hpp +++ b/include/ableton/discovery/Payload.hpp @@ -57,7 +57,8 @@ struct PayloadEntryHeader Size size; tie(key, begin) = Deserialize<Key>::fromNetworkByteStream(begin, end); tie(size, begin) = Deserialize<Size>::fromNetworkByteStream(begin, end); - return make_pair(PayloadEntryHeader{std::move(key), std::move(size)}, std::move(begin)); + return make_pair( + PayloadEntryHeader{std::move(key), std::move(size)}, std::move(begin)); } }; @@ -287,7 +288,8 @@ template <typename... Entries, typename It, typename... Handlers> void parsePayload(It begin, It end, Handlers... handlers) { using namespace std; - ParsePayload<Entries...>::parse(std::move(begin), std::move(end), std::move(handlers)...); + ParsePayload<Entries...>::parse( + std::move(begin), std::move(end), std::move(handlers)...); } } // namespace discovery diff --git a/include/ableton/discovery/PeerGateway.hpp b/include/ableton/discovery/PeerGateway.hpp index 2d35f03..db43439 100644 --- a/include/ableton/discovery/PeerGateway.hpp +++ b/include/ableton/discovery/PeerGateway.hpp @@ -242,8 +242,8 @@ IpV4Gateway<PeerObserver, NodeState, IoContext> makeIpV4Gateway( auto iface = makeIpV4Interface<v1::kMaxMessageSize>(injectRef(*io), addr); - auto messenger = - makeUdpMessenger(injectVal(std::move(iface)), std::move(state), injectRef(*io), ttl, ttlRatio); + auto messenger = makeUdpMessenger( + injectVal(std::move(iface)), std::move(state), injectRef(*io), ttl, ttlRatio); return {injectVal(std::move(messenger)), std::move(observer), std::move(io)}; } diff --git a/include/ableton/discovery/PeerGateways.hpp b/include/ableton/discovery/PeerGateways.hpp index ed5cfe5..bdefbae 100644 --- a/include/ableton/discovery/PeerGateways.hpp +++ b/include/ableton/discovery/PeerGateways.hpp @@ -53,9 +53,8 @@ public: ~PeerGateways() { - // Release the callback in the io thread so that gateway cleanup - // doesn't happen in the client thread - mIo->async(Deleter{*this}); + mpScanner.reset(); + mpScannerCallback.reset(); } PeerGateways(const PeerGateways&) = delete; @@ -66,42 +65,22 @@ public: void enable(const bool bEnable) { - auto pCallback = mpScannerCallback; - auto pScanner = mpScanner; - - if (pCallback && pScanner) - { - mIo->async([pCallback, pScanner, bEnable] { - pCallback->mGateways.clear(); - pScanner->enable(bEnable); - }); - } + mpScannerCallback->mGateways.clear(); + mpScanner->enable(bEnable); } template <typename Handler> - void withGatewaysAsync(Handler handler) + void withGateways(Handler handler) { - auto pCallback = mpScannerCallback; - if (pCallback) - { - mIo->async([pCallback, handler] { - handler(pCallback->mGateways.begin(), pCallback->mGateways.end()); - }); - } + handler(mpScannerCallback->mGateways.begin(), mpScannerCallback->mGateways.end()); } void updateNodeState(const NodeState& state) { - auto pCallback = mpScannerCallback; - if (pCallback) + mpScannerCallback->mState = state; + for (const auto& entry : mpScannerCallback->mGateways) { - mIo->async([pCallback, state] { - pCallback->mState = state; - for (const auto& entry : pCallback->mGateways) - { - entry.second->updateNodeState(state); - } - }); + entry.second->updateNodeState(state); } } @@ -109,18 +88,11 @@ public: // this method can be invoked to either fix it or discard it. void repairGateway(const asio::ip::address& gatewayAddr) { - auto pCallback = mpScannerCallback; - auto pScanner = mpScanner; - if (pCallback && pScanner) + if (mpScannerCallback->mGateways.erase(gatewayAddr)) { - mIo->async([pCallback, pScanner, gatewayAddr] { - if (pCallback->mGateways.erase(gatewayAddr)) - { - // If we erased a gateway, rescan again immediately so that - // we will re-initialize it if it's still present - pScanner->scan(); - } - }); + // If we erased a gateway, rescan again immediately so that + // we will re-initialize it if it's still present + mpScanner->scan(); } } @@ -187,25 +159,6 @@ private: }; using Scanner = InterfaceScanner<std::shared_ptr<Callback>, IoType&>; - - struct Deleter - { - Deleter(PeerGateways& gateways) - : mpScannerCallback(std::move(gateways.mpScannerCallback)) - , mpScanner(std::move(gateways.mpScanner)) - { - } - - void operator()() - { - mpScanner.reset(); - mpScannerCallback.reset(); - } - - std::shared_ptr<Callback> mpScannerCallback; - std::shared_ptr<Scanner> mpScanner; - }; - std::shared_ptr<Callback> mpScannerCallback; std::shared_ptr<Scanner> mpScanner; util::Injected<IoContext> mIo; diff --git a/include/ableton/discovery/Service.hpp b/include/ableton/discovery/Service.hpp index 83d3fe0..8898fce 100644 --- a/include/ableton/discovery/Service.hpp +++ b/include/ableton/discovery/Service.hpp @@ -46,9 +46,9 @@ public: // Asynchronously operate on the current set of peer gateways. The // handler will be invoked in the service's io context. template <typename Handler> - void withGatewaysAsync(Handler handler) + void withGateways(Handler handler) { - mGateways.withGatewaysAsync(std::move(handler)); + mGateways.withGateways(std::move(handler)); } void updateNodeState(const NodeState& state) diff --git a/include/ableton/discovery/v1/Messages.hpp b/include/ableton/discovery/v1/Messages.hpp index 73ba8c3..1707b8e 100644 --- a/include/ableton/discovery/v1/Messages.hpp +++ b/include/ableton/discovery/v1/Messages.hpp @@ -110,8 +110,8 @@ It encodeMessage(NodeId from, if (messageSize < kMaxMessageSize) { return toNetworkByteStream( - payload, toNetworkByteStream( - header, copy(begin(kProtocolHeader), end(kProtocolHeader), std::move(out)))); + payload, toNetworkByteStream(header, + copy(begin(kProtocolHeader), end(kProtocolHeader), std::move(out)))); } else { diff --git a/include/ableton/link/Beats.hpp b/include/ableton/link/Beats.hpp index bf3d45e..de8ec89 100644 --- a/include/ableton/link/Beats.hpp +++ b/include/ableton/link/Beats.hpp @@ -118,7 +118,7 @@ struct Beats } private: - std::int64_t mValue = 0; + std::int64_t mValue = 0; }; } // namespace link diff --git a/include/ableton/link/CircularFifo.hpp b/include/ableton/link/CircularFifo.hpp deleted file mode 100644 index 8ee8a26..0000000 --- a/include/ableton/link/CircularFifo.hpp +++ /dev/null @@ -1,95 +0,0 @@ -/* Copyright 2016, Ableton AG, Berlin. All rights reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * If you would like to incorporate Link into a proprietary software application, - * please contact <link-devs@ableton.com>. - */ - -#pragma once - -#include <array> -#include <atomic> -#include <cassert> -#include <cstddef> - -#include <ableton/link/Optional.hpp> - -namespace ableton -{ -namespace link -{ - -// Single producer, single consumer lockfree Fifo - -template <typename Type, std::size_t size> -class CircularFifo -{ -public: - CircularFifo() - : tail(0) - , head(0) - { - assert(head.is_lock_free() && tail.is_lock_free()); - } - - bool push(Type item) - { - const auto currentTail = tail.load(); - const auto nextTail = nextIndex(currentTail); - if (nextTail != head.load()) - { - data[currentTail] = std::move(item); - tail.store(nextTail); - return true; - } - return false; - } - - Optional<Type> pop() - { - const auto currentHead = head.load(); - if (currentHead == tail.load()) - { - return {}; - } - - auto item = data[currentHead]; - head.store(nextIndex(currentHead)); - return Optional<Type>{std::move(item)}; - } - - bool isEmpty() const - { - return tail == head; - } - -private: - size_t nextIndex(const size_t index) const - { - return (index + 1) % (size + 1); - } - - size_t previousIndex(const size_t index) const - { - return (index + size) % (size + 1); - } - - std::atomic_size_t tail; - std::atomic_size_t head; - std::array<Type, size + 1> data; -}; - -} // namespace link -} // namespace ableton diff --git a/include/ableton/link/Controller.hpp b/include/ableton/link/Controller.hpp index ef802f9..2922f46 100644 --- a/include/ableton/link/Controller.hpp +++ b/include/ableton/link/Controller.hpp @@ -20,7 +20,6 @@ #pragma once #include <ableton/discovery/Service.hpp> -#include <ableton/link/CircularFifo.hpp> #include <ableton/link/ClientSessionTimelines.hpp> #include <ableton/link/Gateway.hpp> #include <ableton/link/GhostXForm.hpp> @@ -29,6 +28,8 @@ #include <ableton/link/SessionState.hpp> #include <ableton/link/Sessions.hpp> #include <ableton/link/StartStopState.hpp> +#include <ableton/link/TripleBuffer.hpp> +#include <condition_variable> #include <mutex> namespace ableton @@ -127,8 +128,7 @@ public: PeerCountCallback peerCallback, TempoCallback tempoCallback, StartStopStateCallback startStopStateCallback, - Clock clock, - util::Injected<IoContext> io) + Clock clock) : mTempoCallback(std::move(tempoCallback)) , mStartStopStateCallback(std::move(startStopStateCallback)) , mClock(std::move(clock)) @@ -137,12 +137,12 @@ public: , mSessionState(detail::initSessionState(tempo, mClock)) , mClientState(detail::initClientState(mSessionState)) , mLastIsPlayingForStartStopStateCallback(false) - , mRtClientState(detail::initRtClientState(mClientState)) + , mRtClientState(detail::initRtClientState(mClientState.get())) , mHasPendingRtClientStates(false) , mSessionPeerCounter(*this, std::move(peerCallback)) , mEnabled(false) , mStartStopSyncEnabled(false) - , mIo(std::move(io)) + , mIo(IoContext{UdpSendExceptionHandler{this}}) , mRtClientStateSetter(*this) , mPeers(util::injectRef(*mIo), std::ref(mSessionPeerCounter), @@ -159,7 +159,7 @@ public: mSessionState.startStopState}, mSessionState.ghostXForm), GatewayFactory{*this}, - util::injectVal(mIo->clone(UdpSendExceptionHandler{*this}))) + util::injectRef(*mIo)) { } @@ -171,6 +171,20 @@ public: ~Controller() { + std::mutex mutex; + std::condition_variable condition; + auto stopped = false; + + mIo->async([this, &mutex, &condition, &stopped]() { + enable(false); + std::unique_lock<std::mutex> lock(mutex); + stopped = true; + condition.notify_one(); + }); + + std::unique_lock<std::mutex> lock(mutex); + condition.wait(lock, [&stopped] { return stopped; }); + mIo->stop(); } @@ -218,30 +232,27 @@ public: // it cannot be used from audio thread. ClientState clientState() const { - std::lock_guard<std::mutex> lock(mClientStateGuard); - return mClientState; + return mClientState.get(); } // Set the client state to be used, starting at the given time. // Thread-safe but may block, so it cannot be used from audio thread. void setClientState(IncomingClientState newClientState) { - { - std::lock_guard<std::mutex> lock(mClientStateGuard); + mClientState.update([&](ClientState& clientState) { if (newClientState.timeline) { *newClientState.timeline = clampTempo(*newClientState.timeline); - mClientState.timeline = *newClientState.timeline; + clientState.timeline = *newClientState.timeline; } if (newClientState.startStopState) { // Prevent updating client start stop state with an outdated start stop state *newClientState.startStopState = detail::selectPreferredStartStopState( - mClientState.startStopState, *newClientState.startStopState); - mClientState.startStopState = *newClientState.startStopState; + clientState.startStopState, *newClientState.startStopState); + clientState.startStopState = *newClientState.startStopState; } - } - + }); mIo->async([this, newClientState] { handleClientState(newClientState); }); } @@ -262,11 +273,9 @@ public: const auto startStopStateGracePeriodOver = now - mRtClientState.startStopStateTimestamp > detail::kLocalModGracePeriod; - if ((timelineGracePeriodOver || startStopStateGracePeriodOver) - && mClientStateGuard.try_lock()) + if (timelineGracePeriodOver || startStopStateGracePeriodOver) { - const auto clientState = mClientState; - mClientStateGuard.unlock(); + const auto clientState = mClientState.getRt(); if (timelineGracePeriodOver && clientState.timeline != mRtClientState.timeline) { @@ -307,24 +316,19 @@ public: // This flag ensures that mRtClientState is only updated after all incoming // client states were processed mHasPendingRtClientStates = true; - // This will fail in case the Fifo in the RtClientStateSetter is full. This indicates - // a very high rate of calls to the setter. In this case we ignore one value because - // we expect the setter to be called again soon. - if (mRtClientStateSetter.tryPush(newClientState)) + mRtClientStateSetter.push(newClientState); + const auto now = mClock.micros(); + // Cache the new timeline and StartStopState for serving back to the client + if (newClientState.timeline) { - const auto now = mClock.micros(); // Cache the new timeline and StartStopState for serving back to the client - if (newClientState.timeline) - { - // Cache the new timeline and StartStopState for serving back to the client - mRtClientState.timeline = *newClientState.timeline; - mRtClientState.timelineTimestamp = makeRtTimestamp(now); - } - if (newClientState.startStopState) - { - mRtClientState.startStopState = *newClientState.startStopState; - mRtClientState.startStopStateTimestamp = makeRtTimestamp(now); - } + mRtClientState.timeline = *newClientState.timeline; + mRtClientState.timelineTimestamp = makeRtTimestamp(now); + } + if (newClientState.startStopState) + { + mRtClientState.startStopState = *newClientState.startStopState; + mRtClientState.startStopStateTimestamp = makeRtTimestamp(now); } } @@ -337,12 +341,12 @@ private: void invokeStartStopStateCallbackIfChanged() { bool shouldInvokeCallback = false; - { - std::lock_guard<std::mutex> lock(mClientStateGuard); + + mClientState.update([&](ClientState& clientState) { shouldInvokeCallback = - mLastIsPlayingForStartStopStateCallback != mClientState.startStopState.isPlaying; - mLastIsPlayingForStartStopStateCallback = mClientState.startStopState.isPlaying; - } + mLastIsPlayingForStartStopStateCallback != clientState.startStopState.isPlaying; + mLastIsPlayingForStartStopStateCallback = clientState.startStopState.isPlaying; + }); if (shouldInvokeCallback) { @@ -376,20 +380,19 @@ private: } // Update the client timeline and start stop state based on the new session timing - { - std::lock_guard<std::mutex> timelineLock(mClientStateGuard); - mClientState.timeline = updateClientTimelineFromSession(mClientState.timeline, + mClientState.update([&](ClientState& clientState) { + clientState.timeline = updateClientTimelineFromSession(clientState.timeline, mSessionState.timeline, mClock.micros(), mSessionState.ghostXForm); // Don't pass the start stop state to the client when start stop sync is disabled // or when we have a default constructed start stop state if (mStartStopSyncEnabled && mSessionState.startStopState != StartStopState{}) { std::lock_guard<std::mutex> startStopStateLock(mSessionStateGuard); - mClientState.startStopState = + clientState.startStopState = detail::mapStartStopStateFromSessionToClient(mSessionState.startStopState, mSessionState.timeline, mSessionState.ghostXForm); } - } + }); if (oldTimeline.tempo != newTimeline.tempo) { @@ -430,11 +433,10 @@ private: if (mStartStopSyncEnabled) { - { - std::lock_guard<std::mutex> lock(mClientStateGuard); - mClientState.startStopState = detail::mapStartStopStateFromSessionToClient( + mClientState.update([&](ClientState& clientState) { + clientState.startStopState = detail::mapStartStopStateFromSessionToClient( startStopState, mSessionState.timeline, mSessionState.ghostXForm); - } + }); invokeStartStopStateCallbackIfChanged(); } } @@ -463,13 +465,12 @@ private: mSessionState.ghostXForm.hostToGhost(clientState.startStopState->timestamp); if (newGhostTime > mSessionState.startStopState.timestamp) { - { - std::lock_guard<std::mutex> lock(mClientStateGuard); + mClientState.update([&](ClientState& currentClientState) { mSessionState.startStopState = detail::mapStartStopStateFromClientToSession(*clientState.startStopState, mSessionState.timeline, mSessionState.ghostXForm); - mClientState.startStopState = *clientState.startStopState; - } + currentClientState.startStopState = *clientState.startStopState; + }); mustUpdateDiscovery = true; } @@ -485,20 +486,19 @@ private: void handleRtClientState(IncomingClientState clientState) { - { - std::lock_guard<std::mutex> lock(mClientStateGuard); + mClientState.update([&](ClientState& currentClientState) { if (clientState.timeline) { - mClientState.timeline = *clientState.timeline; + currentClientState.timeline = *clientState.timeline; } if (clientState.startStopState) { // Prevent updating client start stop state with an outdated start stop state *clientState.startStopState = detail::selectPreferredStartStopState( - mClientState.startStopState, *clientState.startStopState); - mClientState.startStopState = *clientState.startStopState; + currentClientState.startStopState, *clientState.startStopState); + currentClientState.startStopState = *clientState.startStopState; } - } + }); handleClientState(clientState); mHasPendingRtClientStates = false; @@ -575,14 +575,23 @@ private: { } - bool tryPush(const IncomingClientState clientState) + void push(const IncomingClientState clientState) { - const auto success = mClientStateFifo.push(clientState); - if (success) + if (clientState.timeline) + { + mTimelineBuffer.write( + std::make_pair(clientState.timelineTimestamp, *clientState.timeline)); + } + + if (clientState.startStopState) + { + mStartStopStateBuffer.write(*clientState.startStopState); + } + + if (clientState.timeline || clientState.startStopState) { mCallbackDispatcher.invoke(); } - return success; } void processPendingClientStates() @@ -595,27 +604,23 @@ private: IncomingClientState buildMergedPendingClientState() { auto clientState = IncomingClientState{}; - while (const auto result = mClientStateFifo.pop()) + if (auto tl = mTimelineBuffer.readNew()) { - if (result->timeline) - { - clientState.timeline = result->timeline; - clientState.timelineTimestamp = result->timelineTimestamp; - } - if (result->startStopState) - { - clientState.startStopState = result->startStopState; - } + clientState.timelineTimestamp = (*tl).first; + clientState.timeline = OptionalTimeline{(*tl).second}; + } + if (auto sss = mStartStopStateBuffer.readNew()) + { + clientState.startStopState = sss; } return clientState; } Controller& mController; - // Assuming a wake up time of one ms for the threads owned by the CallbackDispatcher - // and the ioService, buffering 16 client states allows to set eight client states - // per ms. - static const std::size_t kBufferSize = 16; - CircularFifo<IncomingClientState, kBufferSize> mClientStateFifo; + // Use separate TripleBuffers for the Timeline and the StartStopState so we read the + // latest set value from either optional. + TripleBuffer<std::pair<std::chrono::microseconds, Timeline>> mTimelineBuffer; + TripleBuffer<ClientStartStopState> mStartStopStateBuffer; CallbackDispatcher mCallbackDispatcher; }; @@ -649,7 +654,7 @@ private: { // When the count goes down to zero, completely reset the // state, effectively founding a new session - mController.resetState(); + mController.mIo->async([this] { mController.resetState(); }); } mCallback(count); } @@ -667,7 +672,7 @@ private: { using It = typename Discovery::ServicePeerGateways::GatewayMap::iterator; using ValueType = typename Discovery::ServicePeerGateways::GatewayMap::value_type; - mController.mDiscovery.withGatewaysAsync([peer, handler](It begin, const It end) { + mController.mDiscovery.withGateways([peer, handler](It begin, const It end) { const auto addr = peer.second; const auto it = std::find_if( begin, end, [&addr](const ValueType& vt) { return vt.first == addr; }); @@ -733,12 +738,14 @@ private: { using Exception = discovery::UdpSendException; - void operator()(const Exception& exception) + void operator()(const Exception exception) { - mController.mDiscovery.repairGateway(exception.interfaceAddr); + mpController->mIo->async([this, exception] { + mpController->mDiscovery.repairGateway(exception.interfaceAddr); + }); } - Controller& mController; + Controller* mpController; }; TempoCallback mTempoCallback; @@ -750,8 +757,7 @@ private: mutable std::mutex mSessionStateGuard; SessionState mSessionState; - mutable std::mutex mClientStateGuard; - ClientState mClientState; + ControllerClientState mClientState; bool mLastIsPlayingForStartStopStateCallback; mutable RtClientState mRtClientState; @@ -776,8 +782,9 @@ private: Clock>; ControllerSessions mSessions; - using Discovery = - discovery::Service<std::pair<NodeState, GhostXForm>, GatewayFactory, IoContext>; + using Discovery = discovery::Service<std::pair<NodeState, GhostXForm>, + GatewayFactory, + typename util::Injected<IoContext>::type&>; Discovery mDiscovery; }; diff --git a/include/ableton/link/Gateway.hpp b/include/ableton/link/Gateway.hpp index 086abfa..16d6abd 100644 --- a/include/ableton/link/Gateway.hpp +++ b/include/ableton/link/Gateway.hpp @@ -43,7 +43,7 @@ public: nodeState.sessionId, std::move(ghostXForm), std::move(clock), - util::injectVal(mIo->clone())) + util::injectRef(*mIo)) , mPeerGateway(discovery::makeIpV4Gateway(util::injectRef(*mIo), std::move(addr), std::move(observer), @@ -83,7 +83,7 @@ public: private: util::Injected<IoContext> mIo; - MeasurementService<Clock, typename std::remove_reference<IoContext>::type> mMeasurement; + MeasurementService<Clock, typename util::Injected<IoContext>::type&> mMeasurement; discovery:: IpV4Gateway<PeerObserver, PeerState, typename util::Injected<IoContext>::type&> mPeerGateway; diff --git a/include/ableton/link/HostTimeFilter.hpp b/include/ableton/link/HostTimeFilter.hpp index 2fdbc94..bbe98e0 100644 --- a/include/ableton/link/HostTimeFilter.hpp +++ b/include/ableton/link/HostTimeFilter.hpp @@ -21,6 +21,8 @@ #include <ableton/link/LinearRegression.hpp> #include <chrono> +#include <cmath> +#include <utility> #include <vector> namespace ableton diff --git a/include/ableton/link/Kalman.hpp b/include/ableton/link/Kalman.hpp deleted file mode 100644 index 06c6387..0000000 --- a/include/ableton/link/Kalman.hpp +++ /dev/null @@ -1,143 +0,0 @@ -/* Copyright 2016, Ableton AG, Berlin. All rights reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * If you would like to incorporate Link into a proprietary software application, - * please contact <link-devs@ableton.com>. - */ - -#pragma once - -#include <array> -#include <cfloat> -#include <cmath> - - -namespace ableton -{ -namespace link -{ - -template <std::size_t n> -struct Kalman -{ - Kalman() - : mValue(0) - , mCoVariance(1) - , mVarianceLength(n) - , mCounter(mVarianceLength) - { - } - - double getValue() - { - return mValue; - } - - double calculateVVariance() - { - auto vVar = 0.; - auto meanOfDiffs = 0.; - - for (size_t k = 0; k < (mVarianceLength); k++) - { - meanOfDiffs += (mMeasuredValues[k] - mFilterValues[k]); - } - - meanOfDiffs /= static_cast<double>(mVarianceLength); - - for (size_t i = 0; i < (mVarianceLength); i++) - { - vVar += (pow(mMeasuredValues[i] - mFilterValues[i] - meanOfDiffs, 2.0)); - } - - vVar /= static_cast<double>(mVarianceLength - 1); - - return vVar; - } - - double calculateWVariance() - { - auto wVar = 0.; - auto meanOfDiffs = 0.; - - for (size_t k = 0; k < (mVarianceLength); k++) - { - meanOfDiffs += (mFilterValues[(mCounter - k - 1) % mVarianceLength] - - mFilterValues[(mCounter - k - 2) % mVarianceLength]); - } - - meanOfDiffs /= static_cast<double>(mVarianceLength); - - for (size_t i = 0; i < (mVarianceLength); i++) - { - wVar += (pow(mFilterValues[(mCounter - i - 1) % mVarianceLength] - - mFilterValues[(mCounter - i - 2) % mVarianceLength] - meanOfDiffs, - 2.0)); - } - - wVar /= static_cast<double>(mVarianceLength - 1); - - return wVar; - } - - void iterate(const double value) - { - const std::size_t currentIndex = mCounter % mVarianceLength; - mMeasuredValues[currentIndex] = value; - - if (mCounter < (mVarianceLength + mVarianceLength)) - { - if (mCounter == mVarianceLength) - { - mValue = value; - } - else - { - mValue = (mValue + value) / 2; - } - } - else - { - // prediction equations - const double prevFilterValue = mFilterValues[(mCounter - 1) % mVarianceLength]; - mFilterValues[currentIndex] = prevFilterValue; - const auto wVariance = calculateWVariance(); - const double coVarianceEstimation = mCoVariance + wVariance; - - // update equations - const auto vVariance = calculateVVariance(); - // Gain defines how easily the filter will adjust to a new condition - // With gain = 1 the output equals the input, with gain = 0 the input - // is ignored and the output equals the last filtered value - const auto divisor = coVarianceEstimation + vVariance; - const auto gain = divisor != 0. ? coVarianceEstimation / divisor : 0.7; - mValue = prevFilterValue + gain * (value - prevFilterValue); - mCoVariance = (1 - gain) * coVarianceEstimation; - } - mFilterValues[currentIndex] = mValue; - - ++mCounter; - } - - double mValue; - double mCoVariance; - size_t mVarianceLength; - size_t mCounter; - std::array<double, n> mFilterValues; - std::array<double, n> mMeasuredValues; -}; - -} // namespace link -} // namespace ableton diff --git a/include/ableton/link/LinearRegression.hpp b/include/ableton/link/LinearRegression.hpp index 9c7cbc7..e0b35cf 100644 --- a/include/ableton/link/LinearRegression.hpp +++ b/include/ableton/link/LinearRegression.hpp @@ -19,9 +19,7 @@ #pragma once -#include <cfloat> -#include <cmath> -#include <numeric> +#include <cassert> #include <utility> namespace ableton @@ -32,32 +30,26 @@ namespace link template <typename It> std::pair<double, double> linearRegression(It begin, It end) { - using namespace std; - using Point = pair<double, double>; + double sumX = 0.0; + double sumXX = 0.0; + double sumXY = 0.0; + double sumY = 0.0; + for (auto i = begin; i != end; ++i) + { + sumX += i->first; + sumXX += i->first * i->first; + sumXY += i->first * i->second; + sumY += i->second; + } const double numPoints = static_cast<double>(distance(begin, end)); + assert(numPoints > 0); + const double denominator = numPoints * sumXX - sumX * sumX; + const double slope = + denominator == 0.0 ? 0.0 : (numPoints * sumXY - sumX * sumY) / denominator; + const double intercept = (sumY - slope * sumX) / numPoints; - const double meanX = accumulate(begin, end, 0.0, [](double a, Point b) { - return a + b.first; - }) / numPoints; - - const double productXX = accumulate(begin, end, 0.0, - [&meanX](double a, Point b) { return a + pow(b.first - meanX, 2.0); }); - - const double meanY = accumulate(begin, end, 0.0, [](double a, Point b) { - return a + b.second; - }) / numPoints; - - const double productXY = - inner_product(begin, end, begin, 0.0, [](double a, double b) { return a + b; }, - [&meanX, &meanY]( - Point a, Point b) { return ((a.first - meanX) * (b.second - meanY)); }); - - const double slope = productXX == 0.0 ? 0.0 : productXY / productXX; - - const double intercept = meanY - (slope * meanX); - - return make_pair(slope, intercept); + return std::make_pair(slope, intercept); } } // namespace link diff --git a/include/ableton/link/Measurement.hpp b/include/ableton/link/Measurement.hpp index c8d0f2b..299ca24 100644 --- a/include/ableton/link/Measurement.hpp +++ b/include/ableton/link/Measurement.hpp @@ -37,8 +37,7 @@ namespace link template <typename Clock, typename IoContext> struct Measurement { - using Point = std::pair<double, double>; - using Callback = std::function<void(std::vector<Point>)>; + using Callback = std::function<void(std::vector<double>&)>; using Micros = std::chrono::microseconds; static const std::size_t kNumberDataPoints = 100; @@ -48,13 +47,10 @@ struct Measurement Callback callback, asio::ip::address_v4 address, Clock clock, - IoContext io) + util::Injected<IoContext> io) : mIo(std::move(io)) - , mpImpl(std::make_shared<Impl>(std::move(state), - std::move(callback), - std::move(address), - std::move(clock), - mIo)) + , mpImpl(std::make_shared<Impl>( + std::move(state), std::move(callback), std::move(address), std::move(clock), mIo)) { mpImpl->listen(); } @@ -66,23 +62,24 @@ struct Measurement struct Impl : std::enable_shared_from_this<Impl> { - using Socket = typename IoContext::template Socket<v1::kMaxMessageSize>; - using Timer = typename IoContext::Timer; - using Log = typename IoContext::Log; + using Socket = + typename util::Injected<IoContext>::type::template Socket<v1::kMaxMessageSize>; + using Timer = typename util::Injected<IoContext>::type::Timer; + using Log = typename util::Injected<IoContext>::type::Log; Impl(const PeerState& state, Callback callback, asio::ip::address_v4 address, Clock clock, - IoContext& io) - : mSocket(io.template openUnicastSocket<v1::kMaxMessageSize>(address)) + util::Injected<IoContext> io) + : mSocket(io->template openUnicastSocket<v1::kMaxMessageSize>(address)) , mSessionId(state.nodeState.sessionId) , mEndpoint(state.endpoint) , mCallback(std::move(callback)) , mClock(std::move(clock)) - , mTimer(io.makeTimer()) + , mTimer(io->makeTimer()) , mMeasurementsStarted(0) - , mLog(channel(io.log(), "Measurement on gateway@" + address.to_string())) + , mLog(channel(io->log(), "Measurement on gateway@" + address.to_string())) , mSuccess(false) { const auto ht = HostTime{mClock.micros()}; @@ -163,13 +160,19 @@ struct Measurement sendPing(from, payload); listen(); - if (prevGHostTime != Micros{0}) + + if (ghostTime != Micros{0} && prevHostTime != Micros{0}) { mData.push_back( - std::make_pair(static_cast<double>((hostTime + prevHostTime).count()) * 0.5, - static_cast<double>(ghostTime.count()))); - mData.push_back(std::make_pair(static_cast<double>(prevHostTime.count()), - static_cast<double>((ghostTime + prevGHostTime).count()) * 0.5)); + static_cast<double>(ghostTime.count()) + - (static_cast<double>((hostTime + prevHostTime).count()) * 0.5)); + + if (prevGHostTime != Micros{0}) + { + mData.push_back( + (static_cast<double>((ghostTime + prevGHostTime).count()) * 0.5) + - static_cast<double>(prevHostTime.count())); + } } if (mData.size() > kNumberDataPoints) @@ -215,23 +218,22 @@ struct Measurement void finish() { mTimer.cancel(); - mCallback(std::move(mData)); - mData = {}; mSuccess = true; debug(mLog) << "Measuring " << mEndpoint << " done."; + mCallback(mData); } void fail() { - mCallback(std::vector<Point>{}); - mData = {}; + mData.clear(); debug(mLog) << "Measuring " << mEndpoint << " failed."; + mCallback(mData); } Socket mSocket; SessionId mSessionId; asio::ip::udp::endpoint mEndpoint; - std::vector<std::pair<double, double>> mData; + std::vector<double> mData; Callback mCallback; Clock mClock; Timer mTimer; @@ -240,28 +242,7 @@ struct Measurement bool mSuccess; }; - struct ImplDeleter - { - ImplDeleter(Measurement& measurement) - : mpImpl(std::move(measurement.mpImpl)) - { - } - - void operator()() - { - // Notify callback that the measurement has failed if it did - // not succeed before destruction - if (!mpImpl->mSuccess) - { - mpImpl->fail(); - } - mpImpl.reset(); - } - - std::shared_ptr<Impl> mpImpl; - }; - - IoContext mIo; + util::Injected<IoContext> mIo; std::shared_ptr<Impl> mpImpl; }; diff --git a/include/ableton/link/MeasurementEndpointV4.hpp b/include/ableton/link/MeasurementEndpointV4.hpp index a7551b3..958aad9 100644 --- a/include/ableton/link/MeasurementEndpointV4.hpp +++ b/include/ableton/link/MeasurementEndpointV4.hpp @@ -56,8 +56,9 @@ struct MeasurementEndpointV4 discovery::Deserialize<std::uint32_t>::fromNetworkByteStream(std::move(begin), end); auto portRes = discovery::Deserialize<std::uint16_t>::fromNetworkByteStream( std::move(addrRes.second), end); - return make_pair(MeasurementEndpointV4{{asio::ip::address_v4{std::move(addrRes.first)}, - std::move(portRes.first)}}, + return make_pair( + MeasurementEndpointV4{ + {asio::ip::address_v4{std::move(addrRes.first)}, std::move(portRes.first)}}, std::move(portRes.second)); } diff --git a/include/ableton/link/MeasurementService.hpp b/include/ableton/link/MeasurementService.hpp index f590715..22bf46d 100644 --- a/include/ableton/link/MeasurementService.hpp +++ b/include/ableton/link/MeasurementService.hpp @@ -20,9 +20,9 @@ #pragma once #include <ableton/link/GhostXForm.hpp> -#include <ableton/link/Kalman.hpp> #include <ableton/link/LinearRegression.hpp> #include <ableton/link/Measurement.hpp> +#include <ableton/link/Median.hpp> #include <ableton/link/PeerState.hpp> #include <ableton/link/PingResponder.hpp> #include <ableton/link/SessionId.hpp> @@ -40,7 +40,6 @@ class MeasurementService { public: using IoType = util::Injected<IoContext>; - using Point = std::pair<double, double>; using MeasurementInstance = Measurement<Clock, IoContext>; MeasurementService(asio::ip::address_v4 address, @@ -61,14 +60,6 @@ public: MeasurementService(const MeasurementService&) = delete; MeasurementService(MeasurementService&&) = delete; - ~MeasurementService() - { - // Clear the measurement map in the IoContext so that whatever - // cleanup code executes in response to the destruction of the - // measurement objects still have access to the IoContext. - mIo->async([this] { mMeasurementMap.clear(); }); - } - void updateNodeState(const SessionId& sessionId, const GhostXForm& xform) { mPingResponder.updateNodeState(sessionId, xform); @@ -85,47 +76,29 @@ public: { using namespace std; - mIo->async([this, state, handler] { - const auto nodeId = state.nodeState.nodeId; - auto addr = mPingResponder.endpoint().address().to_v4(); - auto callback = CompletionCallback<Handler>{*this, nodeId, handler}; - - try - { - - mMeasurementMap[nodeId] = - std::unique_ptr<MeasurementInstance>(new MeasurementInstance{ - state, std::move(callback), std::move(addr), mClock, mIo->clone()}); - } - catch (const runtime_error& err) - { - info(mIo->log()) << "gateway@" + addr.to_string() - << " Failed to measure. Reason: " << err.what(); - handler(GhostXForm{}); - } - }); - } - - static GhostXForm filter( - std::vector<Point>::const_iterator begin, std::vector<Point>::const_iterator end) - { - using namespace std; - using std::chrono::microseconds; + const auto nodeId = state.nodeState.nodeId; + auto addr = mPingResponder.endpoint().address().to_v4(); + auto callback = CompletionCallback<Handler>{*this, nodeId, handler}; - Kalman<5> kalman; - for (auto it = begin; it != end; ++it) + try { - kalman.iterate(it->second - it->first); + mMeasurementMap[nodeId] = + std::unique_ptr<MeasurementInstance>(new MeasurementInstance{ + state, std::move(callback), std::move(addr), mClock, mIo}); + } + catch (const runtime_error& err) + { + info(mIo->log()) << "gateway@" + addr.to_string() + << " Failed to measure. Reason: " << err.what(); + handler(GhostXForm{}); } - - return GhostXForm{1, microseconds(llround(kalman.getValue()))}; } private: template <typename Handler> struct CompletionCallback { - void operator()(const std::vector<Point> data) + void operator()(std::vector<double>& data) { using namespace std; using std::chrono::microseconds; @@ -137,21 +110,19 @@ private: auto nodeId = mNodeId; auto handler = mHandler; auto& measurementMap = mMeasurementService.mMeasurementMap; - mMeasurementService.mIo->async([nodeId, handler, &measurementMap, data] { - const auto it = measurementMap.find(nodeId); - if (it != measurementMap.end()) + const auto it = measurementMap.find(nodeId); + if (it != measurementMap.end()) + { + if (data.empty()) { - if (data.empty()) - { - handler(GhostXForm{}); - } - else - { - handler(MeasurementService::filter(begin(data), end(data))); - } - measurementMap.erase(it); + handler(GhostXForm{}); } - }); + else + { + handler(GhostXForm{1, microseconds(llround(median(data.begin(), data.end())))}); + } + measurementMap.erase(it); + } } MeasurementService& mMeasurementService; diff --git a/src/ableton/link/tst_Kalman.cpp b/include/ableton/link/Median.hpp index 6f2b62e..ddc7faf 100644 --- a/src/ableton/link/tst_Kalman.cpp +++ b/include/ableton/link/Median.hpp @@ -1,4 +1,4 @@ -/* Copyright 2016, Ableton AG, Berlin. All rights reserved. +/* Copyright 2021, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,9 +17,10 @@ * please contact <link-devs@ableton.com>. */ -#include <ableton/link/Kalman.hpp> -#include <ableton/test/CatchWrapper.hpp> -#include <array> +#pragma once + +#include <algorithm> +#include <cassert> #include <vector> namespace ableton @@ -27,42 +28,23 @@ namespace ableton namespace link { -TEST_CASE("Kalman | Check1", "[Kalman]") +template <typename It> +double median(It begin, It end) { - int peerTimeDiff = 0; - Kalman<16> filter; - - for (int i = 0; i < 5; ++i) + const auto n = std::distance(begin, end); + assert(n > 2); + if (n % 2 == 0) { - filter.iterate(peerTimeDiff); + std::nth_element(begin, begin + n / 2, end); + std::nth_element(begin, begin + (n - 1) / 2, end); + return (*(begin + (n / 2)) + *(begin + (n - 1) / 2)) / 2.0; } - - CHECK(peerTimeDiff == Approx(filter.getValue())); - - filter.iterate(100); - - CHECK(peerTimeDiff != Approx(filter.getValue())); -} - -TEST_CASE("Kalman | Check2", "[Kalman]") -{ - double peerTimeDiff = 3e11; - Kalman<5> filter; - - for (int i = 0; i < 15; ++i) - { - filter.iterate(peerTimeDiff); - } - - CHECK(peerTimeDiff == Approx(filter.getValue())); - - for (int i = 0; i < 15; ++i) + else { - filter.iterate(11); + std::nth_element(begin, begin + n / 2, end); + return *(begin + (n / 2)); } - - CHECK(peerTimeDiff != Approx(filter.getValue())); -} +}; } // namespace link } // namespace ableton diff --git a/include/ableton/link/NodeId.hpp b/include/ableton/link/NodeId.hpp index 5076320..ef4f798 100644 --- a/include/ableton/link/NodeId.hpp +++ b/include/ableton/link/NodeId.hpp @@ -68,8 +68,8 @@ struct NodeId : NodeIdArray static std::pair<NodeId, It> fromNetworkByteStream(It begin, It end) { using namespace std; - auto result = - discovery::Deserialize<NodeIdArray>::fromNetworkByteStream(std::move(begin), std::move(end)); + auto result = discovery::Deserialize<NodeIdArray>::fromNetworkByteStream( + std::move(begin), std::move(end)); return make_pair(NodeId(std::move(result.first)), std::move(result.second)); } }; diff --git a/include/ableton/link/NodeState.hpp b/include/ableton/link/NodeState.hpp index c7d8178..787fbda 100644 --- a/include/ableton/link/NodeState.hpp +++ b/include/ableton/link/NodeState.hpp @@ -62,7 +62,8 @@ struct NodeState [&nodeState](SessionMembership membership) { nodeState.sessionId = std::move(membership.sessionId); }, - [&nodeState](StartStopState ststst) { nodeState.startStopState = std::move(ststst); }); + [&nodeState]( + StartStopState ststst) { nodeState.startStopState = std::move(ststst); }); return nodeState; } diff --git a/include/ableton/link/PeerState.hpp b/include/ableton/link/PeerState.hpp index 3bbfbc0..291c278 100644 --- a/include/ableton/link/PeerState.hpp +++ b/include/ableton/link/PeerState.hpp @@ -75,8 +75,10 @@ struct PeerState using namespace std; auto peerState = PeerState{NodeState::fromPayload(std::move(id), begin, end), {}}; - discovery::parsePayload<MeasurementEndpointV4>(std::move(begin), std::move(end), - [&peerState](MeasurementEndpointV4 me4) { peerState.endpoint = std::move(me4.ep); }); + discovery::parsePayload<MeasurementEndpointV4>( + std::move(begin), std::move(end), [&peerState](MeasurementEndpointV4 me4) { + peerState.endpoint = std::move(me4.ep); + }); return peerState; } diff --git a/include/ableton/link/Peers.hpp b/include/ableton/link/Peers.hpp index ce674c9..b633ec3 100644 --- a/include/ableton/link/Peers.hpp +++ b/include/ableton/link/Peers.hpp @@ -137,8 +137,7 @@ public: // Check to handle the moved from case if (mpImpl) { - auto& io = *mpImpl->mIo; - io.async(Deleter{*this}); + mpImpl->gatewayClosed(mAddr); } } @@ -148,44 +147,23 @@ public: auto pImpl = observer.mpImpl; auto addr = observer.mAddr; assert(pImpl); - pImpl->mIo->async([pImpl, addr, state] { - pImpl->sawPeerOnGateway(std::move(state), std::move(addr)); - }); + pImpl->sawPeerOnGateway(std::move(state), std::move(addr)); } friend void peerLeft(GatewayObserver& observer, const NodeId& id) { auto pImpl = observer.mpImpl; auto addr = observer.mAddr; - pImpl->mIo->async( - [pImpl, addr, id] { pImpl->peerLeftGateway(std::move(id), std::move(addr)); }); + pImpl->peerLeftGateway(std::move(id), std::move(addr)); } friend void peerTimedOut(GatewayObserver& observer, const NodeId& id) { auto pImpl = observer.mpImpl; auto addr = observer.mAddr; - pImpl->mIo->async( - [pImpl, addr, id] { pImpl->peerLeftGateway(std::move(id), std::move(addr)); }); + pImpl->peerLeftGateway(std::move(id), std::move(addr)); } - struct Deleter - { - Deleter(GatewayObserver& observer) - : mpImpl(std::move(observer.mpImpl)) - , mAddr(std::move(observer.mAddr)) - { - } - - void operator()() - { - mpImpl->gatewayClosed(mAddr); - } - - std::shared_ptr<Impl> mpImpl; - asio::ip::address mAddr; - }; - std::shared_ptr<Impl> mpImpl; asio::ip::address mAddr; }; @@ -217,47 +195,44 @@ private: const auto peerSession = peerState.sessionId(); const auto peerTimeline = peerState.timeline(); const auto peerStartStopState = peerState.startStopState(); - bool isNewSessionTimeline = false; - bool isNewSessionStartStopState = false; + + bool isNewSessionTimeline = !sessionTimelineExists(peerSession, peerTimeline); + bool isNewSessionStartStopState = + !sessionStartStopStateExists(peerSession, peerStartStopState); + + auto peer = make_pair(std::move(peerState), std::move(gatewayAddr)); + const auto idRange = equal_range(begin(mPeers), end(mPeers), peer, PeerIdComp{}); + bool didSessionMembershipChange = false; + if (idRange.first == idRange.second) + { + // This peer is not currently known on any gateway + didSessionMembershipChange = true; + mPeers.insert(std::move(idRange.first), std::move(peer)); + } + else { - isNewSessionTimeline = !sessionTimelineExists(peerSession, peerTimeline); - isNewSessionStartStopState = - !sessionStartStopStateExists(peerSession, peerStartStopState); + // We've seen this peer before... does it have a new session? + didSessionMembershipChange = + all_of(idRange.first, idRange.second, [&peerSession](const Peer& test) { + return test.first.sessionId() != peerSession; + }); - auto peer = make_pair(std::move(peerState), std::move(gatewayAddr)); - const auto idRange = equal_range(begin(mPeers), end(mPeers), peer, PeerIdComp{}); + // was it on this gateway? + const auto addrRange = + equal_range(idRange.first, idRange.second, peer, AddrComp{}); - if (idRange.first == idRange.second) + if (addrRange.first == addrRange.second) { - // This peer is not currently known on any gateway - didSessionMembershipChange = true; - mPeers.insert(std::move(idRange.first), std::move(peer)); + // First time on this gateway, add it + mPeers.insert(std::move(addrRange.first), std::move(peer)); } else { - // We've seen this peer before... does it have a new session? - didSessionMembershipChange = - all_of(idRange.first, idRange.second, [&peerSession](const Peer& test) { - return test.first.sessionId() != peerSession; - }); - - // was it on this gateway? - const auto addrRange = - equal_range(idRange.first, idRange.second, peer, AddrComp{}); - - if (addrRange.first == addrRange.second) - { - // First time on this gateway, add it - mPeers.insert(std::move(addrRange.first), std::move(peer)); - } - else - { - // We have an entry for this peer on this gateway, update it - *addrRange.first = std::move(peer); - } + // We have an entry for this peer on this gateway, update it + *addrRange.first = std::move(peer); } - } // end lock + } // Invoke callbacks outside the critical section if (isNewSessionTimeline) @@ -284,18 +259,16 @@ private: { using namespace std; + auto it = find_if(begin(mPeers), end(mPeers), [&](const Peer& peer) { + return peer.first.ident() == nodeId && peer.second == gatewayAddr; + }); + bool didSessionMembershipChange = false; + if (it != end(mPeers)) { - auto it = find_if(begin(mPeers), end(mPeers), [&](const Peer& peer) { - return peer.first.ident() == nodeId && peer.second == gatewayAddr; - }); - - if (it != end(mPeers)) - { - mPeers.erase(std::move(it)); - didSessionMembershipChange = true; - } - } // end lock + mPeers.erase(std::move(it)); + didSessionMembershipChange = true; + } if (didSessionMembershipChange) { @@ -307,12 +280,10 @@ private: { using namespace std; - { - mPeers.erase( - remove_if(begin(mPeers), end(mPeers), - [&gatewayAddr](const Peer& peer) { return peer.second == gatewayAddr; }), - end(mPeers)); - } // end lock + mPeers.erase( + remove_if(begin(mPeers), end(mPeers), + [&gatewayAddr](const Peer& peer) { return peer.second == gatewayAddr; }), + end(mPeers)); mSessionMembershipCallback(); } diff --git a/include/ableton/link/PingResponder.hpp b/include/ableton/link/PingResponder.hpp index e8a5e8b..a8f455f 100644 --- a/include/ableton/link/PingResponder.hpp +++ b/include/ableton/link/PingResponder.hpp @@ -58,21 +58,10 @@ public: PingResponder(const PingResponder&) = delete; PingResponder(PingResponder&&) = delete; - ~PingResponder() - { - // post the release of the impl object into the IoContext so that - // it happens in the same thread as its handlers - auto pImpl = mpImpl; - mIo->async([pImpl]() mutable { pImpl.reset(); }); - } - void updateNodeState(const SessionId& sessionId, const GhostXForm& xform) { - auto pImpl = mpImpl; - mIo->async([pImpl, sessionId, xform] { - pImpl->mSessionId = std::move(sessionId); - pImpl->mGhostXForm = std::move(xform); - }); + mpImpl->mSessionId = std::move(sessionId); + mpImpl->mGhostXForm = std::move(xform); } asio::ip::udp::endpoint endpoint() const diff --git a/include/ableton/link/SessionState.hpp b/include/ableton/link/SessionState.hpp index 67e2dd6..183c8a9 100644 --- a/include/ableton/link/SessionState.hpp +++ b/include/ableton/link/SessionState.hpp @@ -22,6 +22,7 @@ #include <ableton/link/Optional.hpp> #include <ableton/link/StartStopState.hpp> #include <ableton/link/Timeline.hpp> +#include <ableton/link/TripleBuffer.hpp> namespace ableton { @@ -56,6 +57,39 @@ struct ClientState ClientStartStopState startStopState; }; +struct ControllerClientState +{ + ControllerClientState(ClientState state) + : mState(state) + , mRtState(state) + { + } + + template <typename Fn> + void update(Fn fn) + { + std::unique_lock<std::mutex> lock(mMutex); + fn(mState); + mRtState.write(mState); + } + + ClientState get() const + { + std::unique_lock<std::mutex> lock(mMutex); + return mState; + } + + ClientState getRt() const + { + return mRtState.read(); + } + +private: + mutable std::mutex mMutex; + ClientState mState; + mutable TripleBuffer<ClientState> mRtState; +}; + struct RtClientState { Timeline timeline; diff --git a/include/ableton/link/Sessions.hpp b/include/ableton/link/Sessions.hpp index ee1a95e..14ea54a 100644 --- a/include/ableton/link/Sessions.hpp +++ b/include/ableton/link/Sessions.hpp @@ -248,15 +248,11 @@ private: const SessionId& sessionId = mSessionId; if (xform == GhostXForm{}) { - mSessions.mIo->async([&sessions, sessionId] { - sessions.handleFailedMeasurement(std::move(sessionId)); - }); + sessions.handleFailedMeasurement(std::move(sessionId)); } else { - mSessions.mIo->async([&sessions, sessionId, xform] { - sessions.handleSuccessfulMeasurement(std::move(sessionId), std::move(xform)); - }); + sessions.handleSuccessfulMeasurement(std::move(sessionId), std::move(xform)); } } @@ -297,7 +293,8 @@ Sessions<Peers, MeasurePeer, JoinSessionCallback, IoContext, Clock> makeSessions Clock clock) { using namespace std; - return {std::move(init), std::move(peers), std::move(measure), std::move(join), std::move(io), std::move(clock)}; + return {std::move(init), std::move(peers), std::move(measure), std::move(join), + std::move(io), std::move(clock)}; } } // namespace link diff --git a/include/ableton/link/StartStopState.hpp b/include/ableton/link/StartStopState.hpp index 38fba5a..1caad99 100644 --- a/include/ableton/link/StartStopState.hpp +++ b/include/ableton/link/StartStopState.hpp @@ -78,8 +78,8 @@ struct StartStopState { using namespace std; using namespace discovery; - auto result = - Deserialize<StartStopStateTuple>::fromNetworkByteStream(std::move(begin), std::move(end)); + auto result = Deserialize<StartStopStateTuple>::fromNetworkByteStream( + std::move(begin), std::move(end)); auto state = StartStopState{get<0>(result.first), get<1>(result.first), get<2>(result.first)}; return make_pair(std::move(state), std::move(result.second)); diff --git a/include/ableton/link/Timeline.hpp b/include/ableton/link/Timeline.hpp index 941f943..9fa3a45 100644 --- a/include/ableton/link/Timeline.hpp +++ b/include/ableton/link/Timeline.hpp @@ -84,7 +84,8 @@ struct Timeline auto result = Deserialize<tuple<Tempo, Beats, chrono::microseconds>>::fromNetworkByteStream( std::move(begin), std::move(end)); - tie(timeline.tempo, timeline.beatOrigin, timeline.timeOrigin) = std::move(result.first); + tie(timeline.tempo, timeline.beatOrigin, timeline.timeOrigin) = + std::move(result.first); return make_pair(std::move(timeline), std::move(result.second)); } diff --git a/include/ableton/link/TripleBuffer.hpp b/include/ableton/link/TripleBuffer.hpp new file mode 100644 index 0000000..10a83c1 --- /dev/null +++ b/include/ableton/link/TripleBuffer.hpp @@ -0,0 +1,121 @@ +/* Copyright 2022, Ableton AG, Berlin. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * If you would like to incorporate Link into a proprietary software application, + * please contact <link-devs@ableton.com>. + */ + + +#pragma once + +#include <ableton/link/Optional.hpp> + +#include <array> +#include <atomic> +#include <cassert> +#include <cstdint> + +namespace ableton +{ +namespace link +{ + +template <typename T> +struct TripleBuffer +{ +public: + TripleBuffer() + : mBuffers{{{}, {}, {}}} + { + assert(mState.is_lock_free()); + } + + explicit TripleBuffer(const T& initial) + : mBuffers{{initial, initial, initial}} + { + assert(mState.is_lock_free()); + } + + TripleBuffer(const TripleBuffer&) = delete; + TripleBuffer& operator=(const TripleBuffer&) = delete; + + T read() noexcept + { + loadReadBuffer(); + return mBuffers[mReadIndex]; + } + + Optional<T> readNew() + { + if (loadReadBuffer()) + { + return Optional<T>(mBuffers[mReadIndex]); + } + return {}; + } + + template <typename U> + void write(U&& value) + { + mBuffers[mWriteIndex] = std::forward<U>(value); + + const auto prevState = + mState.exchange(makeState(mWriteIndex, true), std::memory_order_acq_rel); + + mWriteIndex = backIndex(prevState); + } + +private: + bool loadReadBuffer() + { + auto state = mState.load(std::memory_order_acquire); + auto isNew = isNewWrite(state); + if (isNew) + { + const auto prevState = + mState.exchange(makeState(mReadIndex, false), std::memory_order_acq_rel); + + mReadIndex = backIndex(prevState); + } + return isNew; + } + + using BackingState = uint32_t; + + static constexpr bool isNewWrite(const BackingState state) + { + return (state & 0x0000FFFFu) != 0; + } + + static constexpr uint32_t backIndex(const BackingState state) + { + return state >> 16; + } + + static constexpr BackingState makeState( + const uint32_t backBufferIndex, const bool isWrite) + { + return (backBufferIndex << 16) | uint32_t(isWrite); + } + + std::atomic<BackingState> mState{makeState(1u, false)}; // Reader and writer + uint32_t mReadIndex = 0u; // Reader only + uint32_t mWriteIndex = 2u; // Writer only + + std::array<T, 3> mBuffers{}; +}; + +} // namespace link +} // namespace ableton diff --git a/include/ableton/link/v1/Messages.hpp b/include/ableton/link/v1/Messages.hpp index 3844b40..a0e0ee3 100644 --- a/include/ableton/link/v1/Messages.hpp +++ b/include/ableton/link/v1/Messages.hpp @@ -87,8 +87,8 @@ It encodeMessage(const MessageType messageType, const Payload& payload, It out) if (messageSize < kMaxMessageSize) { return toNetworkByteStream( - payload, toNetworkByteStream( - header, copy(begin(kProtocolHeader), end(kProtocolHeader), std::move(out)))); + payload, toNetworkByteStream(header, + copy(begin(kProtocolHeader), end(kProtocolHeader), std::move(out)))); } else { diff --git a/include/ableton/platforms/Config.hpp b/include/ableton/platforms/Config.hpp index 63aea27..529ae9f 100644 --- a/include/ableton/platforms/Config.hpp +++ b/include/ableton/platforms/Config.hpp @@ -27,9 +27,13 @@ #include <ableton/platforms/stl/Random.hpp> #include <ableton/platforms/windows/Clock.hpp> #include <ableton/platforms/windows/ScanIpIfAddrs.hpp> +#if _MSC_VER >= 1920 +#include <ableton/platforms/windows/ThreadFactory.hpp> +#endif #elif defined(LINK_PLATFORM_MACOSX) #include <ableton/platforms/asio/Context.hpp> #include <ableton/platforms/darwin/Clock.hpp> +#include <ableton/platforms/darwin/ThreadFactory.hpp> #include <ableton/platforms/posix/ScanIpIfAddrs.hpp> #include <ableton/platforms/stl/Random.hpp> #elif defined(LINK_PLATFORM_LINUX) @@ -37,6 +41,9 @@ #include <ableton/platforms/linux/Clock.hpp> #include <ableton/platforms/posix/ScanIpIfAddrs.hpp> #include <ableton/platforms/stl/Random.hpp> +#ifdef __linux__ +#include <ableton/platforms/linux/ThreadFactory.hpp> +#endif #elif defined(ESP_PLATFORM) #include <ableton/platforms/esp32/Clock.hpp> #include <ableton/platforms/esp32/Context.hpp> @@ -53,21 +60,34 @@ namespace platform #if defined(LINK_PLATFORM_WINDOWS) using Clock = platforms::windows::Clock; +using Random = platforms::stl::Random; +#if _MSC_VER >= 1920 +using IoContext = platforms::asio::Context<platforms::windows::ScanIpIfAddrs, + util::NullLog, + platforms::windows::ThreadFactory>; +#else using IoContext = platforms::asio::Context<platforms::windows::ScanIpIfAddrs, util::NullLog>; -using Random = platforms::stl::Random; +#endif #elif defined(LINK_PLATFORM_MACOSX) using Clock = platforms::darwin::Clock; -using IoContext = - platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>; +using IoContext = platforms::asio::Context<platforms::posix::ScanIpIfAddrs, + util::NullLog, + platforms::darwin::ThreadFactory>; using Random = platforms::stl::Random; #elif defined(LINK_PLATFORM_LINUX) using Clock = platforms::linux::ClockMonotonicRaw; +using Random = platforms::stl::Random; +#ifdef __linux__ +using IoContext = platforms::asio::Context<platforms::posix::ScanIpIfAddrs, + util::NullLog, + platforms::linux::ThreadFactory>; +#else using IoContext = platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>; -using Random = platforms::stl::Random; +#endif #elif defined(ESP_PLATFORM) using Clock = platforms::esp32::Clock; diff --git a/include/ableton/platforms/asio/AsioWrapper.hpp b/include/ableton/platforms/asio/AsioWrapper.hpp index 6477e45..1f3dc4d 100644 --- a/include/ableton/platforms/asio/AsioWrapper.hpp +++ b/include/ableton/platforms/asio/AsioWrapper.hpp @@ -62,8 +62,10 @@ #define _SCL_SECURE_NO_WARNINGS 1 #pragma warning(push, 0) #pragma warning(disable : 4242) +#pragma warning(disable : 4668) #pragma warning(disable : 4702) #pragma warning(disable : 5204) +#pragma warning(disable : 5220) #endif #include <asio.hpp> diff --git a/include/ableton/platforms/asio/Context.hpp b/include/ableton/platforms/asio/Context.hpp index 54125fc..7c10a4b 100644 --- a/include/ableton/platforms/asio/Context.hpp +++ b/include/ableton/platforms/asio/Context.hpp @@ -25,6 +25,7 @@ #include <ableton/platforms/asio/LockFreeCallbackDispatcher.hpp> #include <ableton/platforms/asio/Socket.hpp> #include <thread> +#include <utility> namespace ableton { @@ -32,8 +33,21 @@ namespace platforms { namespace asio { +namespace +{ + +struct ThreadFactory +{ + template <typename Callable, typename... Args> + static std::thread makeThread(std::string, Callable&& f, Args&&... args) + { + return std::thread(std::forward<Callable>(f), std::forward<Args>(args)...); + } +}; + +} // namespace -template <typename ScanIpIfAddrs, typename LogT> +template <typename ScanIpIfAddrs, typename LogT, typename ThreadFactoryT = ThreadFactory> class Context { public: @@ -41,7 +55,8 @@ public: using Log = LogT; template <typename Handler, typename Duration> - using LockFreeCallbackDispatcher = LockFreeCallbackDispatcher<Handler, Duration>; + using LockFreeCallbackDispatcher = + LockFreeCallbackDispatcher<Handler, Duration, ThreadFactoryT>; template <std::size_t BufferSize> using Socket = asio::Socket<BufferSize>; @@ -56,22 +71,22 @@ public: : mpService(new ::asio::io_service()) , mpWork(new ::asio::io_service::work(*mpService)) { - mThread = - std::thread{[](::asio::io_service& service, ExceptionHandler handler) { - for (;;) - { - try - { - service.run(); - break; - } - catch (const typename ExceptionHandler::Exception& exception) - { - handler(exception); - } - } - }, - std::ref(*mpService), std::move(exceptHandler)}; + mThread = ThreadFactoryT::makeThread("Link Main", + [](::asio::io_service& service, ExceptionHandler handler) { + for (;;) + { + try + { + service.run(); + break; + } + catch (const typename ExceptionHandler::Exception& exception) + { + handler(exception); + } + } + }, + std::ref(*mpService), std::move(exceptHandler)); } Context(const Context&) = delete; @@ -154,17 +169,6 @@ public: mpService->post(std::move(handler)); } - Context clone() const - { - return {}; - } - - template <typename ExceptionHandler> - Context clone(ExceptionHandler handler) const - { - return Context{std::move(handler)}; - } - private: // Default handler is hidden and defines a hidden exception type // that will never be thrown by other code, so it effectively does diff --git a/include/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp b/include/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp index 4515eb6..189a6f7 100644 --- a/include/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp +++ b/include/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp @@ -38,7 +38,7 @@ namespace asio // after a timeout. This gives us a guaranteed minimum signaling rate which is defined // by the fallbackPeriod parameter. -template <typename Callback, typename Duration> +template <typename Callback, typename Duration, typename ThreadFactory> class LockFreeCallbackDispatcher { public: @@ -46,7 +46,7 @@ public: : mCallback(std::move(callback)) , mFallbackPeriod(std::move(fallbackPeriod)) , mRunning(true) - , mThread([this] { run(); }) + , mThread(ThreadFactory::makeThread("Link Dispatcher", [this] { run(); })) { } @@ -59,11 +59,7 @@ public: void invoke() { - if (mMutex.try_lock()) - { - mCondition.notify_one(); - mMutex.unlock(); - } + mCondition.notify_one(); } private: diff --git a/include/ableton/platforms/darwin/ThreadFactory.hpp b/include/ableton/platforms/darwin/ThreadFactory.hpp new file mode 100644 index 0000000..22c3162 --- /dev/null +++ b/include/ableton/platforms/darwin/ThreadFactory.hpp @@ -0,0 +1,48 @@ +/* Copyright 2021, Ableton AG, Berlin. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * If you would like to incorporate Link into a proprietary software application, + * please contact <link-devs@ableton.com>. + */ + +#pragma once + +#include <pthread.h> +#include <thread> +#include <utility> + +namespace ableton +{ +namespace platforms +{ +namespace darwin +{ + +struct ThreadFactory +{ + template <typename Callable, typename... Args> + static std::thread makeThread(std::string name, Callable&& f, Args&&... args) + { + return std::thread{[](std::string name, Callable&& f, Args&&... args) { + pthread_setname_np(name.c_str()); + f(args...); + }, + std::move(name), std::forward<Callable>(f), std::forward<Args>(args)...}; + } +}; + +} // namespace darwin +} // namespace platforms +} // namespace ableton diff --git a/include/ableton/platforms/esp32/Context.hpp b/include/ableton/platforms/esp32/Context.hpp index 772d38e..0c1cd00 100644 --- a/include/ableton/platforms/esp32/Context.hpp +++ b/include/ableton/platforms/esp32/Context.hpp @@ -24,6 +24,8 @@ #include <ableton/platforms/asio/AsioWrapper.hpp> #include <ableton/platforms/asio/Socket.hpp> #include <ableton/platforms/esp32/LockFreeCallbackDispatcher.hpp> +#include <driver/timer.h> +#include <freertos/FreeRTOS.h> #include <freertos/task.h> namespace ableton @@ -46,12 +48,26 @@ class Context { try { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); runner->mpService->poll_one(); } catch (...) { } - portYIELD(); + } + } + + static void IRAM_ATTR timerIsr(void* userParam) + { + static BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_1); + timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_1); + + vTaskNotifyGiveFromISR(userParam, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken) + { + portYIELD_FROM_ISR(); } } @@ -60,8 +76,23 @@ class Context : mpService(new ::asio::io_service()) , mpWork(new ::asio::io_service::work(*mpService)) { - xTaskCreatePinnedToCore( - run, "link", 8192, this, 2 | portPRIVILEGE_BIT, &mTaskHandle, LINK_ESP_TASK_CORE_ID); + xTaskCreatePinnedToCore(run, "link", 8192, this, 2 | portPRIVILEGE_BIT, + &mTaskHandle, LINK_ESP_TASK_CORE_ID); + + timer_config_t config = {.alarm_en = TIMER_ALARM_EN, + .counter_en = TIMER_PAUSE, + .intr_type = TIMER_INTR_LEVEL, + .counter_dir = TIMER_COUNT_UP, + .auto_reload = TIMER_AUTORELOAD_EN, + .divider = 80}; + + timer_init(TIMER_GROUP_0, TIMER_1, &config); + timer_set_counter_value(TIMER_GROUP_0, TIMER_1, 0); + timer_set_alarm_value(TIMER_GROUP_0, TIMER_1, 100); + timer_enable_intr(TIMER_GROUP_0, TIMER_1); + timer_isr_register(TIMER_GROUP_0, TIMER_1, &timerIsr, mTaskHandle, 0, nullptr); + + timer_start(TIMER_GROUP_0, TIMER_1); } ~ServiceRunner() @@ -167,17 +198,6 @@ public: serviceRunner().service().post(std::move(handler)); } - Context clone() const - { - return {}; - } - - template <typename ExceptionHandler> - Context clone(ExceptionHandler handler) const - { - return Context{std::move(handler)}; - } - private: // Default handler is hidden and defines a hidden exception type // that will never be thrown by other code, so it effectively does diff --git a/include/ableton/platforms/esp32/LockFreeCallbackDispatcher.hpp b/include/ableton/platforms/esp32/LockFreeCallbackDispatcher.hpp index b02ab73..1d7d120 100644 --- a/include/ableton/platforms/esp32/LockFreeCallbackDispatcher.hpp +++ b/include/ableton/platforms/esp32/LockFreeCallbackDispatcher.hpp @@ -77,7 +77,7 @@ private: dispatcher->mCondition.wait_for(lock, dispatcher->mFallbackPeriod); } dispatcher->mCallback(); - portYIELD(); + vTaskDelay(1); } } diff --git a/include/ableton/platforms/esp32/ScanIpIfAddrs.hpp b/include/ableton/platforms/esp32/ScanIpIfAddrs.hpp index 26406a1..527d080 100644 --- a/include/ableton/platforms/esp32/ScanIpIfAddrs.hpp +++ b/include/ableton/platforms/esp32/ScanIpIfAddrs.hpp @@ -19,8 +19,8 @@ #include <ableton/platforms/asio/AsioWrapper.hpp> #include <arpa/inet.h> +#include <esp_netif.h> #include <net/if.h> -#include <tcpip_adapter.h> #include <vector> namespace ableton @@ -36,11 +36,19 @@ struct ScanIpIfAddrs std::vector<::asio::ip::address> operator()() { std::vector<::asio::ip::address> addrs; - tcpip_adapter_ip_info_t ip_info; - if (ESP_OK == tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info) - && tcpip_adapter_is_netif_up(TCPIP_ADAPTER_IF_STA) && ip_info.ip.addr) + // Get first network interface + esp_netif_t* esp_netif = esp_netif_next(NULL); + while (esp_netif) { - addrs.emplace_back(::asio::ip::address_v4(ntohl(ip_info.ip.addr))); + // Check if interface is active + if (esp_netif_is_netif_up(esp_netif)) + { + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(esp_netif, &ip_info); + addrs.emplace_back(::asio::ip::address_v4(ntohl(ip_info.ip.addr))); + } + // Get next network interface + esp_netif = esp_netif_next(esp_netif); } return addrs; } diff --git a/include/ableton/platforms/linux/Clock.hpp b/include/ableton/platforms/linux/Clock.hpp index 0cc4d9a..accfe47 100644 --- a/include/ableton/platforms/linux/Clock.hpp +++ b/include/ableton/platforms/linux/Clock.hpp @@ -32,6 +32,10 @@ namespace platforms #undef linux #endif +#if defined(__FreeBSD_kernel__) +#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC +#endif + namespace linux { diff --git a/include/ableton/platforms/linux/ThreadFactory.hpp b/include/ableton/platforms/linux/ThreadFactory.hpp new file mode 100644 index 0000000..81988f9 --- /dev/null +++ b/include/ableton/platforms/linux/ThreadFactory.hpp @@ -0,0 +1,45 @@ +/* Copyright 2021, Ableton AG, Berlin. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * If you would like to incorporate Link into a proprietary software application, + * please contact <link-devs@ableton.com>. + */ + +#pragma once + +#include <pthread.h> +#include <thread> + +namespace ableton +{ +namespace platforms +{ +namespace linux +{ + +struct ThreadFactory +{ + template <typename Callable, typename... Args> + static std::thread makeThread(std::string name, Callable&& f, Args&&... args) + { + auto thread = std::thread(std::forward<Callable>(f), std::forward<Args>(args)...); + pthread_setname_np(thread.native_handle(), name.c_str()); + return thread; + } +}; + +} // namespace linux +} // namespace platforms +} // namespace ableton diff --git a/include/ableton/platforms/stl/Random.hpp b/include/ableton/platforms/stl/Random.hpp index e4b224b..18fb1a3 100644 --- a/include/ableton/platforms/stl/Random.hpp +++ b/include/ableton/platforms/stl/Random.hpp @@ -33,7 +33,8 @@ struct Random Random() : gen(rd()) , dist(33, 126) // printable ascii chars - {} + { + } uint8_t operator()() { diff --git a/include/ableton/platforms/windows/ThreadFactory.hpp b/include/ableton/platforms/windows/ThreadFactory.hpp new file mode 100644 index 0000000..1fc1a05 --- /dev/null +++ b/include/ableton/platforms/windows/ThreadFactory.hpp @@ -0,0 +1,52 @@ +/* Copyright 2021, Ableton AG, Berlin. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * If you would like to incorporate Link into a proprietary software application, + * please contact <link-devs@ableton.com>. + */ + +#pragma once + +#include <processthreadsapi.h> +#include <thread> +#include <utility> + +namespace ableton +{ +namespace platforms +{ +namespace windows +{ + +struct ThreadFactory +{ + template <typename Callable, typename... Args> + static std::thread makeThread(std::string name, Callable&& f, Args&&... args) + { + return std::thread( + [](std::string name, Callable&& f, Args&&... args) { + assert(name.length() < 20); + wchar_t nativeName[20]; + mbstowcs(nativeName, name.c_str(), name.length() + 1); + SetThreadDescription(GetCurrentThread(), nativeName); + f(args...); + }, + std::move(name), std::forward<Callable>(f), std::forward<Args>(args)...); + } +}; + +} // namespace windows +} // namespace platforms +} // namespace ableton diff --git a/include/ableton/platforms/windows/Windows.hpp b/include/ableton/platforms/windows/Windows.hpp index 0b835d1..5e8bd90 100644 --- a/include/ableton/platforms/windows/Windows.hpp +++ b/include/ableton/platforms/windows/Windows.hpp @@ -26,7 +26,7 @@ #define htonll(x) (x) #define ntohll(x) (x) #else -#define htonll(x) (((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) -#define ntohll(x) (((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) +#define htonll(x) (((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32)) +#define ntohll(x) (((uint64_t)ntohl((x)&0xFFFFFFFF) << 32) | ntohl((x) >> 32)) #endif #endif diff --git a/include/ableton/test/CatchWrapper.hpp b/include/ableton/test/CatchWrapper.hpp index 77edf50..997da91 100644 --- a/include/ableton/test/CatchWrapper.hpp +++ b/include/ableton/test/CatchWrapper.hpp @@ -30,7 +30,9 @@ #pragma warning(push, 0) #pragma warning(disable : 4242) #pragma warning(disable : 4244) +#pragma warning(disable : 4668) #pragma warning(disable : 4702) +#pragma warning(disable : 5220) #endif #include <catch.hpp> diff --git a/include/ableton/test/serial_io/Context.hpp b/include/ableton/test/serial_io/Context.hpp index ecc738d..f450fbb 100644 --- a/include/ableton/test/serial_io/Context.hpp +++ b/include/ableton/test/serial_io/Context.hpp @@ -77,11 +77,6 @@ public: mpScheduler->async(std::move(handler)); } - Context clone() - { - return {mNow, mIfAddrs, mpScheduler->makeChild()}; - } - using Timer = serial_io::Timer; Timer makeTimer() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1ebdd4b..170ef41 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,19 +19,19 @@ set(link_discovery_test_SOURCES set(link_core_test_SOURCES ableton/link/tst_Beats.cpp - ableton/link/tst_CircularFifo.cpp ableton/link/tst_ClientSessionTimelines.cpp ableton/link/tst_Controller.cpp ableton/link/tst_HostTimeFilter.cpp - ableton/link/tst_Kalman.cpp ableton/link/tst_LinearRegression.cpp ableton/link/tst_Measurement.cpp + ableton/link/tst_Median.cpp ableton/link/tst_Peers.cpp ableton/link/tst_Phase.cpp ableton/link/tst_PingResponder.cpp ableton/link/tst_StartStopState.cpp ableton/link/tst_Tempo.cpp ableton/link/tst_Timeline.cpp + ableton/link/tst_TripleBuffer.cpp ) set(link_test_SOURCES @@ -75,10 +75,11 @@ source_group("Test Utils" FILES # |___/ function(configure_link_test_executable target) - if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + if(CMAKE_SYSTEM_NAME MATCHES "Linux|kFreeBSD|GNU") target_link_libraries(${target} atomic pthread) endif() target_link_libraries(${target} Catch::Catch Ableton::Link) + target_compile_definitions(${target} PRIVATE -DCATCH_CONFIG_ENABLE_BENCHMARKING=1) endfunction() # For the LinkCore test suite, we add header dependencies individually so that diff --git a/src/ableton/discovery/tst_InterfaceScanner.cpp b/src/ableton/discovery/tst_InterfaceScanner.cpp index f52b721..b9e7d01 100644 --- a/src/ableton/discovery/tst_InterfaceScanner.cpp +++ b/src/ableton/discovery/tst_InterfaceScanner.cpp @@ -39,46 +39,51 @@ struct TestCallback std::vector<std::vector<asio::ip::address>> addrRanges; }; -// some test addresses -const asio::ip::address addr1 = asio::ip::address::from_string("123.123.123.1"); -const asio::ip::address addr2 = asio::ip::address::from_string("123.123.123.2"); - } // anonymous namespace -TEST_CASE("InterfaceScanner | NoInterfacesThenOne", "[InterfaceScanner]") +TEST_CASE("InterfaceScanner") { + const asio::ip::address addr1 = asio::ip::address::from_string("123.123.123.1"); + const asio::ip::address addr2 = asio::ip::address::from_string("123.123.123.2"); test::serial_io::Fixture io; auto callback = TestCallback{}; + + SECTION("NoInterfacesThenOneThenTwo") { - auto scanner = discovery::makeInterfaceScanner(std::chrono::seconds(2), - util::injectRef(callback), util::injectVal(io.makeIoContext())); - scanner.enable(true); - CHECK(1 == callback.addrRanges.size()); - io.setNetworkInterfaces({addr1}); - io.advanceTime(std::chrono::seconds(3)); + { + auto scanner = discovery::makeInterfaceScanner(std::chrono::seconds(2), + util::injectRef(callback), util::injectVal(io.makeIoContext())); + scanner.enable(true); + CHECK(1 == callback.addrRanges.size()); + io.setNetworkInterfaces({addr1}); + io.advanceTime(std::chrono::seconds(3)); + io.setNetworkInterfaces({addr1, addr2}); + io.advanceTime(std::chrono::seconds(2)); + } + REQUIRE(3 == callback.addrRanges.size()); + CHECK(0 == callback.addrRanges[0].size()); + REQUIRE(1 == callback.addrRanges[1].size()); + CHECK(addr1 == callback.addrRanges[1].front()); + REQUIRE(2 == callback.addrRanges[2].size()); + CHECK(addr1 == callback.addrRanges[2].front()); + CHECK(addr2 == callback.addrRanges[2].back()); } - REQUIRE(2 == callback.addrRanges.size()); - CHECK(0 == callback.addrRanges[0].size()); - REQUIRE(1 == callback.addrRanges[1].size()); - CHECK(addr1 == callback.addrRanges[1].front()); -} -TEST_CASE("InterfaceScanner | InterfaceGoesAway", "[InterfaceScanner]") -{ - test::serial_io::Fixture io; - io.setNetworkInterfaces({addr1}); - auto callback = TestCallback{}; + SECTION("InterfaceGoesAway", "[InterfaceScanner]") { - auto scanner = discovery::makeInterfaceScanner(std::chrono::seconds(2), - util::injectRef(callback), util::injectVal(io.makeIoContext())); - scanner.enable(true); - io.setNetworkInterfaces({}); - io.advanceTime(std::chrono::seconds(3)); + io.setNetworkInterfaces({addr1}); + { + auto scanner = discovery::makeInterfaceScanner(std::chrono::seconds(2), + util::injectRef(callback), util::injectVal(io.makeIoContext())); + scanner.enable(true); + io.setNetworkInterfaces({}); + io.advanceTime(std::chrono::seconds(3)); + } + REQUIRE(2 == callback.addrRanges.size()); + REQUIRE(1 == callback.addrRanges[0].size()); + CHECK(addr1 == callback.addrRanges[0].front()); + CHECK(0 == callback.addrRanges[1].size()); } - REQUIRE(2 == callback.addrRanges.size()); - REQUIRE(1 == callback.addrRanges[0].size()); - CHECK(addr1 == callback.addrRanges[0].front()); - CHECK(0 == callback.addrRanges[1].size()); } } // namespace link diff --git a/src/ableton/discovery/tst_Payload.cpp b/src/ableton/discovery/tst_Payload.cpp index ec7b4ad..5c776a0 100644 --- a/src/ableton/discovery/tst_Payload.cpp +++ b/src/ableton/discovery/tst_Payload.cpp @@ -28,157 +28,161 @@ namespace ableton namespace discovery { -TEST_CASE("Payload | EmptyPayload", "[Payload]") +TEST_CASE("Payload") { - CHECK(0 == sizeInByteStream(makePayload())); - CHECK(nullptr == toNetworkByteStream(makePayload(), nullptr)); -} - -TEST_CASE("Payload | FixedSizeEntryPayloadSize", "[Payload]") -{ - CHECK(12 == sizeInByteStream(makePayload(test::Foo{}))); -} - -TEST_CASE("Payload | SingleEntryPayloadEncoding", "[Payload]") -{ - const auto payload = makePayload(test::Foo{-1}); - std::vector<char> bytes(sizeInByteStream(payload)); - const auto end = toNetworkByteStream(payload, begin(bytes)); - - // Should have filled the buffer with the payload - CHECK(bytes.size() == static_cast<size_t>(end - begin(bytes))); - // Should have encoded the value 1 after the payload entry header - // as an unsigned in network byte order - const auto uresult = ntohl(reinterpret_cast<const std::uint32_t&>(*(begin(bytes) + 8))); - CHECK(-1 == reinterpret_cast<const std::int32_t&>(uresult)); -} - -TEST_CASE("Payload | DoubleEntryPayloadSize", "[Payload]") -{ - CHECK(48 == sizeInByteStream(makePayload(test::Foo{}, test::Bar{{0, 1, 2}}))); -} - -TEST_CASE("Payload | DoubleEntryPayloadEncoding", "[Payload]") -{ - const auto payload = makePayload(test::Foo{1}, test::Bar{{0, 1, 2}}); - std::vector<char> bytes(sizeInByteStream(payload)); - const auto end = toNetworkByteStream(payload, begin(bytes)); - - // Should have filled the buffer with the payload - CHECK(bytes.size() == static_cast<size_t>(end - begin(bytes))); - // Should have encoded the value 1 after the first payload entry header - // and the number of elements of the vector as well as 0,1,2 after the - // second payload entry header in network byte order - CHECK(1 == ntohl(reinterpret_cast<const std::uint32_t&>(*(begin(bytes) + 8)))); - CHECK(3 == ntohl(reinterpret_cast<const std::uint32_t&>(*(begin(bytes) + 20)))); - CHECK(0 == ntohll(reinterpret_cast<const std::uint64_t&>(*(begin(bytes) + 24)))); - CHECK(1 == ntohll(reinterpret_cast<const std::uint64_t&>(*(begin(bytes) + 32)))); - CHECK(2 == ntohll(reinterpret_cast<const std::uint64_t&>(*(begin(bytes) + 40)))); -} - -TEST_CASE("Payload | RoundtripSingleEntry", "[Payload]") -{ - const auto expected = test::Foo{1}; - const auto payload = makePayload(expected); - std::vector<char> bytes(sizeInByteStream(payload)); - const auto end = toNetworkByteStream(payload, begin(bytes)); - - test::Foo actual{}; - parsePayload<test::Foo>( - begin(bytes), end, [&actual](const test::Foo& foo) { actual = foo; }); - - CHECK(expected.fooVal == actual.fooVal); -} - -TEST_CASE("Payload | RoundtripDoubleEntry", "[Payload]") -{ - const auto expectedFoo = test::Foo{1}; - const auto expectedBar = test::Bar{{0, 1, 2}}; - const auto payload = makePayload(expectedBar, expectedFoo); - std::vector<char> bytes(sizeInByteStream(payload)); - const auto end = toNetworkByteStream(payload, begin(bytes)); - - test::Foo actualFoo{}; - test::Bar actualBar{}; - parsePayload<test::Foo, test::Bar>(begin(bytes), end, - [&actualFoo](const test::Foo& foo) { actualFoo = foo; }, - [&actualBar](const test::Bar& bar) { actualBar = bar; }); - - CHECK(expectedFoo.fooVal == actualFoo.fooVal); - CHECK(expectedBar.barVals == actualBar.barVals); -} - -TEST_CASE("Payload | RoundtripSingleEntryWithMultipleVectors", "[Payload]") -{ - const auto expectedFoobar = test::Foobar{{0, 1, 2}, {3, 4, 5}}; - const auto payload = makePayload(expectedFoobar); - std::vector<char> bytes(sizeInByteStream(payload)); - const auto end = toNetworkByteStream(payload, begin(bytes)); - - test::Foobar actualFoobar{}; - parsePayload<test::Foobar>(begin(bytes), end, - [&actualFoobar](const test::Foobar& foobar) { actualFoobar = foobar; }); - - CHECK(expectedFoobar.asTuple() == actualFoobar.asTuple()); -} - -TEST_CASE("Payload | ParseSubset", "[Payload]") -{ - // Encode two payload entries - const auto expectedFoo = test::Foo{1}; - const auto expectedBar = test::Bar{{0, 1, 2}}; - const auto payload = makePayload(expectedFoo, expectedBar); - std::vector<char> bytes(sizeInByteStream(payload)); - const auto end = toNetworkByteStream(payload, begin(bytes)); - - // Only decode one of them - test::Bar actualBar{}; - parsePayload<test::Bar>( - begin(bytes), end, [&actualBar](const test::Bar& bar) { actualBar = bar; }); - - CHECK(expectedBar.barVals == actualBar.barVals); -} - -TEST_CASE("Payload | ParseTruncatedEntry", "[Payload]") -{ - const auto expectedFoo = test::Foo{1}; - const auto expectedBar = test::Bar{{0, 1, 2}}; - const auto payload = makePayload(expectedBar, expectedFoo); - std::vector<char> bytes(sizeInByteStream(payload)); - const auto end = toNetworkByteStream(payload, begin(bytes)); - - test::Foo actualFoo{}; - test::Bar actualBar{}; - - REQUIRE_THROWS_AS(( // We truncate the buffer by one byte - parsePayload<test::Foo, test::Bar>(begin(bytes), end - 1, - [&actualFoo](const test::Foo& foo) { actualFoo = foo; }, - [&actualBar](const test::Bar& bar) { actualBar = bar; })), - std::runtime_error); - - // We expect that bar should be properly parsed but foo not - CHECK(0 == actualFoo.fooVal); - CHECK(expectedBar.barVals == actualBar.barVals); -} - -TEST_CASE("Payload | AddPayloads", "[Payload]") -{ - // The sum of a foo payload and a bar payload should be equal in - // every way to a foobar payload - const auto foo = test::Foo{1}; - const auto bar = test::Bar{{0, 1, 2}}; - const auto fooBarPayload = makePayload(foo, bar); - const auto sumPayload = makePayload(foo) + makePayload(bar); - - REQUIRE(sizeInByteStream(fooBarPayload) == sizeInByteStream(sumPayload)); - - std::vector<char> fooBarBytes(sizeInByteStream(fooBarPayload)); - std::vector<char> sumBytes(sizeInByteStream(sumPayload)); - - const auto fooBarEnd = toNetworkByteStream(fooBarPayload, begin(fooBarBytes)); - toNetworkByteStream(sumPayload, begin(sumBytes)); - - CHECK(std::equal(begin(fooBarBytes), fooBarEnd, begin(sumBytes))); + SECTION("EmptyPayload") + { + CHECK(0 == sizeInByteStream(makePayload())); + CHECK(nullptr == toNetworkByteStream(makePayload(), nullptr)); + } + + SECTION("FixedSizeEntryPayloadSize") + { + CHECK(12 == sizeInByteStream(makePayload(test::Foo{}))); + } + + SECTION("SingleEntryPayloadEncoding") + { + const auto payload = makePayload(test::Foo{-1}); + std::vector<char> bytes(sizeInByteStream(payload)); + const auto end = toNetworkByteStream(payload, begin(bytes)); + + // Should have filled the buffer with the payload + CHECK(bytes.size() == static_cast<size_t>(end - begin(bytes))); + // Should have encoded the value 1 after the payload entry header + // as an unsigned in network byte order + const auto uresult = + ntohl(reinterpret_cast<const std::uint32_t&>(*(begin(bytes) + 8))); + CHECK(-1 == reinterpret_cast<const std::int32_t&>(uresult)); + } + + SECTION("DoubleEntryPayloadSize") + { + CHECK(48 == sizeInByteStream(makePayload(test::Foo{}, test::Bar{{0, 1, 2}}))); + } + + SECTION("DoubleEntryPayloadEncoding") + { + const auto payload = makePayload(test::Foo{1}, test::Bar{{0, 1, 2}}); + std::vector<char> bytes(sizeInByteStream(payload)); + const auto end = toNetworkByteStream(payload, begin(bytes)); + + // Should have filled the buffer with the payload + CHECK(bytes.size() == static_cast<size_t>(end - begin(bytes))); + // Should have encoded the value 1 after the first payload entry header + // and the number of elements of the vector as well as 0,1,2 after the + // second payload entry header in network byte order + CHECK(1 == ntohl(reinterpret_cast<const std::uint32_t&>(*(begin(bytes) + 8)))); + CHECK(3 == ntohl(reinterpret_cast<const std::uint32_t&>(*(begin(bytes) + 20)))); + CHECK(0 == ntohll(reinterpret_cast<const std::uint64_t&>(*(begin(bytes) + 24)))); + CHECK(1 == ntohll(reinterpret_cast<const std::uint64_t&>(*(begin(bytes) + 32)))); + CHECK(2 == ntohll(reinterpret_cast<const std::uint64_t&>(*(begin(bytes) + 40)))); + } + + SECTION("RoundtripSingleEntry") + { + const auto expected = test::Foo{1}; + const auto payload = makePayload(expected); + std::vector<char> bytes(sizeInByteStream(payload)); + const auto end = toNetworkByteStream(payload, begin(bytes)); + + test::Foo actual{}; + parsePayload<test::Foo>( + begin(bytes), end, [&actual](const test::Foo& foo) { actual = foo; }); + + CHECK(expected.fooVal == actual.fooVal); + } + + SECTION("RoundtripDoubleEntry") + { + const auto expectedFoo = test::Foo{1}; + const auto expectedBar = test::Bar{{0, 1, 2}}; + const auto payload = makePayload(expectedBar, expectedFoo); + std::vector<char> bytes(sizeInByteStream(payload)); + const auto end = toNetworkByteStream(payload, begin(bytes)); + + test::Foo actualFoo{}; + test::Bar actualBar{}; + parsePayload<test::Foo, test::Bar>(begin(bytes), end, + [&actualFoo](const test::Foo& foo) { actualFoo = foo; }, + [&actualBar](const test::Bar& bar) { actualBar = bar; }); + + CHECK(expectedFoo.fooVal == actualFoo.fooVal); + CHECK(expectedBar.barVals == actualBar.barVals); + } + + SECTION("RoundtripSingleEntryWithMultipleVectors") + { + const auto expectedFoobar = test::Foobar{{0, 1, 2}, {3, 4, 5}}; + const auto payload = makePayload(expectedFoobar); + std::vector<char> bytes(sizeInByteStream(payload)); + const auto end = toNetworkByteStream(payload, begin(bytes)); + + test::Foobar actualFoobar{}; + parsePayload<test::Foobar>(begin(bytes), end, + [&actualFoobar](const test::Foobar& foobar) { actualFoobar = foobar; }); + + CHECK(expectedFoobar.asTuple() == actualFoobar.asTuple()); + } + + SECTION("ParseSubset") + { + // Encode two payload entries + const auto expectedFoo = test::Foo{1}; + const auto expectedBar = test::Bar{{0, 1, 2}}; + const auto payload = makePayload(expectedFoo, expectedBar); + std::vector<char> bytes(sizeInByteStream(payload)); + const auto end = toNetworkByteStream(payload, begin(bytes)); + + // Only decode one of them + test::Bar actualBar{}; + parsePayload<test::Bar>( + begin(bytes), end, [&actualBar](const test::Bar& bar) { actualBar = bar; }); + + CHECK(expectedBar.barVals == actualBar.barVals); + } + + SECTION("ParseTruncatedEntry") + { + const auto expectedFoo = test::Foo{1}; + const auto expectedBar = test::Bar{{0, 1, 2}}; + const auto payload = makePayload(expectedBar, expectedFoo); + std::vector<char> bytes(sizeInByteStream(payload)); + const auto end = toNetworkByteStream(payload, begin(bytes)); + + test::Foo actualFoo{}; + test::Bar actualBar{}; + + REQUIRE_THROWS_AS(( // We truncate the buffer by one byte + parsePayload<test::Foo, test::Bar>(begin(bytes), end - 1, + [&actualFoo](const test::Foo& foo) { actualFoo = foo; }, + [&actualBar](const test::Bar& bar) { actualBar = bar; })), + std::runtime_error); + + // We expect that bar should be properly parsed but foo not + CHECK(0 == actualFoo.fooVal); + CHECK(expectedBar.barVals == actualBar.barVals); + } + + SECTION("AddPayloads") + { + // The sum of a foo payload and a bar payload should be equal in + // every way to a foobar payload + const auto foo = test::Foo{1}; + const auto bar = test::Bar{{0, 1, 2}}; + const auto fooBarPayload = makePayload(foo, bar); + const auto sumPayload = makePayload(foo) + makePayload(bar); + + REQUIRE(sizeInByteStream(fooBarPayload) == sizeInByteStream(sumPayload)); + + std::vector<char> fooBarBytes(sizeInByteStream(fooBarPayload)); + std::vector<char> sumBytes(sizeInByteStream(sumPayload)); + + const auto fooBarEnd = toNetworkByteStream(fooBarPayload, begin(fooBarBytes)); + toNetworkByteStream(sumPayload, begin(sumBytes)); + + CHECK(std::equal(begin(fooBarBytes), fooBarEnd, begin(sumBytes))); + } } } // namespace discovery diff --git a/src/ableton/discovery/tst_PeerGateway.cpp b/src/ableton/discovery/tst_PeerGateway.cpp index 4bd38ff..5890a43 100644 --- a/src/ableton/discovery/tst_PeerGateway.cpp +++ b/src/ableton/discovery/tst_PeerGateway.cpp @@ -96,93 +96,76 @@ void expectPeersTimedOut(std::vector<std::string> expected, const TestObserver& CHECK(expected == observer.mPeersTimedOut); } -// Test peers -const auto peerA = TestNodeState{"peerA", 120}; -const auto peerB = TestNodeState{"peerB", 150}; - } // anonymous namespace -TEST_CASE("PeerGateway | NoActivity", "[PeerGateway]") +TEST_CASE("PeerGateway") { - test::serial_io::Fixture io; - TestObserver observer; - auto listener = makePeerGateway(util::injectVal(TestMessenger{}), - util::injectRef(observer), util::injectVal(io.makeIoContext())); - io.advanceTime(std::chrono::seconds(10)); - - // Without any outside interaction but the passage of time, our - // listener should not have seen any peers. - CHECK(observer.mPeersSeen.empty()); - CHECK(observer.mPeersLeft.empty()); - CHECK(observer.mPeersTimedOut.empty()); -} + const auto peerA = TestNodeState{"peerA", 120}; + const auto peerB = TestNodeState{"peerB", 150}; -TEST_CASE("PeerGateway | ReceivedPeerState", "[PeerGateway]") -{ test::serial_io::Fixture io; TestObserver observer; - TestMessenger messenger; - auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer), - util::injectVal(io.makeIoContext())); - messenger.receivePeerState({peerA, 5}); - io.flush(); - - expectPeersSeen({peerA}, observer); -} + SECTION("NoActivity") + { + auto listener = makePeerGateway(util::injectVal(TestMessenger{}), + util::injectRef(observer), util::injectVal(io.makeIoContext())); + io.advanceTime(std::chrono::seconds(10)); + + // Without any outside interaction but the passage of time, our + // listener should not have seen any peers. + CHECK(observer.mPeersSeen.empty()); + CHECK(observer.mPeersLeft.empty()); + CHECK(observer.mPeersTimedOut.empty()); + } -TEST_CASE("PeerGateway | TwoPeersOneLeaves", "[PeerGateway]") -{ - test::serial_io::Fixture io; - TestObserver observer; TestMessenger messenger; auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer), util::injectVal(io.makeIoContext())); - messenger.receivePeerState({peerA, 5}); - messenger.receivePeerState({peerB, 5}); - messenger.receiveByeBye({peerA.ident()}); - io.flush(); + SECTION("ReceivedPeerState") + { + messenger.receivePeerState({peerA, 5}); + io.flush(); - expectPeersSeen({peerA, peerB}, observer); - expectPeersLeft({peerA.ident()}, observer); -} + expectPeersSeen({peerA}, observer); + } -TEST_CASE("PeerGateway | TwoPeersOneTimesOut", "[PeerGateway]") -{ - test::serial_io::Fixture io; - TestObserver observer; - TestMessenger messenger; - auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer), - util::injectVal(io.makeIoContext())); + SECTION("TwoPeersOneLeaves") + { + messenger.receivePeerState({peerA, 5}); + messenger.receivePeerState({peerB, 5}); + messenger.receiveByeBye({peerA.ident()}); + io.flush(); - messenger.receivePeerState({peerA, 5}); - messenger.receivePeerState({peerB, 10}); + expectPeersSeen({peerA, peerB}, observer); + expectPeersLeft({peerA.ident()}, observer); + } - io.advanceTime(std::chrono::seconds(3)); - expectPeersTimedOut({}, observer); - io.advanceTime(std::chrono::seconds(4)); - expectPeersTimedOut({peerA.ident()}, observer); -} + SECTION("TwoPeersOneTimesOut") + { + messenger.receivePeerState({peerA, 5}); + messenger.receivePeerState({peerB, 10}); -TEST_CASE("PeerGateway | PeerTimesOutAndIsSeenAgain", "[PeerGateway]") -{ - test::serial_io::Fixture io; - TestObserver observer; - TestMessenger messenger; - auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer), - util::injectVal(io.makeIoContext())); + io.advanceTime(std::chrono::seconds(3)); + expectPeersTimedOut({}, observer); + io.advanceTime(std::chrono::seconds(4)); + expectPeersTimedOut({peerA.ident()}, observer); + } - messenger.receivePeerState({peerA, 5}); - io.advanceTime(std::chrono::seconds(7)); + SECTION("PeerTimesOutAndIsSeenAgain") + { + messenger.receivePeerState({peerA, 5}); + io.advanceTime(std::chrono::seconds(7)); - expectPeersTimedOut({peerA.ident()}, observer); + expectPeersTimedOut({peerA.ident()}, observer); - messenger.receivePeerState({peerA, 5}); - io.advanceTime(std::chrono::seconds(3)); + messenger.receivePeerState({peerA, 5}); + io.advanceTime(std::chrono::seconds(3)); - expectPeersSeen({peerA, peerA}, observer); - expectPeersTimedOut({peerA.ident()}, observer); + expectPeersSeen({peerA, peerA}, observer); + expectPeersTimedOut({peerA.ident()}, observer); + } } } // namespace discovery diff --git a/src/ableton/discovery/tst_PeerGateways.cpp b/src/ableton/discovery/tst_PeerGateways.cpp index c92a0b5..ec5f2da 100644 --- a/src/ableton/discovery/tst_PeerGateways.cpp +++ b/src/ableton/discovery/tst_PeerGateways.cpp @@ -47,12 +47,9 @@ struct Factory } }; -const asio::ip::address addr1 = asio::ip::address::from_string("192.192.192.1"); - -const asio::ip::address addr2 = asio::ip::address::from_string("192.192.192.2"); template <typename Gateways> -void expectGatewaysAsync( +void expectGateways( Gateways& gateways, test::serial_io::Fixture& io, std::vector<asio::ip::address> addrs) { @@ -60,7 +57,7 @@ void expectGatewaysAsync( using GatewayIt = typename Gateways::GatewayMap::iterator; bool bTested = false; - gateways.withGatewaysAsync([addrs, &bTested](GatewayIt begin, const GatewayIt end) { + gateways.withGateways([addrs, &bTested](GatewayIt begin, const GatewayIt end) { bTested = true; REQUIRE(static_cast<size_t>(distance(begin, end)) == addrs.size()); std::size_t i = 0; @@ -75,68 +72,60 @@ void expectGatewaysAsync( } // anonymous namespace -TEST_CASE("PeerGateways | EmptyIfNoInterfaces", "[PeerGateways]") +TEST_CASE("PeerGateways") { - test::serial_io::Fixture io; - auto pGateways = makePeerGateways( - std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext())); - pGateways->enable(true); - expectGatewaysAsync(*pGateways, io, {}); -} - + const asio::ip::address addr1 = asio::ip::address::from_string("192.192.192.1"); + const asio::ip::address addr2 = asio::ip::address::from_string("192.192.192.2"); -TEST_CASE("PeerGateways | MatchesAfterInitialScan", "[PeerGateways]") -{ test::serial_io::Fixture io; - io.setNetworkInterfaces({addr1, addr2}); auto pGateways = makePeerGateways( std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext())); - pGateways->enable(true); - expectGatewaysAsync(*pGateways, io, {addr1, addr2}); -} -TEST_CASE("PeerGateways | GatewayAppears", "[PeerGateways]") -{ - test::serial_io::Fixture io; - io.setNetworkInterfaces({addr1}); + SECTION("EmptyIfNoInterfaces") + { + pGateways->enable(true); + expectGateways(*pGateways, io, {}); + } - auto pGateways = makePeerGateways( - std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext())); - pGateways->enable(true); - expectGatewaysAsync(*pGateways, io, {addr1}); + SECTION("MatchesAfterInitialScan") + { + io.setNetworkInterfaces({addr1, addr2}); + pGateways->enable(true); + expectGateways(*pGateways, io, {addr1, addr2}); + } - io.setNetworkInterfaces({addr1, addr2}); - io.advanceTime(std::chrono::seconds(3)); - expectGatewaysAsync(*pGateways, io, {addr1, addr2}); -} + SECTION("GatewayAppears") + { + io.setNetworkInterfaces({addr1}); + pGateways->enable(true); + expectGateways(*pGateways, io, {addr1}); -TEST_CASE("PeerGateways | GatewayDisappears", "[PeerGateways]") -{ - test::serial_io::Fixture io; - io.setNetworkInterfaces({addr1, addr2}); + io.setNetworkInterfaces({addr1, addr2}); + io.advanceTime(std::chrono::seconds(3)); + expectGateways(*pGateways, io, {addr1, addr2}); + } - auto pGateways = makePeerGateways( - std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext())); - pGateways->enable(true); - expectGatewaysAsync(*pGateways, io, {addr1, addr2}); + SECTION("GatewayDisappears") + { + io.setNetworkInterfaces({addr1, addr2}); + pGateways->enable(true); + expectGateways(*pGateways, io, {addr1, addr2}); - io.setNetworkInterfaces({addr1}); - io.advanceTime(std::chrono::seconds(3)); - expectGatewaysAsync(*pGateways, io, {addr1}); -} + io.setNetworkInterfaces({addr1}); + io.advanceTime(std::chrono::seconds(3)); + expectGateways(*pGateways, io, {addr1}); + } -TEST_CASE("PeerGateways | GatewayChangesAddress", "[PeerGateways]") -{ - test::serial_io::Fixture io; - io.setNetworkInterfaces({addr1}); - auto pGateways = makePeerGateways( - std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext())); - pGateways->enable(true); - expectGatewaysAsync(*pGateways, io, {addr1}); + SECTION("GatewayChangesAddress") + { + io.setNetworkInterfaces({addr1}); + pGateways->enable(true); + expectGateways(*pGateways, io, {addr1}); - io.setNetworkInterfaces({addr2}); - io.advanceTime(std::chrono::seconds(3)); - expectGatewaysAsync(*pGateways, io, {addr2}); + io.setNetworkInterfaces({addr2}); + io.advanceTime(std::chrono::seconds(3)); + expectGateways(*pGateways, io, {addr2}); + } } } // namespace discovery diff --git a/src/ableton/discovery/tst_UdpMessenger.cpp b/src/ableton/discovery/tst_UdpMessenger.cpp index eae068f..7d66f21 100644 --- a/src/ableton/discovery/tst_UdpMessenger.cpp +++ b/src/ableton/discovery/tst_UdpMessenger.cpp @@ -58,88 +58,6 @@ struct TestNodeState int32_t fooVal; }; -// Test data for a simulated peer -const TestNodeState peerState = {5, 15}; - -const asio::ip::udp::endpoint peerEndpoint = { - asio::ip::address::from_string("123.123.234.234"), 1900}; - -} // anonymous namespace - -TEST_CASE("UdpMessenger | BroadcastsStateOnConstruction", "[UdpMessenger]") -{ - ::ableton::test::serial_io::Fixture io; - auto context = io.makeIoContext(); - const auto state = TestNodeState{3, 10}; - auto iface = test::Interface{}; - auto messenger = - makeUdpMessenger(util::injectRef(iface), state, util::injectRef(context), 1, 1); - - REQUIRE(1 == iface.sentMessages.size()); - - const auto messageBuffer = iface.sentMessages[0].first; - const auto sentTo = iface.sentMessages[0].second; - const auto result = v1::parseMessageHeader<TestNodeState::IdType>( - begin(messageBuffer), end(messageBuffer)); - - // Expect an Alive header - CHECK(v1::kAlive == result.first.messageType); - CHECK(state.nodeId == result.first.ident); - CHECK(1 == result.first.ttl); - // Sent to the multicast endpoint - CHECK(multicastEndpoint() == sentTo); - - // And the payload should parse to equal to the original state - const auto actualState = - TestNodeState::fromPayload(state.nodeId, result.second, end(messageBuffer)); - CHECK(state.fooVal == actualState.fooVal); -} - -TEST_CASE("UdpMessenger | Heartbeat", "[UdpMessenger]") -{ - ::ableton::test::serial_io::Fixture io; - const auto state = TestNodeState{3, 10}; - auto iface = test::Interface{}; - auto messenger = makeUdpMessenger( - util::injectRef(iface), state, util::injectVal(io.makeIoContext()), 4, 2); - - REQUIRE(1 == iface.sentMessages.size()); - // At two seconds the messenger should have broadcasted its state again - io.advanceTime(std::chrono::seconds(3)); - CHECK(2 == iface.sentMessages.size()); -} - -TEST_CASE("UdpMessenger | Response", "[UdpMessenger]") -{ - ::ableton::test::serial_io::Fixture io; - const auto state = TestNodeState{3, 10}; - auto iface = test::Interface{}; - auto messenger = makeUdpMessenger( - util::injectRef(iface), state, util::injectVal(io.makeIoContext()), 1, 1); - - // Simulate state broadcast from peer, leaving out details like payload - v1::MessageBuffer buffer; - const auto messageEnd = - v1::aliveMessage(peerState.ident(), 0, makePayload(), begin(buffer)); - iface.incomingMessage(peerEndpoint, begin(buffer), messageEnd); - - // The messenger should have responded to the alive message with its - // current state - REQUIRE(2 == iface.sentMessages.size()); - const auto messageBuffer = iface.sentMessages[1].first; - const auto sentTo = iface.sentMessages[1].second; - const auto result = v1::parseMessageHeader<TestNodeState::IdType>( - begin(messageBuffer), end(messageBuffer)); - - CHECK(v1::kResponse == result.first.messageType); - CHECK(state.nodeId == result.first.ident); - CHECK(1 == result.first.ttl); - CHECK(peerEndpoint == sentTo); -} - -namespace -{ - struct TestHandler { void operator()(PeerState<TestNodeState> state) @@ -156,55 +74,6 @@ struct TestHandler std::vector<ByeBye<TestNodeState::IdType>> byeByes; }; -} // namespace - -TEST_CASE("UdpMessenger | Receive", "[UdpMessenger]") -{ - ::ableton::test::serial_io::Fixture io; - auto iface = test::Interface{}; - auto tmpMessenger = makeUdpMessenger( - util::injectRef(iface), TestNodeState{}, util::injectVal(io.makeIoContext()), 1, 1); - auto messenger = std::move(tmpMessenger); - auto handler = TestHandler{}; - messenger.receive(std::ref(handler)); - - v1::MessageBuffer buffer; - // Receive an alive message - auto end = v1::aliveMessage(peerState.nodeId, 3, toPayload(peerState), begin(buffer)); - iface.incomingMessage(peerEndpoint, begin(buffer), end); - // And a bye bye message - end = v1::byeByeMessage(peerState.nodeId, begin(buffer)); - messenger.receive(std::ref(handler)); - iface.incomingMessage(peerEndpoint, begin(buffer), end); - - REQUIRE(1 == handler.peerStates.size()); - CHECK(3 == handler.peerStates[0].ttl); - CHECK(peerState.nodeId == handler.peerStates[0].peerState.nodeId); - CHECK(peerState.fooVal == handler.peerStates[0].peerState.fooVal); - REQUIRE(1 == handler.byeByes.size()); - CHECK(peerState.nodeId == handler.byeByes[0].peerId); -} - -TEST_CASE("UdpMessenger | SendByeByeOnDestruction", "[UdpMessenger]") -{ - ::ableton::test::serial_io::Fixture io; - auto iface = test::Interface{}; - { - auto messenger = makeUdpMessenger(util::injectRef(iface), TestNodeState{5, 10}, - util::injectVal(io.makeIoContext()), 1, 1); - } - REQUIRE(2 == iface.sentMessages.size()); - const auto messageBuffer = iface.sentMessages[1].first; - const auto sentTo = iface.sentMessages[1].second; - const auto result = v1::parseMessageHeader<TestNodeState::IdType>( - begin(messageBuffer), end(messageBuffer)); - CHECK(v1::kByeBye == result.first.messageType); - CHECK(multicastEndpoint() == sentTo); -} - -namespace -{ - template <typename Messenger> struct MessengerWrapper { @@ -221,21 +90,131 @@ MessengerWrapper<Messenger> wrapMessenger(Messenger messenger) { return {std::move(messenger)}; } -} // namespace -TEST_CASE("UdpMessenger | MovingMessengerDoesntSendByeBye", "[UdpMessenger]") +} // anonymous namespace + +TEST_CASE("UdpMessenger") { + const TestNodeState state1 = {5, 15}; + const auto state2 = TestNodeState{3, 10}; + const auto peerEndpoint = + asio::ip::udp::endpoint{asio::ip::address::from_string("123.123.234.234"), 1900}; ::ableton::test::serial_io::Fixture io; - // The destructor for the messenger is written so that if an - // instance is moved from, it won't send the bye bye message. auto iface = test::Interface{}; + + SECTION("BroadcastsStateOnConstruction") + { + auto messenger = makeUdpMessenger( + util::injectRef(iface), state2, util::injectVal(io.makeIoContext()), 1, 1); + + REQUIRE(1 == iface.sentMessages.size()); + + const auto messageBuffer = iface.sentMessages[0].first; + const auto sentTo = iface.sentMessages[0].second; + const auto result = v1::parseMessageHeader<TestNodeState::IdType>( + begin(messageBuffer), end(messageBuffer)); + + // Expect an Alive header + CHECK(v1::kAlive == result.first.messageType); + CHECK(state2.nodeId == result.first.ident); + CHECK(1 == result.first.ttl); + // Sent to the multicast endpoint + CHECK(multicastEndpoint() == sentTo); + + // And the payload should parse to equal to the original state + const auto actualState = + TestNodeState::fromPayload(state2.nodeId, result.second, end(messageBuffer)); + CHECK(state2.fooVal == actualState.fooVal); + } + + SECTION("Heartbeat") + { + auto messenger = makeUdpMessenger( + util::injectRef(iface), state2, util::injectVal(io.makeIoContext()), 4, 2); + + REQUIRE(1 == iface.sentMessages.size()); + // At two seconds the messenger should have broadcasted its state again + io.advanceTime(std::chrono::seconds(3)); + CHECK(2 == iface.sentMessages.size()); + } + + SECTION("Response") + { + auto messenger = makeUdpMessenger( + util::injectRef(iface), state2, util::injectVal(io.makeIoContext()), 1, 1); + + // Simulate state broadcast from peer, leaving out details like payload + v1::MessageBuffer buffer; + const auto messageEnd = + v1::aliveMessage(state1.ident(), 0, makePayload(), begin(buffer)); + iface.incomingMessage(peerEndpoint, begin(buffer), messageEnd); + + // The messenger should have responded to the alive message with its + // current state + REQUIRE(2 == iface.sentMessages.size()); + const auto messageBuffer = iface.sentMessages[1].first; + const auto sentTo = iface.sentMessages[1].second; + const auto result = v1::parseMessageHeader<TestNodeState::IdType>( + begin(messageBuffer), end(messageBuffer)); + + CHECK(v1::kResponse == result.first.messageType); + CHECK(state2.nodeId == result.first.ident); + CHECK(1 == result.first.ttl); + CHECK(peerEndpoint == sentTo); + } + + SECTION("Receive") + { + auto tmpMessenger = makeUdpMessenger( + util::injectRef(iface), TestNodeState{}, util::injectVal(io.makeIoContext()), 1, 1); + auto messenger = std::move(tmpMessenger); + auto handler = TestHandler{}; + messenger.receive(std::ref(handler)); + + v1::MessageBuffer buffer; + // Receive an alive message + auto end = v1::aliveMessage(state1.nodeId, 3, toPayload(state1), begin(buffer)); + iface.incomingMessage(peerEndpoint, begin(buffer), end); + // And a bye bye message + end = v1::byeByeMessage(state1.nodeId, begin(buffer)); + messenger.receive(std::ref(handler)); + iface.incomingMessage(peerEndpoint, begin(buffer), end); + + REQUIRE(1 == handler.peerStates.size()); + CHECK(3 == handler.peerStates[0].ttl); + CHECK(state1.nodeId == handler.peerStates[0].peerState.nodeId); + CHECK(state1.fooVal == handler.peerStates[0].peerState.fooVal); + REQUIRE(1 == handler.byeByes.size()); + CHECK(state1.nodeId == handler.byeByes[0].peerId); + } + + SECTION("SendByeByeOnDestruction") + { + { + auto messenger = makeUdpMessenger(util::injectRef(iface), TestNodeState{5, 10}, + util::injectVal(io.makeIoContext()), 1, 1); + } + REQUIRE(2 == iface.sentMessages.size()); + const auto messageBuffer = iface.sentMessages[1].first; + const auto sentTo = iface.sentMessages[1].second; + const auto result = v1::parseMessageHeader<TestNodeState::IdType>( + begin(messageBuffer), end(messageBuffer)); + CHECK(v1::kByeBye == result.first.messageType); + CHECK(multicastEndpoint() == sentTo); + } + + SECTION("MovingMessengerDoesntSendByeBye") { - auto messenger = makeUdpMessenger(util::injectRef(iface), TestNodeState{5, 10}, - util::injectVal(io.makeIoContext()), 1, 1); - auto wrapper = wrapMessenger(std::move(messenger)); + // The destructor for the messenger is written so that if an + // instance is moved from, it won't send the bye bye message. + { + auto messenger = makeUdpMessenger(util::injectRef(iface), TestNodeState{5, 10}, + util::injectVal(io.makeIoContext()), 1, 1); + auto wrapper = wrapMessenger(std::move(messenger)); + } + // We should have an initial Alive and then a single ByeBye + CHECK(2 == iface.sentMessages.size()); } - // We should have an initial Alive and then a single ByeBye - CHECK(2 == iface.sentMessages.size()); } } // namespace discovery diff --git a/src/ableton/link/tst_Beats.cpp b/src/ableton/link/tst_Beats.cpp index 4856f74..8ee3e26 100644 --- a/src/ableton/link/tst_Beats.cpp +++ b/src/ableton/link/tst_Beats.cpp @@ -25,67 +25,70 @@ namespace ableton namespace link { -TEST_CASE("Beats | ConstructFromFloating", "[Beats]") +TEST_CASE("Beats") { - const auto beats = Beats{0.5}; - CHECK(500000 == beats.microBeats()); - CHECK(0.5 == Approx(beats.floating())); -} + SECTION("ConstructFromFloating") + { + const auto beats = Beats{0.5}; + CHECK(500000 == beats.microBeats()); + CHECK(0.5 == Approx(beats.floating())); + } -TEST_CASE("Beats | ConstructFromMicros", "[Beats]") -{ - const auto beats = Beats{INT64_C(100000)}; - CHECK(100000 == beats.microBeats()); - CHECK(0.1 == Approx(beats.floating())); -} + SECTION("ConstructFromMicros") + { + const auto beats = Beats{INT64_C(100000)}; + CHECK(100000 == beats.microBeats()); + CHECK(0.1 == Approx(beats.floating())); + } -TEST_CASE("Beats | Negation", "[Beats]") -{ - const auto beat = Beats{1.}; - CHECK(beat > -beat); - CHECK(beat == -(-beat)); - CHECK(-beat < Beats{0.}); -} + SECTION("Negation") + { + const auto beat = Beats{1.}; + CHECK(beat > -beat); + CHECK(beat == -(-beat)); + CHECK(-beat < Beats{0.}); + } -TEST_CASE("Beats | Addition", "[Beats]") -{ - const auto beat1 = Beats{0.5}; - const auto beat2 = Beats{INT64_C(200000)}; - const auto beat3 = Beats{0.1}; - CHECK(beat1 == beat2 + beat2 + beat3); -} + SECTION("Addition") + { + const auto beat1 = Beats{0.5}; + const auto beat2 = Beats{INT64_C(200000)}; + const auto beat3 = Beats{0.1}; + CHECK(beat1 == beat2 + beat2 + beat3); + } -TEST_CASE("Beats | Subtraction", "[Beats]") -{ - const auto beat1 = Beats{0.5}; - const auto beat2 = Beats{INT64_C(200000)}; - const auto beat3 = Beats{0.1}; - CHECK(beat3 == beat1 - beat2 - beat2); -} + SECTION("Subtraction") + { + const auto beat1 = Beats{0.5}; + const auto beat2 = Beats{INT64_C(200000)}; + const auto beat3 = Beats{0.1}; + CHECK(beat3 == beat1 - beat2 - beat2); + } -TEST_CASE("Beats | Modulo", "[Beats]") -{ - const auto beat1 = Beats{0.1}; - const auto beat2 = Beats{0.5}; - const auto beat3 = Beats{0.6}; - const auto beat4 = Beats{0.}; - CHECK(beat1 == beat3 % beat2); - CHECK(beat4 == beat3 % beat4); -} + SECTION("Modulo") + { + const auto beat1 = Beats{0.1}; + const auto beat2 = Beats{0.5}; + const auto beat3 = Beats{0.6}; + const auto beat4 = Beats{0.}; + CHECK(beat1 == beat3 % beat2); + CHECK(beat4 == beat3 % beat4); + } -TEST_CASE("Beats | SizeInByteStream", "[Beats]") -{ - Beats beats{0.5}; - CHECK(8 == sizeInByteStream(beats)); -} + SECTION("SizeInByteStream") + { + Beats beats{0.5}; + CHECK(8 == sizeInByteStream(beats)); + } -TEST_CASE("Beats | RoundtripByteStreamEncoding", "[Beats]") -{ - Beats beats{0.5}; - std::vector<std::uint8_t> bytes(sizeInByteStream(beats)); - const auto end = toNetworkByteStream(beats, begin(bytes)); - const auto result = Beats::fromNetworkByteStream(begin(bytes), end); - CHECK(beats == result.first); + SECTION("RoundtripByteStreamEncoding") + { + Beats beats{0.5}; + std::vector<std::uint8_t> bytes(sizeInByteStream(beats)); + const auto end = toNetworkByteStream(beats, begin(bytes)); + const auto result = Beats::fromNetworkByteStream(begin(bytes), end); + CHECK(beats == result.first); + } } } // namespace link diff --git a/src/ableton/link/tst_CircularFifo.cpp b/src/ableton/link/tst_CircularFifo.cpp deleted file mode 100644 index f9e710b..0000000 --- a/src/ableton/link/tst_CircularFifo.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* Copyright 2016, Ableton AG, Berlin. All rights reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * If you would like to incorporate Link into a proprietary software application, - * please contact <link-devs@ableton.com>. - */ - -#include <ableton/link/CircularFifo.hpp> -#include <ableton/test/CatchWrapper.hpp> - -namespace ableton -{ -namespace link -{ - -TEST_CASE("CircularFifo | PushNPop", "[CircularFifo]") -{ - CircularFifo<int, 2> cf; - - for (int i = 0; i < 2; ++i) - { - CHECK(cf.push(i)); - } - - CHECK(!cf.push(0)); - - for (int i = 0; i < 2; ++i) - { - auto result = cf.pop(); - CHECK(result); - CHECK(*result == i); - } - - CHECK(!cf.pop()); -} - -TEST_CASE("CircularFifo | Wrap", "[CircularFifo]") -{ - CircularFifo<int, 2> cf; - - for (int i = 0; i < 5; ++i) - { - CHECK(cf.push(i)); - auto result = cf.pop(); - CHECK(result); - CHECK(*result == i); - } -} - -TEST_CASE("CircularFifo | IsEmpty", "[CircularFifo]") -{ - CircularFifo<int, 2> cf; - CHECK(cf.isEmpty()); - - CHECK(cf.push(1)); - CHECK(!cf.isEmpty()); - CHECK(cf.push(2)); - CHECK(!cf.isEmpty()); - CHECK(!cf.push(3)); - CHECK(!cf.isEmpty()); - - CHECK(cf.pop()); - CHECK(!cf.isEmpty()); - CHECK(cf.pop()); - CHECK(cf.isEmpty()); - CHECK(!cf.pop()); - CHECK(cf.isEmpty()); -} - -} // namespace link -} // namespace ableton diff --git a/src/ableton/link/tst_ClientSessionTimelines.cpp b/src/ableton/link/tst_ClientSessionTimelines.cpp index 6b87f63..bcc5e21 100644 --- a/src/ableton/link/tst_ClientSessionTimelines.cpp +++ b/src/ableton/link/tst_ClientSessionTimelines.cpp @@ -24,124 +24,125 @@ namespace ableton { namespace link { -namespace -{ -using std::chrono::microseconds; - -// Make the constants non-zero to make sure we're not hiding bugs -const auto b0 = Beats{-1.}; -const auto t0 = microseconds{-1}; -const auto xform = GhostXForm{1., microseconds{-1000000}}; -} // namespace - -TEST_CASE("session->client | UpdatesTempo", "[ClientSessionTimelines]") -{ - const auto curClient = Timeline{Tempo{60.}, b0, t0}; - const auto session = Timeline{Tempo{90.}, b0, xform.hostToGhost(t0)}; - const auto newClient = updateClientTimelineFromSession(curClient, session, t0, xform); - CHECK(newClient.tempo == Tempo{90.}); -} - -TEST_CASE("session->client | PreservesClientBeatMagnitude", "[ClientSessionTimelines]") -{ - const auto curClient = Timeline{Tempo{60.}, b0, t0}; - const auto session = Timeline{Tempo{120.}, Beats{12.32}, microseconds{-123456789}}; - // Transition to the session timeline at t1 - const auto t1 = t0 + microseconds{5000000}; - const auto b1 = curClient.toBeats(t1); - const auto newClient = updateClientTimelineFromSession(curClient, session, t1, xform); - // The new client timeline should have the same magnitude as the old - // one at the transition point - CHECK(newClient.toBeats(t1) == b1); - // At t1 + dt, the new tempo should be in effect - CHECK(newClient.toBeats(t1 + microseconds{1000000}) == b1 + Beats{2.}); -} - -TEST_CASE("session->client | AtDifferentTimesGivesSameResult", "[ClientSessionTimelines]") -{ - const auto curClient = Timeline{Tempo{60.}, b0, t0}; - const auto session = Timeline{Tempo{120.}, Beats{-11.}, microseconds{-12356789}}; - - const auto t1 = t0 + microseconds{5000000}; - const auto t2 = t1 + microseconds{6849353}; - - // Once a given session timeline has been converted to a client - // timeline, converting again from the same session at different - // times should not change the result because the result is colinear - // and the origin should always be anchored at the session's beat 0. - const auto updated1 = updateClientTimelineFromSession(curClient, session, t1, xform); - const auto updated2 = updateClientTimelineFromSession(updated1, session, t2, xform); - CHECK(updated1 == updated2); -} - -TEST_CASE("session->client | EncodesSessionOrigin", "[ClientSessionTimelines]") -{ - const auto curClient = Timeline{Tempo{60.}, b0, t0}; - const auto session = Timeline{Tempo{21.3}, Beats{-421.3}, microseconds{15003240}}; - const auto newClient = - updateClientTimelineFromSession(curClient, session, microseconds{-1345298}, xform); - // The new client timeline's origin should be at beat 0 on the - // session timeline. This is how we encode the session origin in the - // client timeline. - CHECK(xform.hostToGhost(newClient.timeOrigin) == session.fromBeats(Beats{0.})); -} - - -TEST_CASE("client->session | ShiftedForward", "[ClientSessionTimeline]") -{ - const auto session = Timeline{Tempo{60.}, b0, t0}; - auto client = Timeline{Tempo{60.}, Beats{111.}, xform.ghostToHost(t0)}; - client = updateClientTimelineFromSession(client, session, t0, xform); - // Shift the phase one beat forward - client = shiftClientTimeline(client, Beats{1.}); - - const auto newSession = - updateSessionTimelineFromClient(session, client, xform.ghostToHost(t0), xform); - CHECK(newSession.toBeats(t0) == b0 + Beats{1.}); -} - -TEST_CASE("client->session | ShiftedBackward", "[ClientSessionTimeline]") -{ - const auto session = Timeline{Tempo{60.}, b0, t0}; - auto client = Timeline{Tempo{60.}, Beats{983.}, xform.ghostToHost(t0)}; - client = updateClientTimelineFromSession(client, session, t0, xform); - // Shift the phase one beat backward - client = shiftClientTimeline(client, Beats{-1.}); - - const auto newSession = - updateSessionTimelineFromClient(session, client, xform.ghostToHost(t0), xform); - CHECK(newSession.toBeats(t0) == b0 - Beats{1.}); -} - -TEST_CASE("session->client->session | Roundtrip", "[ClientSessionTimeline]") -{ - const auto session = Timeline{Tempo{60.}, b0, t0}; - // Initial client timeline values shouldn't matter - auto client = Timeline{Tempo{213.5}, Beats{432.}, microseconds{5143503}}; - - client = updateClientTimelineFromSession(client, session, t0, xform); - auto newSession = - updateSessionTimelineFromClient(session, client, microseconds{42905944}, xform); - - CHECK(session == newSession); - - // Now verify that modifying the client timeline and then routing it - // through a session update results in the same client timeline. - const auto updateTime = microseconds{-35023900}; - newSession = updateSessionTimelineFromClient(newSession, client, updateTime, xform); - const auto newClient = - updateClientTimelineFromSession(client, newSession, updateTime, xform); - - CHECK(client == newClient); -} -TEST_CASE("shiftClientTimelineOrigin | Shift", "[ClientSessionTimeline]") +TEST_CASE("ClientSessionTimelines") { - const auto shift = Beats{1.1}; - const auto timeline = Timeline{Tempo{60.}, b0, t0}; - CHECK(shiftClientTimeline(timeline, shift).toBeats(t0) == timeline.toBeats(t0) + shift); - CHECK( - shiftClientTimeline(timeline, -shift).toBeats(t0) == timeline.toBeats(t0) - shift); + using std::chrono::microseconds; + + // Make the constants non-zero to make sure we're not hiding bugs + const auto b0 = Beats{-1.}; + const auto t0 = microseconds{-1}; + const auto xform = GhostXForm{1., microseconds{-1000000}}; + + SECTION("Session->Client | UpdatesTempo") + { + const auto curClient = Timeline{Tempo{60.}, b0, t0}; + const auto session = Timeline{Tempo{90.}, b0, xform.hostToGhost(t0)}; + const auto newClient = updateClientTimelineFromSession(curClient, session, t0, xform); + CHECK(newClient.tempo == Tempo{90.}); + } + + SECTION("Session->Client | PreservesClientBeatMagnitude") + { + const auto curClient = Timeline{Tempo{60.}, b0, t0}; + const auto session = Timeline{Tempo{120.}, Beats{12.32}, microseconds{-123456789}}; + // Transition to the session timeline at t1 + const auto t1 = t0 + microseconds{5000000}; + const auto b1 = curClient.toBeats(t1); + const auto newClient = updateClientTimelineFromSession(curClient, session, t1, xform); + // The new client timeline should have the same magnitude as the old + // one at the transition point + CHECK(newClient.toBeats(t1) == b1); + // At t1 + dt, the new tempo should be in effect + CHECK(newClient.toBeats(t1 + microseconds{1000000}) == b1 + Beats{2.}); + } + + SECTION("Session->Client | AtDifferentTimesGivesSameResult") + { + const auto curClient = Timeline{Tempo{60.}, b0, t0}; + const auto session = Timeline{Tempo{120.}, Beats{-11.}, microseconds{-12356789}}; + + const auto t1 = t0 + microseconds{5000000}; + const auto t2 = t1 + microseconds{6849353}; + + // Once a given session timeline has been converted to a client + // timeline, converting again from the same session at different + // times should not change the result because the result is colinear + // and the origin should always be anchored at the session's beat 0. + const auto updated1 = updateClientTimelineFromSession(curClient, session, t1, xform); + const auto updated2 = updateClientTimelineFromSession(updated1, session, t2, xform); + CHECK(updated1 == updated2); + } + + SECTION("Session->Client | EncodesSessionOrigin") + { + const auto curClient = Timeline{Tempo{60.}, b0, t0}; + const auto session = Timeline{Tempo{21.3}, Beats{-421.3}, microseconds{15003240}}; + const auto newClient = + updateClientTimelineFromSession(curClient, session, microseconds{-1345298}, xform); + // The new client timeline's origin should be at beat 0 on the + // session timeline. This is how we encode the session origin in the + // client timeline. + CHECK(xform.hostToGhost(newClient.timeOrigin) == session.fromBeats(Beats{0.})); + } + + SECTION("Client->Session | ShiftedForward") + { + const auto session = Timeline{Tempo{60.}, b0, t0}; + auto client = Timeline{Tempo{60.}, Beats{111.}, xform.ghostToHost(t0)}; + client = updateClientTimelineFromSession(client, session, t0, xform); + // Shift the phase one beat forward + client = shiftClientTimeline(client, Beats{1.}); + + const auto newSession = + updateSessionTimelineFromClient(session, client, xform.ghostToHost(t0), xform); + CHECK(newSession.toBeats(t0) == b0 + Beats{1.}); + } + + SECTION("Client->Session | ShiftedBackward") + { + const auto session = Timeline{Tempo{60.}, b0, t0}; + auto client = Timeline{Tempo{60.}, Beats{983.}, xform.ghostToHost(t0)}; + client = updateClientTimelineFromSession(client, session, t0, xform); + // Shift the phase one beat backward + client = shiftClientTimeline(client, Beats{-1.}); + + const auto newSession = + updateSessionTimelineFromClient(session, client, xform.ghostToHost(t0), xform); + CHECK(newSession.toBeats(t0) == b0 - Beats{1.}); + } + + SECTION("Session->Client->Session | Roundtrip") + { + const auto session = Timeline{Tempo{60.}, b0, t0}; + // Initial client timeline values shouldn't matter + auto client = Timeline{Tempo{213.5}, Beats{432.}, microseconds{5143503}}; + + client = updateClientTimelineFromSession(client, session, t0, xform); + auto newSession = + updateSessionTimelineFromClient(session, client, microseconds{42905944}, xform); + + CHECK(session == newSession); + + // Now verify that modifying the client timeline and then routing it + // through a session update results in the same client timeline. + const auto updateTime = microseconds{-35023900}; + newSession = updateSessionTimelineFromClient(newSession, client, updateTime, xform); + const auto newClient = + updateClientTimelineFromSession(client, newSession, updateTime, xform); + + CHECK(client == newClient); + } + + SECTION("ShiftClientTimelineOrigin") + { + const auto shift = Beats{1.1}; + const auto timeline = Timeline{Tempo{60.}, b0, t0}; + CHECK( + shiftClientTimeline(timeline, shift).toBeats(t0) == timeline.toBeats(t0) + shift); + CHECK( + shiftClientTimeline(timeline, -shift).toBeats(t0) == timeline.toBeats(t0) - shift); + } } } // namespace link diff --git a/src/ableton/link/tst_Controller.cpp b/src/ableton/link/tst_Controller.cpp index 4439cf2..b94fcd4 100644 --- a/src/ableton/link/tst_Controller.cpp +++ b/src/ableton/link/tst_Controller.cpp @@ -64,6 +64,15 @@ private: struct MockIoContext { + MockIoContext() + { + } + + template <typename ExceptionHandler> + MockIoContext(ExceptionHandler) + { + } + template <std::size_t BufferSize> struct Socket { @@ -140,17 +149,6 @@ struct MockIoContext { handler(); } - - MockIoContext clone() const - { - return {}; - } - - template <typename ExceptionHandler> - MockIoContext clone(ExceptionHandler) const - { - return {}; - } }; using MockController = Controller<PeerCountCallback, @@ -160,8 +158,6 @@ using MockController = Controller<PeerCountCallback, platforms::stl::Random, MockIoContext>; -const auto kAnyBeatTime = Beats{5.}; - const auto kAnyTime = std::chrono::microseconds{6}; struct TempoClientCallback @@ -191,8 +187,8 @@ void testSetAndGetClientState( using namespace std::chrono; auto clock = MockClock{}; - MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, - clock, util::injectVal(MockIoContext{})); + MockController controller( + Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, clock); clock.advance(microseconds{1}); const auto initialTimeline = @@ -261,7 +257,7 @@ void testCallbackInvocation(SetClientStateFunctionT setClientState) auto tempoCallback = TempoClientCallback{}; auto startStopStateCallback = StartStopStateClientCallback{}; MockController controller(Tempo{100.0}, [](std::size_t) {}, std::ref(tempoCallback), - std::ref(startStopStateCallback), clock, util::injectVal(MockIoContext{})); + std::ref(startStopStateCallback), clock); clock.advance(microseconds{1}); @@ -298,147 +294,151 @@ void testCallbackInvocation(SetClientStateFunctionT setClientState) } // namespace - -TEST_CASE("Controller | ConstructOptimistically", "[Controller]") -{ - MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, - MockClock{}, util::injectVal(MockIoContext{})); - - CHECK(!controller.isEnabled()); - CHECK(!controller.isStartStopSyncEnabled()); - CHECK(0 == controller.numPeers()); - const auto tl = controller.clientState().timeline; - CHECK(Tempo{100.0} == tl.tempo); -} - -TEST_CASE("Controller | ConstructWithInvalidTempo", "[Controller]") +TEST_CASE("Controller") { - MockController controllerLowTempo(Tempo{1.0}, [](std::size_t) {}, [](Tempo) {}, - [](bool) {}, MockClock{}, util::injectVal(MockIoContext{})); - const auto tlLow = controllerLowTempo.clientState().timeline; - CHECK(Tempo{20.0} == tlLow.tempo); - - MockController controllerHighTempo(Tempo{100000.0}, [](std::size_t) {}, [](Tempo) {}, - [](bool) {}, MockClock{}, util::injectVal(MockIoContext{})); - const auto tlHigh = controllerHighTempo.clientState().timeline; - CHECK(Tempo{999.0} == tlHigh.tempo); -} + SECTION("ConstructOptimistically") + { + MockController controller( + Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, MockClock{}); + + CHECK(!controller.isEnabled()); + CHECK(!controller.isStartStopSyncEnabled()); + CHECK(0 == controller.numPeers()); + const auto tl = controller.clientState().timeline; + CHECK(Tempo{100.0} == tl.tempo); + } -TEST_CASE("Controller | EnableDisable", "[Controller]") -{ - MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, - MockClock{}, util::injectVal(MockIoContext{})); + SECTION("ConstructWithInvalidTempo") + { + MockController controllerLowTempo( + Tempo{1.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, MockClock{}); + const auto tlLow = controllerLowTempo.clientState().timeline; + CHECK(Tempo{20.0} == tlLow.tempo); + + MockController controllerHighTempo( + Tempo{100000.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, MockClock{}); + const auto tlHigh = controllerHighTempo.clientState().timeline; + CHECK(Tempo{999.0} == tlHigh.tempo); + } - controller.enable(true); - CHECK(controller.isEnabled()); - controller.enable(false); - CHECK(!controller.isEnabled()); -} + SECTION("EnableDisable") + { + MockController controller( + Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, MockClock{}); -TEST_CASE("Controller | EnableDisableStartStopSync", "[Controller]") -{ - MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, - MockClock{}, util::injectVal(MockIoContext{})); + controller.enable(true); + CHECK(controller.isEnabled()); + controller.enable(false); + CHECK(!controller.isEnabled()); + } - controller.enableStartStopSync(true); - CHECK(controller.isStartStopSyncEnabled()); - controller.enableStartStopSync(false); - CHECK(!controller.isStartStopSyncEnabled()); -} + SECTION("EnableDisableStartStopSync") + { + MockController controller( + Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, MockClock{}); -TEST_CASE("Controller | SetAndGetClientStateThreadSafe", "[Controller]") -{ - testSetAndGetClientState( - [](MockController& controller, IncomingClientState clientState) { - controller.setClientState(clientState); - }, - [](MockController& controller) { return controller.clientState(); }); -} + controller.enableStartStopSync(true); + CHECK(controller.isStartStopSyncEnabled()); + controller.enableStartStopSync(false); + CHECK(!controller.isStartStopSyncEnabled()); + } -TEST_CASE("Controller | SetAndGetClientStateRealtimeSafe", "[Controller]") -{ - testSetAndGetClientState( - [](MockController& controller, IncomingClientState clientState) { - controller.setClientStateRtSafe(clientState); - }, - [](MockController& controller) { return controller.clientStateRtSafe(); }); -} + SECTION("SetAndGetClientStateThreadSafe") + { + testSetAndGetClientState( + [](MockController& controller, IncomingClientState clientState) { + controller.setClientState(clientState); + }, + [](MockController& controller) { return controller.clientState(); }); + } -TEST_CASE("Controller | SetClientStateRealtimeSafeAndGetItThreadSafe", "[Controller]") -{ - testSetAndGetClientState( - [](MockController& controller, IncomingClientState clientState) { - controller.setClientStateRtSafe(clientState); - }, - [](MockController& controller) { return controller.clientState(); }); -} + SECTION("SetAndGetClientStateRealtimeSafe") + { + testSetAndGetClientState( + [](MockController& controller, IncomingClientState clientState) { + controller.setClientStateRtSafe(clientState); + }, + [](MockController& controller) { return controller.clientStateRtSafe(); }); + } -TEST_CASE("Controller | SetClientStateThreadSafeAndGetItRealtimeSafe", "[Controller]") -{ - testSetAndGetClientState( - [](MockController& controller, IncomingClientState clientState) { - controller.setClientState(clientState); - }, - [](MockController& controller) { - MockClock{}.advance(seconds{2}); - return controller.clientStateRtSafe(); - }); -} + SECTION("SetClientStateRealtimeSafeAndGetItThreadSafe") + { + testSetAndGetClientState( + [](MockController& controller, IncomingClientState clientState) { + controller.setClientStateRtSafe(clientState); + }, + [](MockController& controller) { return controller.clientState(); }); + } -TEST_CASE("Controller | CallbacksCalledBySettingClientStateThreadSafe", "[Controller]") -{ - testCallbackInvocation([](MockController& controller, IncomingClientState clientState) { - controller.setClientState(clientState); - }); -} + SECTION("SetClientStateThreadSafeAndGetItRealtimeSafe") + { + testSetAndGetClientState( + [](MockController& controller, IncomingClientState clientState) { + controller.setClientState(clientState); + }, + [](MockController& controller) { + MockClock{}.advance(seconds{2}); + return controller.clientStateRtSafe(); + }); + } -TEST_CASE("Controller | CallbacksCalledBySettingClientStateRealtimeSafe", "[Controller]") -{ - testCallbackInvocation([](MockController& controller, IncomingClientState clientState) { - controller.setClientStateRtSafe(clientState); - }); -} + SECTION("CallbacksCalledBySettingClientStateThreadSafe") + { + testCallbackInvocation( + [](MockController& controller, IncomingClientState clientState) { + controller.setClientState(clientState); + }); + } -TEST_CASE("Controller | GetClientStateRtSafeGracePeriod", "[Controller]") -{ - using namespace std::chrono; + SECTION("CallbacksCalledBySettingClientStateRealtimeSafe") + { + testCallbackInvocation( + [](MockController& controller, IncomingClientState clientState) { + controller.setClientStateRtSafe(clientState); + }); + } - auto clock = MockClock{}; - auto tempoCallback = TempoClientCallback{}; - auto startStopStateCallback = StartStopStateClientCallback{}; - MockController controller(Tempo{100.0}, [](std::size_t) {}, std::ref(tempoCallback), - std::ref(startStopStateCallback), clock, util::injectVal(MockIoContext{})); - controller.enable(true); + SECTION("GetClientStateRtSafeGracePeriod") + { + using namespace std::chrono; - clock.advance(microseconds{1}); - const auto initialTimeline = - Optional<Timeline>{Timeline{Tempo{50.}, Beats{0.}, clock.micros()}}; - const auto initialStartStopState = - Optional<ClientStartStopState>{ClientStartStopState{true, kAnyTime, clock.micros()}}; - const auto initialState = - IncomingClientState{initialTimeline, initialStartStopState, clock.micros()}; + auto clock = MockClock{}; + auto tempoCallback = TempoClientCallback{}; + auto startStopStateCallback = StartStopStateClientCallback{}; + MockController controller(Tempo{100.0}, [](std::size_t) {}, std::ref(tempoCallback), + std::ref(startStopStateCallback), clock); + controller.enable(true); - controller.setClientStateRtSafe( - {initialTimeline, initialStartStopState, clock.micros()}); - REQUIRE(initialState == controller.clientState()); - REQUIRE(initialState == controller.clientStateRtSafe()); + clock.advance(microseconds{1}); + const auto initialTimeline = + Optional<Timeline>{Timeline{Tempo{50.}, Beats{0.}, clock.micros()}}; + const auto initialStartStopState = Optional<ClientStartStopState>{ + ClientStartStopState{true, kAnyTime, clock.micros()}}; + const auto initialState = + IncomingClientState{initialTimeline, initialStartStopState, clock.micros()}; + + controller.setClientStateRtSafe( + {initialTimeline, initialStartStopState, clock.micros()}); + REQUIRE(initialState == controller.clientState()); + REQUIRE(initialState == controller.clientStateRtSafe()); - clock.advance(microseconds{1}); - const auto newTimeline = - Optional<Timeline>{Timeline{Tempo{70.}, Beats{1.}, clock.micros()}}; - const auto newStartStopState = - Optional<ClientStartStopState>{ClientStartStopState{false, kAnyTime, clock.micros()}}; - const auto newState = - IncomingClientState{newTimeline, newStartStopState, clock.micros()}; + clock.advance(microseconds{1}); + const auto newTimeline = + Optional<Timeline>{Timeline{Tempo{70.}, Beats{1.}, clock.micros()}}; + const auto newStartStopState = Optional<ClientStartStopState>{ + ClientStartStopState{false, kAnyTime, clock.micros()}}; + const auto newState = + IncomingClientState{newTimeline, newStartStopState, clock.micros()}; - controller.setClientState({newTimeline, newStartStopState, clock.micros()}); - clock.advance(milliseconds{500}); - CHECK(newState == controller.clientState()); - CHECK(initialState == controller.clientStateRtSafe()); + controller.setClientState({newTimeline, newStartStopState, clock.micros()}); + clock.advance(milliseconds{500}); + CHECK(newState == controller.clientState()); + CHECK(initialState == controller.clientStateRtSafe()); - clock.advance(milliseconds{500}); - CHECK(newState == controller.clientState()); - CHECK(newState == controller.clientStateRtSafe()); + clock.advance(milliseconds{500}); + CHECK(newState == controller.clientState()); + CHECK(newState == controller.clientStateRtSafe()); + } } } // namespace link diff --git a/src/ableton/link/tst_HostTimeFilter.cpp b/src/ableton/link/tst_HostTimeFilter.cpp index e1715bb..0e673ad 100644 --- a/src/ableton/link/tst_HostTimeFilter.cpp +++ b/src/ableton/link/tst_HostTimeFilter.cpp @@ -43,40 +43,41 @@ struct MockClock std::chrono::microseconds time; }; -using Filter = ableton::link::HostTimeFilter<MockClock>; - -TEST_CASE("HostTimeFilter | OneValue", "[HostTimeFilter]") +TEST_CASE("HostTimeFilter") { + using Filter = ableton::link::HostTimeFilter<MockClock>; Filter filter; - const auto ht = filter.sampleTimeToHostTime(5); - CHECK(0 == ht.count()); -} -TEST_CASE("HostTimeFilter | MultipleValues", "[HostTimeFilter]") -{ - Filter filter; - const auto numValues = 600; - auto ht = std::chrono::microseconds(0); - - for (int i = 0; i <= numValues; ++i) + SECTION("OneValue") { - ht = filter.sampleTimeToHostTime(i); + const auto ht = filter.sampleTimeToHostTime(5); + CHECK(0 == ht.count()); } - CHECK(numValues == ht.count()); -} + SECTION("MultipleValues") + { + const auto numValues = 600; + auto ht = std::chrono::microseconds(0); -TEST_CASE("HostTimeFilter | Reset", "[HostTimeFilter]") -{ - Filter filter; - auto ht = filter.sampleTimeToHostTime(0); - ht = filter.sampleTimeToHostTime(-230); - ht = filter.sampleTimeToHostTime(40); - REQUIRE(2 != ht.count()); + for (int i = 0; i <= numValues; ++i) + { + ht = filter.sampleTimeToHostTime(i); + } + + CHECK(numValues == ht.count()); + } - filter.reset(); - ht = filter.sampleTimeToHostTime(0); - CHECK(3 == ht.count()); + SECTION("Reset") + { + auto ht = filter.sampleTimeToHostTime(0); + ht = filter.sampleTimeToHostTime(-230); + ht = filter.sampleTimeToHostTime(40); + REQUIRE(2 != ht.count()); + + filter.reset(); + ht = filter.sampleTimeToHostTime(0); + CHECK(3 == ht.count()); + } } } // namespace link diff --git a/src/ableton/link/tst_LinearRegression.cpp b/src/ableton/link/tst_LinearRegression.cpp index 07c9b7f..3cd73f6 100644 --- a/src/ableton/link/tst_LinearRegression.cpp +++ b/src/ableton/link/tst_LinearRegression.cpp @@ -27,50 +27,46 @@ namespace ableton namespace link { -using Vector = std::vector<std::pair<double, double>>; - -TEST_CASE("LinearRegression | EmptyVector", "[LinearRegression]") -{ - Vector data; - const auto result = linearRegression(data.begin(), data.end()); - CHECK(0 == Approx(result.first)); -} - -TEST_CASE("LinearRegression | OnePoint", "[LinearRegression]") +TEST_CASE("LinearRegression") { + using Vector = std::vector<std::pair<double, double>>; using Array = std::array<std::pair<double, double>, 1>; - Array data; - data[0] = {{}, {}}; - const auto result = linearRegression(data.begin(), data.end()); - CHECK(0 == Approx(result.first)); - CHECK(0 == Approx(result.second)); -} -TEST_CASE("LinearRegression | TwoPoints", "[LinearRegression]") -{ - Vector data; - data.emplace_back(0.0, 0.0); - data.emplace_back(666666.6, 66666.6); + SECTION("OnePoint") + { + Array data; + data[0] = {0., 0.}; + const auto result = linearRegression(data.begin(), data.end()); + CHECK(0 == Approx(result.first)); + CHECK(0 == Approx(result.second)); + } - const auto result = linearRegression(data.begin(), data.end()); - CHECK(0.1 == Approx(result.first)); - CHECK(0.0 == Approx(result.second)); -} + SECTION("TwoPoints") + { + Vector data; + data.emplace_back(0.0, 0.0); + data.emplace_back(666666.6, 66666.6); -TEST_CASE("LinearRegression | 10000Points", "[LinearRegression]") -{ - Vector data; - const double slope = -0.2; - const double intercept = -357.53456; + const auto result = linearRegression(data.begin(), data.end()); + CHECK(0.1 == Approx(result.first)); + CHECK(0.0 == Approx(result.second)); + } - for (int i = 1; i < 10000; ++i) + SECTION("10000Points") { - data.emplace_back(i, i * slope + intercept); - } + Vector data; + const double slope = -0.2; + const double intercept = -357.53456; + + for (int i = 1; i < 10000; ++i) + { + data.emplace_back(i, i * slope + intercept); + } - const auto result = linearRegression(data.begin(), data.end()); - CHECK(slope == Approx(result.first)); - CHECK(intercept == Approx(result.second)); + const auto result = linearRegression(data.begin(), data.end()); + CHECK(slope == Approx(result.first)); + CHECK(intercept == Approx(result.second)); + } } } // namespace link diff --git a/src/ableton/link/tst_Measurement.cpp b/src/ableton/link/tst_Measurement.cpp index 89edf1d..6a23f68 100644 --- a/src/ableton/link/tst_Measurement.cpp +++ b/src/ableton/link/tst_Measurement.cpp @@ -74,10 +74,10 @@ struct TFixture { TFixture() : mMeasurement(mStateQuery(), - [](std::vector<std::pair<double, double>>) {}, + [](std::vector<double>) {}, {}, MockClock{}, - MockIoContext{}) + util::Injected<MockIoContext>(MockIoContext{})) { } @@ -109,70 +109,67 @@ struct TFixture } // anonymous namespace -TEST_CASE("PeerMeasurement | SendPingsOnConstruction", "[PeerMeasurement]") -{ - TFixture fixture; - CHECK(1 == fixture.socket().sentMessages.size()); - - const auto messageBuffer = fixture.socket().sentMessages[0].first; - const auto result = - v1::parseMessageHeader(std::begin(messageBuffer), std::end(messageBuffer)); - const auto& header = result.first; - - std::chrono::microseconds gt{0}; - std::chrono::microseconds ht{0}; - discovery::parsePayload<GHostTime, HostTime>(result.second, std::end(messageBuffer), - [>](GHostTime ghostTime) { gt = std::move(ghostTime.time); }, - [&ht](HostTime hostTime) { ht = std::move(hostTime.time); }); - - CHECK(v1::kPing == header.messageType); - CHECK(std::chrono::microseconds{4} == ht); - CHECK(std::chrono::microseconds{0} == gt); -} - -TEST_CASE("PeerMeasurement | ReceiveInitPong", "[PeerMeasurement]") +TEST_CASE("PeerMeasurement") { using Micros = std::chrono::microseconds; TFixture fixture; + const auto endpoint = + asio::ip::udp::endpoint(asio::ip::address_v4::from_string("127.0.0.1"), 8888); - const auto id = SessionMembership{fixture.mStateQuery.mState.nodeState.sessionId}; - const auto ht = HostTime{Micros(0)}; - const auto gt = GHostTime{Micros(0)}; - const auto payload = discovery::makePayload(id, gt, ht); + SECTION("SendPingsOnConstruction") + { + CHECK(1 == fixture.socket().sentMessages.size()); + + const auto messageBuffer = fixture.socket().sentMessages[0].first; + const auto result = + v1::parseMessageHeader(std::begin(messageBuffer), std::end(messageBuffer)); + const auto& header = result.first; + + std::chrono::microseconds gt{0}; + std::chrono::microseconds ht{0}; + discovery::parsePayload<GHostTime, HostTime>(result.second, std::end(messageBuffer), + [>](GHostTime ghostTime) { gt = std::move(ghostTime.time); }, + [&ht](HostTime hostTime) { ht = std::move(hostTime.time); }); + + CHECK(v1::kPing == header.messageType); + CHECK(std::chrono::microseconds{4} == ht); + CHECK(std::chrono::microseconds{0} == gt); + } - v1::MessageBuffer buffer; - const auto msgBegin = std::begin(buffer); - const auto msgEnd = v1::pongMessage(payload, msgBegin); + SECTION("ReceiveInitPong") + { + const auto id = SessionMembership{fixture.mStateQuery.mState.nodeState.sessionId}; + const auto ht = HostTime{Micros(0)}; + const auto gt = GHostTime{Micros(0)}; + const auto payload = discovery::makePayload(id, gt, ht); - const auto endpoint = - asio::ip::udp::endpoint(asio::ip::address_v4::from_string("127.0.0.1"), 8888); - fixture.socket().incomingMessage(endpoint, msgBegin, msgEnd); + v1::MessageBuffer buffer; + const auto msgBegin = std::begin(buffer); + const auto msgEnd = v1::pongMessage(payload, msgBegin); - CHECK(2 == fixture.socket().sentMessages.size()); - CHECK(0 == fixture.mMeasurement.mpImpl->mData.size()); -} + fixture.socket().incomingMessage(endpoint, msgBegin, msgEnd); -TEST_CASE("PeerMeasurement | ReceivePong", "[PeerMeasurement]") -{ - using Micros = std::chrono::microseconds; - TFixture fixture; + CHECK(2 == fixture.socket().sentMessages.size()); + CHECK(0 == fixture.mMeasurement.mpImpl->mData.size()); + } - const auto id = SessionMembership{fixture.mStateQuery.mState.nodeState.sessionId}; - const auto ht = HostTime{Micros(2)}; - const auto gt = GHostTime{Micros(3)}; - const auto pgt = PrevGHostTime{Micros(1)}; - const auto payload = discovery::makePayload(id, gt, ht, pgt); + SECTION("ReceivePong") + { + const auto id = SessionMembership{fixture.mStateQuery.mState.nodeState.sessionId}; + const auto ht = HostTime{Micros(2)}; + const auto gt = GHostTime{Micros(3)}; + const auto pgt = PrevGHostTime{Micros(1)}; + const auto payload = discovery::makePayload(id, gt, ht, pgt); - v1::MessageBuffer buffer; - const auto msgBegin = std::begin(buffer); - const auto msgEnd = v1::pongMessage(payload, msgBegin); + v1::MessageBuffer buffer; + const auto msgBegin = std::begin(buffer); + const auto msgEnd = v1::pongMessage(payload, msgBegin); - const auto endpoint = - asio::ip::udp::endpoint(asio::ip::address_v4::from_string("127.0.0.1"), 8888); - fixture.socket().incomingMessage(endpoint, msgBegin, msgEnd); + fixture.socket().incomingMessage(endpoint, msgBegin, msgEnd); - CHECK(2 == fixture.socket().sentMessages.size()); - CHECK(2 == fixture.mMeasurement.mpImpl->mData.size()); + CHECK(2 == fixture.socket().sentMessages.size()); + CHECK(2 == fixture.mMeasurement.mpImpl->mData.size()); + } } } // namespace link diff --git a/src/ableton/link/tst_Median.cpp b/src/ableton/link/tst_Median.cpp new file mode 100644 index 0000000..9e4342a --- /dev/null +++ b/src/ableton/link/tst_Median.cpp @@ -0,0 +1,66 @@ +/* Copyright 2021, Ableton AG, Berlin. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * If you would like to incorporate Link into a proprietary software application, + * please contact <link-devs@ableton.com>. + */ + +#include <ableton/link/Median.hpp> +#include <ableton/test/CatchWrapper.hpp> +#include <array> +#include <vector> + +namespace ableton +{ +namespace link +{ + +TEST_CASE("Median") +{ + using Vector = std::vector<double>; + + SECTION("ArrayWithThreePoints") + { + auto data = std::array<double, 3>{{5., 0., 6.}}; + CHECK(5 == Approx(median(data.begin(), data.end()))); + } + + SECTION("VectorWithFourPoints") + { + auto data = Vector{{0., 2., 1., 3., 2.}}; + CHECK(2.0 == Approx(median(data.begin(), data.end()))); + } + + SECTION("VectorWithFivePoints") + { + auto data = Vector{{0., 1., 3., 2.}}; + CHECK(1.5 == Approx(median(data.begin(), data.end()))); + } + + SECTION("VectorWith9999Points") + { + Vector data; + const double slope = -0.2; + const double intercept = -357.53456; + for (int i = 1; i < 10000; ++i) + { + data.emplace_back(i * slope + intercept); + } + CHECK(slope * 5000 + intercept == Approx(median(data.begin(), data.end()))); + } +} + +} // namespace link +} // namespace ableton diff --git a/src/ableton/link/tst_Peers.cpp b/src/ableton/link/tst_Peers.cpp index a8060f5..e8bfdac 100644 --- a/src/ableton/link/tst_Peers.cpp +++ b/src/ableton/link/tst_Peers.cpp @@ -61,25 +61,6 @@ struct SessionStartStopStateCallback std::vector<std::pair<SessionId, StartStopState>> sessionStartStopStates; }; -const auto fooPeer = - PeerState{{NodeId::random<Random>(), NodeId::random<Random>(), - Timeline{Tempo{60.}, Beats{1.}, std::chrono::microseconds{1234}}, - StartStopState{false, Beats{0.}, std::chrono::microseconds{2345}}}, - {}}; - -const auto barPeer = - PeerState{{NodeId::random<Random>(), NodeId::random<Random>(), - Timeline{Tempo{120.}, Beats{10.}, std::chrono::microseconds{500}}, {}}, - {}}; - -const auto bazPeer = - PeerState{{NodeId::random<Random>(), NodeId::random<Random>(), - Timeline{Tempo{100.}, Beats{4.}, std::chrono::microseconds{100}}, {}}, - {}}; - -const auto gateway1 = asio::ip::address::from_string("123.123.123.123"); -const auto gateway2 = asio::ip::address::from_string("210.210.210.210"); - using PeerVector = std::vector<typename Peers<test::serial_io::Context, SessionMembershipCallback, SessionTimelineCallback, @@ -105,114 +86,116 @@ void expectStartStopStates( } // anonymous namespace -TEST_CASE("Peers | EmptySessionPeersAfterInit", "[Peers]") +TEST_CASE("Peers") { - test::serial_io::Fixture io; - auto peers = makePeers(util::injectVal(io.makeIoContext()), SessionMembershipCallback{}, - SessionTimelineCallback{}, SessionStartStopStateCallback{}); - io.flush(); - expectPeers({}, peers.sessionPeers(fooPeer.sessionId())); -} + const auto fooPeer = + PeerState{{NodeId::random<Random>(), NodeId::random<Random>(), + Timeline{Tempo{60.}, Beats{1.}, std::chrono::microseconds{1234}}, + StartStopState{false, Beats{0.}, std::chrono::microseconds{2345}}}, + {}}; + + const auto barPeer = + PeerState{{NodeId::random<Random>(), NodeId::random<Random>(), + Timeline{Tempo{120.}, Beats{10.}, std::chrono::microseconds{500}}, {}}, + {}}; + + const auto bazPeer = + PeerState{{NodeId::random<Random>(), NodeId::random<Random>(), + Timeline{Tempo{100.}, Beats{4.}, std::chrono::microseconds{100}}, {}}, + {}}; + + const auto gateway1 = asio::ip::address::from_string("123.123.123.123"); + const auto gateway2 = asio::ip::address::from_string("210.210.210.210"); -TEST_CASE("Peers | AddAndFindPeer", "[Peers]") -{ auto membership = SessionMembershipCallback{}; auto sessions = SessionTimelineCallback{}; auto startStops = SessionStartStopStateCallback{}; + test::serial_io::Fixture io; + auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership), std::ref(sessions), std::ref(startStops)); - auto observer = makeGatewayObserver(peers, gateway1); - sawPeer(observer, fooPeer); - io.flush(); + SECTION("EmptySessionPeersAfterInit") + { + io.flush(); + expectPeers({}, peers.sessionPeers(fooPeer.sessionId())); + } - expectPeers({{fooPeer, gateway1}}, peers.sessionPeers(fooPeer.sessionId())); - CHECK(1u == membership.calls); - expectSessionTimelines({make_pair(fooPeer.sessionId(), fooPeer.timeline())}, sessions); - expectStartStopStates( - {make_pair(fooPeer.sessionId(), fooPeer.startStopState())}, startStops); -} + SECTION("AddAndFindPeer") + { + auto observer = makeGatewayObserver(peers, gateway1); -TEST_CASE("Peers | AddAndRemovePeer", "[Peers]") -{ - auto membership = SessionMembershipCallback{}; - test::serial_io::Fixture io; - auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership), - SessionTimelineCallback{}, SessionStartStopStateCallback{}); - auto observer = makeGatewayObserver(peers, gateway1); + sawPeer(observer, fooPeer); + io.flush(); - sawPeer(observer, fooPeer); - peerLeft(observer, fooPeer.ident()); - io.flush(); + expectPeers({{fooPeer, gateway1}}, peers.sessionPeers(fooPeer.sessionId())); + CHECK(1u == membership.calls); + expectSessionTimelines( + {make_pair(fooPeer.sessionId(), fooPeer.timeline())}, sessions); + expectStartStopStates( + {make_pair(fooPeer.sessionId(), fooPeer.startStopState())}, startStops); + } - expectPeers({}, peers.sessionPeers(fooPeer.sessionId())); - CHECK(2u == membership.calls); -} + SECTION("AddAndRemovePeer") + { + auto observer = makeGatewayObserver(peers, gateway1); -TEST_CASE("Peers | AddTwoPeersRemoveOne", "[Peers]") -{ - auto membership = SessionMembershipCallback{}; - auto sessions = SessionTimelineCallback{}; - auto startStops = SessionStartStopStateCallback{}; - test::serial_io::Fixture io; - auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership), - std::ref(sessions), std::ref(startStops)); - auto observer = makeGatewayObserver(peers, gateway1); + sawPeer(observer, fooPeer); + peerLeft(observer, fooPeer.ident()); + io.flush(); - sawPeer(observer, fooPeer); - sawPeer(observer, barPeer); - peerLeft(observer, fooPeer.ident()); - io.flush(); + expectPeers({}, peers.sessionPeers(fooPeer.sessionId())); + CHECK(2u == membership.calls); + } - expectPeers({}, peers.sessionPeers(fooPeer.sessionId())); - expectPeers({{barPeer, gateway1}}, peers.sessionPeers(barPeer.sessionId())); - CHECK(3u == membership.calls); -} + SECTION("AddTwoPeersRemoveOne") + { + auto observer = makeGatewayObserver(peers, gateway1); -TEST_CASE("Peers | AddThreePeersTwoOnSameGateway", "[Peers]") -{ - auto membership = SessionMembershipCallback{}; - auto sessions = SessionTimelineCallback{}; - auto startStops = SessionStartStopStateCallback{}; - test::serial_io::Fixture io; - auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership), - std::ref(sessions), std::ref(startStops)); - auto observer1 = makeGatewayObserver(peers, gateway1); - auto observer2 = makeGatewayObserver(peers, gateway2); - - sawPeer(observer1, fooPeer); - sawPeer(observer2, fooPeer); - sawPeer(observer1, barPeer); - sawPeer(observer1, bazPeer); - io.flush(); - - expectPeers( - {{fooPeer, gateway1}, {fooPeer, gateway2}}, peers.sessionPeers(fooPeer.sessionId())); - CHECK(3 == membership.calls); -} + sawPeer(observer, fooPeer); + sawPeer(observer, barPeer); + peerLeft(observer, fooPeer.ident()); + io.flush(); -TEST_CASE("Peers | CloseGateway", "[Peers]") -{ - auto membership = SessionMembershipCallback{}; - auto sessions = SessionTimelineCallback{}; - auto startStops = SessionStartStopStateCallback{}; - test::serial_io::Fixture io; - auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership), - std::ref(sessions), std::ref(startStops)); - auto observer1 = makeGatewayObserver(peers, gateway1); + expectPeers({}, peers.sessionPeers(fooPeer.sessionId())); + expectPeers({{barPeer, gateway1}}, peers.sessionPeers(barPeer.sessionId())); + CHECK(3u == membership.calls); + } + + SECTION("AddThreePeersTwoOnSameGateway") { - // The observer will close the gateway when it goes out of scope + + auto observer1 = makeGatewayObserver(peers, gateway1); auto observer2 = makeGatewayObserver(peers, gateway2); - sawPeer(observer2, fooPeer); - sawPeer(observer2, barPeer); + sawPeer(observer1, fooPeer); - sawPeer(observer2, bazPeer); + sawPeer(observer2, fooPeer); + sawPeer(observer1, barPeer); + sawPeer(observer1, bazPeer); + io.flush(); + + expectPeers({{fooPeer, gateway1}, {fooPeer, gateway2}}, + peers.sessionPeers(fooPeer.sessionId())); + CHECK(3 == membership.calls); } - io.flush(); - expectPeers({{fooPeer, gateway1}}, peers.sessionPeers(fooPeer.sessionId())); - CHECK(4 == membership.calls); + SECTION("CloseGateway") + { + auto observer1 = makeGatewayObserver(peers, gateway1); + { + // The observer will close the gateway when it goes out of scope + auto observer2 = makeGatewayObserver(peers, gateway2); + sawPeer(observer2, fooPeer); + sawPeer(observer2, barPeer); + sawPeer(observer1, fooPeer); + sawPeer(observer2, bazPeer); + io.flush(); + } + + expectPeers({{fooPeer, gateway1}}, peers.sessionPeers(fooPeer.sessionId())); + CHECK(4 == membership.calls); + } } } // namespace link diff --git a/src/ableton/link/tst_Phase.cpp b/src/ableton/link/tst_Phase.cpp index 9b73e8e..f9e3af9 100644 --- a/src/ableton/link/tst_Phase.cpp +++ b/src/ableton/link/tst_Phase.cpp @@ -24,99 +24,6 @@ namespace ableton { namespace link { -namespace -{ - -const auto zero = Beats{0.}; -const auto one = Beats{1.}; -const auto two = Beats{2.}; -const auto three = Beats{3.}; -const auto four = Beats{4.}; - -using std::chrono::microseconds; -const auto tl0 = Timeline{Tempo{120.}, one, microseconds{0}}; -const auto tl1 = Timeline{Tempo{60.}, Beats{-9.5}, microseconds{2000000}}; -} // namespace - -TEST_CASE("phase | phase(x, 0) == 0", "[Phase]") -{ - CHECK(phase(zero, zero) == zero); - CHECK(phase(Beats{0.1}, zero) == zero); - CHECK(phase(one, zero) == zero); - CHECK(phase(-one, zero) == zero); -} - -TEST_CASE("phase | phase(x, y) == x % y when x and y >= 0", "[Phase]") -{ - CHECK(phase(zero, zero) == zero % zero); - CHECK(phase(one, zero) == one % zero); - CHECK(phase(zero, one) == zero % one); - CHECK(phase(Beats{0.1}, one) == Beats{0.1} % one); - CHECK(phase(Beats{2.3}, one) == Beats{2.3} % one); - CHECK(phase(Beats{9.5}, Beats{2.3}) == Beats{9.5} % Beats{2.3}); -} - -TEST_CASE("phase | phase of negatives is not mirrored around zero", "[Phase]") -{ - CHECK(phase(-one, one) == zero); - CHECK(phase(Beats{-0.1}, one) == Beats{0.9}); - CHECK(phase(Beats{-2.3}, one) == Beats{0.7}); - CHECK(phase(Beats{-9.5}, Beats{2.3}) == Beats{2.}); -} - -TEST_CASE("nextPhaseMatch | result == x when quantum == 0", "[Phase]") -{ - CHECK(nextPhaseMatch(Beats{0.1}, one, zero) == Beats{0.1}); - CHECK(nextPhaseMatch(Beats{2.3}, Beats{9.5}, zero) == Beats{2.3}); - CHECK(nextPhaseMatch(Beats{-0.1}, Beats{-2.3}, zero) == Beats{-0.1}); -} - -TEST_CASE("nextPhaseMatch | result == target when 0 <= x < target < quantum", "[Phase]") -{ - CHECK(nextPhaseMatch(zero, Beats{0.1}, one) == Beats{0.1}); - CHECK(nextPhaseMatch(Beats{0.1}, one, two) == one); - CHECK(nextPhaseMatch(one, two, Beats{2.3}) == two); - CHECK(nextPhaseMatch(two, Beats{2.3}, three) == Beats{2.3}); -} - -TEST_CASE("nextPhaseMatch | some example cases", "[Phase]") -{ - CHECK(nextPhaseMatch(one, Beats{2.3}, two) == Beats{2.3}); - CHECK(nextPhaseMatch(Beats{2.3}, Beats{-0.1}, two) == Beats{3.9}); - CHECK(nextPhaseMatch(Beats{-9.5}, Beats{0.1}, two) == Beats{-7.9}); - CHECK(nextPhaseMatch(Beats{-2.3}, Beats{0.1}, Beats{9.5}) == Beats{0.1}); -} - -TEST_CASE("toPhaseEncodedBeats | result == tl.toBeats when quantum == 0", "[Phase]") -{ - const auto t0 = microseconds{0}; - const auto t1 = microseconds{2000000}; - const auto t2 = microseconds{-3200000}; - CHECK(toPhaseEncodedBeats(tl1, t0, zero) == tl1.toBeats(t0)); - CHECK(toPhaseEncodedBeats(tl0, t1, zero) == tl0.toBeats(t1)); - CHECK(toPhaseEncodedBeats(tl0, t2, zero) == tl0.toBeats(t2)); -} - -TEST_CASE("toPhaseEncodedBeats | result is the nearest quantum boundary", "[Phase]") -{ - const auto sec = microseconds{1000000}; - - // Takes the previous boundary - CHECK(toPhaseEncodedBeats(tl0, sec, Beats{2.2}) == two); - // Takes the next boundary - CHECK(toPhaseEncodedBeats(tl0, sec, Beats{1.8}) == Beats{3.8}); - // Takes the previous boundary when exactly in the middle - CHECK(toPhaseEncodedBeats(tl0, sec, two) == two); -} - -TEST_CASE("toPhaseEncodedBeats | some example cases", "[Phase]") -{ - CHECK(toPhaseEncodedBeats(tl0, microseconds{-2000000}, four) == -four); - CHECK(toPhaseEncodedBeats(tl0, microseconds{-3000000}, four) == Beats{-6.}); - CHECK(toPhaseEncodedBeats(tl0, microseconds{3200000}, three) == Beats{6.4}); - CHECK(toPhaseEncodedBeats(tl1, microseconds{0}, three) == Beats{-11.}); - CHECK(toPhaseEncodedBeats(tl1, microseconds{1500000}, Beats{2.4}) == Beats{-10.1}); -} namespace { @@ -128,52 +35,148 @@ std::chrono::microseconds phaseEncodingRoundtrip( } } // namespace -TEST_CASE("fromPhaseEncodedBeats | inverse of toPhaseEncodedBeats", "[Phase]") + +TEST_CASE("Phase") { - const auto t0 = microseconds{0}; - const auto t1 = microseconds{2000000}; - const auto t2 = microseconds{-3200000}; - const auto t3 = microseconds{87654321}; - - CHECK(phaseEncodingRoundtrip(tl0, t0, zero) == t0); - CHECK(phaseEncodingRoundtrip(tl0, t1, zero) == t1); - CHECK(phaseEncodingRoundtrip(tl0, t2, zero) == t2); - CHECK(phaseEncodingRoundtrip(tl0, t3, zero) == t3); - - CHECK(phaseEncodingRoundtrip(tl0, t0, one) == t0); - CHECK(phaseEncodingRoundtrip(tl0, t1, one) == t1); - CHECK(phaseEncodingRoundtrip(tl0, t2, one) == t2); - CHECK(phaseEncodingRoundtrip(tl0, t3, one) == t3); - - CHECK(phaseEncodingRoundtrip(tl0, t0, two) == t0); - CHECK(phaseEncodingRoundtrip(tl0, t1, two) == t1); - CHECK(phaseEncodingRoundtrip(tl0, t2, two) == t2); - CHECK(phaseEncodingRoundtrip(tl0, t3, two) == t3); - - CHECK(phaseEncodingRoundtrip(tl0, t0, three) == t0); - CHECK(phaseEncodingRoundtrip(tl0, t1, three) == t1); - CHECK(phaseEncodingRoundtrip(tl0, t2, three) == t2); - CHECK(phaseEncodingRoundtrip(tl0, t3, three) == t3); - - CHECK(phaseEncodingRoundtrip(tl1, t0, zero) == t0); - CHECK(phaseEncodingRoundtrip(tl1, t1, zero) == t1); - CHECK(phaseEncodingRoundtrip(tl1, t2, zero) == t2); - CHECK(phaseEncodingRoundtrip(tl1, t3, zero) == t3); - - CHECK(phaseEncodingRoundtrip(tl1, t0, one) == t0); - CHECK(phaseEncodingRoundtrip(tl1, t1, one) == t1); - CHECK(phaseEncodingRoundtrip(tl1, t2, one) == t2); - CHECK(phaseEncodingRoundtrip(tl1, t3, one) == t3); - - CHECK(phaseEncodingRoundtrip(tl1, t0, two) == t0); - CHECK(phaseEncodingRoundtrip(tl1, t1, two) == t1); - CHECK(phaseEncodingRoundtrip(tl1, t2, two) == t2); - CHECK(phaseEncodingRoundtrip(tl1, t3, two) == t3); - - CHECK(phaseEncodingRoundtrip(tl1, t0, three) == t0); - CHECK(phaseEncodingRoundtrip(tl1, t1, three) == t1); - CHECK(phaseEncodingRoundtrip(tl1, t2, three) == t2); - CHECK(phaseEncodingRoundtrip(tl1, t3, three) == t3); + const auto zero = Beats{0.}; + const auto one = Beats{1.}; + const auto two = Beats{2.}; + const auto three = Beats{3.}; + const auto four = Beats{4.}; + + using std::chrono::microseconds; + const auto tl0 = Timeline{Tempo{120.}, one, microseconds{0}}; + const auto tl1 = Timeline{Tempo{60.}, Beats{-9.5}, microseconds{2000000}}; + + SECTION("phase(x, 0) == 0") + { + CHECK(phase(zero, zero) == zero); + CHECK(phase(Beats{0.1}, zero) == zero); + CHECK(phase(one, zero) == zero); + CHECK(phase(-one, zero) == zero); + } + + SECTION("phase(x, y) == x % y when x and y >= 0") + { + CHECK(phase(zero, zero) == zero % zero); + CHECK(phase(one, zero) == one % zero); + CHECK(phase(zero, one) == zero % one); + CHECK(phase(Beats{0.1}, one) == Beats{0.1} % one); + CHECK(phase(Beats{2.3}, one) == Beats{2.3} % one); + CHECK(phase(Beats{9.5}, Beats{2.3}) == Beats{9.5} % Beats{2.3}); + } + + SECTION("phase of negatives is not mirrored around zero") + { + CHECK(phase(-one, one) == zero); + CHECK(phase(Beats{-0.1}, one) == Beats{0.9}); + CHECK(phase(Beats{-2.3}, one) == Beats{0.7}); + CHECK(phase(Beats{-9.5}, Beats{2.3}) == Beats{2.}); + } + + SECTION("nextPhaseMatch | result == x when quantum == 0") + { + CHECK(nextPhaseMatch(Beats{0.1}, one, zero) == Beats{0.1}); + CHECK(nextPhaseMatch(Beats{2.3}, Beats{9.5}, zero) == Beats{2.3}); + CHECK(nextPhaseMatch(Beats{-0.1}, Beats{-2.3}, zero) == Beats{-0.1}); + } + + SECTION("nextPhaseMatch | result == target when 0 <= x < target < quantum") + { + CHECK(nextPhaseMatch(zero, Beats{0.1}, one) == Beats{0.1}); + CHECK(nextPhaseMatch(Beats{0.1}, one, two) == one); + CHECK(nextPhaseMatch(one, two, Beats{2.3}) == two); + CHECK(nextPhaseMatch(two, Beats{2.3}, three) == Beats{2.3}); + } + + SECTION("nextPhaseMatch | some example cases") + { + CHECK(nextPhaseMatch(one, Beats{2.3}, two) == Beats{2.3}); + CHECK(nextPhaseMatch(Beats{2.3}, Beats{-0.1}, two) == Beats{3.9}); + CHECK(nextPhaseMatch(Beats{-9.5}, Beats{0.1}, two) == Beats{-7.9}); + CHECK(nextPhaseMatch(Beats{-2.3}, Beats{0.1}, Beats{9.5}) == Beats{0.1}); + } + + SECTION("toPhaseEncodedBeats | result == tl.toBeats when quantum == 0") + { + const auto t0 = microseconds{0}; + const auto t1 = microseconds{2000000}; + const auto t2 = microseconds{-3200000}; + CHECK(toPhaseEncodedBeats(tl1, t0, zero) == tl1.toBeats(t0)); + CHECK(toPhaseEncodedBeats(tl0, t1, zero) == tl0.toBeats(t1)); + CHECK(toPhaseEncodedBeats(tl0, t2, zero) == tl0.toBeats(t2)); + } + + SECTION("toPhaseEncodedBeats | result is the nearest quantum boundary") + { + const auto sec = microseconds{1000000}; + + // Takes the previous boundary + CHECK(toPhaseEncodedBeats(tl0, sec, Beats{2.2}) == two); + // Takes the next boundary + CHECK(toPhaseEncodedBeats(tl0, sec, Beats{1.8}) == Beats{3.8}); + // Takes the previous boundary when exactly in the middle + CHECK(toPhaseEncodedBeats(tl0, sec, two) == two); + } + + SECTION("toPhaseEncodedBeats | some example cases") + { + CHECK(toPhaseEncodedBeats(tl0, microseconds{-2000000}, four) == -four); + CHECK(toPhaseEncodedBeats(tl0, microseconds{-3000000}, four) == Beats{-6.}); + CHECK(toPhaseEncodedBeats(tl0, microseconds{3200000}, three) == Beats{6.4}); + CHECK(toPhaseEncodedBeats(tl1, microseconds{0}, three) == Beats{-11.}); + CHECK(toPhaseEncodedBeats(tl1, microseconds{1500000}, Beats{2.4}) == Beats{-10.1}); + } + + SECTION("fromPhaseEncodedBeats | inverse of toPhaseEncodedBeats") + { + using std::chrono::microseconds; + + const auto t0 = microseconds{0}; + const auto t1 = microseconds{2000000}; + const auto t2 = microseconds{-3200000}; + const auto t3 = microseconds{87654321}; + + CHECK(phaseEncodingRoundtrip(tl0, t0, zero) == t0); + CHECK(phaseEncodingRoundtrip(tl0, t1, zero) == t1); + CHECK(phaseEncodingRoundtrip(tl0, t2, zero) == t2); + CHECK(phaseEncodingRoundtrip(tl0, t3, zero) == t3); + + CHECK(phaseEncodingRoundtrip(tl0, t0, one) == t0); + CHECK(phaseEncodingRoundtrip(tl0, t1, one) == t1); + CHECK(phaseEncodingRoundtrip(tl0, t2, one) == t2); + CHECK(phaseEncodingRoundtrip(tl0, t3, one) == t3); + + CHECK(phaseEncodingRoundtrip(tl0, t0, two) == t0); + CHECK(phaseEncodingRoundtrip(tl0, t1, two) == t1); + CHECK(phaseEncodingRoundtrip(tl0, t2, two) == t2); + CHECK(phaseEncodingRoundtrip(tl0, t3, two) == t3); + + CHECK(phaseEncodingRoundtrip(tl0, t0, three) == t0); + CHECK(phaseEncodingRoundtrip(tl0, t1, three) == t1); + CHECK(phaseEncodingRoundtrip(tl0, t2, three) == t2); + CHECK(phaseEncodingRoundtrip(tl0, t3, three) == t3); + + CHECK(phaseEncodingRoundtrip(tl1, t0, zero) == t0); + CHECK(phaseEncodingRoundtrip(tl1, t1, zero) == t1); + CHECK(phaseEncodingRoundtrip(tl1, t2, zero) == t2); + CHECK(phaseEncodingRoundtrip(tl1, t3, zero) == t3); + + CHECK(phaseEncodingRoundtrip(tl1, t0, one) == t0); + CHECK(phaseEncodingRoundtrip(tl1, t1, one) == t1); + CHECK(phaseEncodingRoundtrip(tl1, t2, one) == t2); + CHECK(phaseEncodingRoundtrip(tl1, t3, one) == t3); + + CHECK(phaseEncodingRoundtrip(tl1, t0, two) == t0); + CHECK(phaseEncodingRoundtrip(tl1, t1, two) == t1); + CHECK(phaseEncodingRoundtrip(tl1, t2, two) == t2); + CHECK(phaseEncodingRoundtrip(tl1, t3, two) == t3); + + CHECK(phaseEncodingRoundtrip(tl1, t0, three) == t0); + CHECK(phaseEncodingRoundtrip(tl1, t1, three) == t1); + CHECK(phaseEncodingRoundtrip(tl1, t2, three) == t2); + CHECK(phaseEncodingRoundtrip(tl1, t3, three) == t3); + } } } // namespace link diff --git a/src/ableton/link/tst_PingResponder.cpp b/src/ableton/link/tst_PingResponder.cpp index 8f0c095..ecaffa2 100644 --- a/src/ableton/link/tst_PingResponder.cpp +++ b/src/ableton/link/tst_PingResponder.cpp @@ -64,12 +64,6 @@ struct MockIoContext return {}; } - template <typename Handler> - void async(Handler handler) const - { - handler(); - } - ableton::util::test::IoService mIo; }; @@ -102,63 +96,62 @@ struct RpFixture } // anonymous namespace -TEST_CASE("PingResponder | ReplyToPing", "[PingResponder]") -{ - using std::chrono::microseconds; - - RpFixture fixture; - // Construct and send Ping Message. Check if Responder sent back a Message - const auto payload = - discovery::makePayload(HostTime{microseconds(2)}, PrevGHostTime{microseconds(1)}); - - v1::MessageBuffer buffer; - const auto msgBegin = std::begin(buffer); - const auto msgEnd = v1::pingMessage(payload, msgBegin); - const auto endpoint = asio::ip::udp::endpoint(fixture.mAddress, 8888); - - fixture.responderSocket().incomingMessage(endpoint, msgBegin, msgEnd); - - CHECK(1 == fixture.numSentMessages()); - - // Check Responder's message - const auto messageBuffer = fixture.responderSocket().sentMessages[0].first; - const auto result = v1::parseMessageHeader(begin(messageBuffer), end(messageBuffer)); - const auto& hdr = result.first; - - std::chrono::microseconds ghostTime{0}; - std::chrono::microseconds prevGHostTime{0}; - std::chrono::microseconds hostTime{0}; - discovery::parsePayload<GHostTime, PrevGHostTime, HostTime>(result.second, - std::end(messageBuffer), - [&ghostTime](GHostTime gt) { ghostTime = std::move(gt.time); }, - [&prevGHostTime](PrevGHostTime gt) { prevGHostTime = std::move(gt.time); }, - [&hostTime](HostTime ht) { hostTime = std::move(ht.time); }); - - CHECK(v1::kPong == hdr.messageType); - CHECK(std::chrono::microseconds{2} == hostTime); - CHECK(std::chrono::microseconds{1} == prevGHostTime); - CHECK(std::chrono::microseconds{4} == ghostTime); -} - -TEST_CASE("PingResponder | PingSizeExceeding", "[PingResponder]") +TEST_CASE("PingResponder") { using std::chrono::microseconds; - RpFixture fixture; - const auto ht = HostTime{microseconds(2)}; - const auto payload = discovery::makePayload(ht, ht, ht); + SECTION("ReplyToPing") + { + // Construct and send Ping Message. Check if Responder sent back a Message + const auto payload = + discovery::makePayload(HostTime{microseconds(2)}, PrevGHostTime{microseconds(1)}); + + v1::MessageBuffer buffer; + const auto msgBegin = std::begin(buffer); + const auto msgEnd = v1::pingMessage(payload, msgBegin); + const auto endpoint = asio::ip::udp::endpoint(fixture.mAddress, 8888); + + fixture.responderSocket().incomingMessage(endpoint, msgBegin, msgEnd); + + CHECK(1 == fixture.numSentMessages()); + + // Check Responder's message + const auto messageBuffer = fixture.responderSocket().sentMessages[0].first; + const auto result = v1::parseMessageHeader(begin(messageBuffer), end(messageBuffer)); + const auto& hdr = result.first; + + std::chrono::microseconds ghostTime{0}; + std::chrono::microseconds prevGHostTime{0}; + std::chrono::microseconds hostTime{0}; + discovery::parsePayload<GHostTime, PrevGHostTime, HostTime>(result.second, + std::end(messageBuffer), + [&ghostTime](GHostTime gt) { ghostTime = std::move(gt.time); }, + [&prevGHostTime](PrevGHostTime gt) { prevGHostTime = std::move(gt.time); }, + [&hostTime](HostTime ht) { hostTime = std::move(ht.time); }); + + CHECK(v1::kPong == hdr.messageType); + CHECK(std::chrono::microseconds{2} == hostTime); + CHECK(std::chrono::microseconds{1} == prevGHostTime); + CHECK(std::chrono::microseconds{4} == ghostTime); + } - v1::MessageBuffer buffer; - const auto msgBegin = std::begin(buffer); - const auto msgEnd = v1::pingMessage(payload, msgBegin); + SECTION("PingSizeExceeding") + { + const auto ht = HostTime{microseconds(2)}; + const auto payload = discovery::makePayload(ht, ht, ht); + + v1::MessageBuffer buffer; + const auto msgBegin = std::begin(buffer); + const auto msgEnd = v1::pingMessage(payload, msgBegin); - const auto endpoint = asio::ip::udp::endpoint(fixture.mAddress, 8888); + const auto endpoint = asio::ip::udp::endpoint(fixture.mAddress, 8888); - fixture.responderSocket().incomingMessage(endpoint, msgBegin, msgEnd); + fixture.responderSocket().incomingMessage(endpoint, msgBegin, msgEnd); - CHECK(0 == fixture.numSentMessages()); + CHECK(0 == fixture.numSentMessages()); + } } } // namespace link diff --git a/src/ableton/link/tst_StartStopState.cpp b/src/ableton/link/tst_StartStopState.cpp index 69ba817..ae01b3f 100644 --- a/src/ableton/link/tst_StartStopState.cpp +++ b/src/ableton/link/tst_StartStopState.cpp @@ -25,7 +25,7 @@ namespace ableton namespace link { -TEST_CASE("StartStopState | RoundtripByteStreamEncoding", "[StartStopState]") +TEST_CASE("StartStopState | RoundtripByteStreamEncoding") { const auto originalState = StartStopState{true, Beats{1234.}, std::chrono::microseconds{5678}}; diff --git a/src/ableton/link/tst_Tempo.cpp b/src/ableton/link/tst_Tempo.cpp index a7c87c3..85a0f38 100644 --- a/src/ableton/link/tst_Tempo.cpp +++ b/src/ableton/link/tst_Tempo.cpp @@ -25,84 +25,56 @@ namespace ableton namespace link { -TEST_CASE("Tempo | ConstructFromBpm", "[Tempo]") +TEST_CASE("Tempo") { - const auto tempo = Tempo{120.}; - CHECK(120. == Approx(tempo.bpm())); - CHECK(std::chrono::microseconds{500000} == tempo.microsPerBeat()); -} - -TEST_CASE("Tempo | ConstructFromMicros", "[Tempo]") -{ - const auto tempo = Tempo{std::chrono::microseconds{500000}}; - CHECK(120. == Approx(tempo.bpm())); - CHECK(std::chrono::microseconds{500000} == tempo.microsPerBeat()); -} - -TEST_CASE("Tempo | MicrosToBeats", "[Tempo]") -{ - const auto tempo = Tempo{120.}; - CHECK(Beats{2.} == tempo.microsToBeats(std::chrono::microseconds{1000000})); -} - -TEST_CASE("Tempo | BeatsToMicros", "[Tempo]") -{ - const auto tempo = Tempo{120.}; - CHECK(std::chrono::microseconds{1000000} == tempo.beatsToMicros(Beats{2.})); -} - -TEST_CASE("Tempo | ComparisonLT", "[Tempo]") -{ - const auto tempo1 = Tempo{100.}; - const auto tempo2 = Tempo{200.}; - CHECK(tempo1 < tempo2); -} + SECTION("ConstructFromBpm") + { + const auto tempo = Tempo{120.}; + CHECK(120. == Approx(tempo.bpm())); + CHECK(std::chrono::microseconds{500000} == tempo.microsPerBeat()); + } -TEST_CASE("Tempo | ComparisonGT", "[Tempo]") -{ - const auto tempo1 = Tempo{100.}; - const auto tempo2 = Tempo{200.}; - CHECK(tempo2 > tempo1); -} + SECTION("ConstructFromMicros") + { + const auto tempo = Tempo{std::chrono::microseconds{500000}}; + CHECK(120. == Approx(tempo.bpm())); + CHECK(std::chrono::microseconds{500000} == tempo.microsPerBeat()); + } + SECTION("MicrosToBeats") + { + const auto tempo = Tempo{120.}; + CHECK(Beats{2.} == tempo.microsToBeats(std::chrono::microseconds{1000000})); + } -TEST_CASE("Tempo | ComparisonLE", "[Tempo]") -{ - const auto tempo1 = Tempo{100.}; - const auto tempo2 = Tempo{200.}; - CHECK(tempo1 <= tempo2); - CHECK(tempo2 <= tempo2); -} + SECTION("BeatsToMicros") + { + const auto tempo = Tempo{120.}; + CHECK(std::chrono::microseconds{1000000} == tempo.beatsToMicros(Beats{2.})); + } -TEST_CASE("Tempo | ComparisonGE", "[Tempo]") -{ - const auto tempo1 = Tempo{100.}; - const auto tempo2 = Tempo{200.}; - CHECK(tempo2 >= tempo1); - CHECK(tempo2 >= tempo2); -} + SECTION("Comparison") + { + const auto tempo1 = Tempo{100.}; + const auto tempo2 = Tempo{200.}; + CHECK(tempo1 < tempo2); + CHECK(tempo2 > tempo1); + CHECK(tempo1 <= tempo2); + CHECK(tempo2 <= tempo2); + CHECK(tempo2 >= tempo1); + CHECK(tempo2 >= tempo2); + CHECK(Tempo{100.} == tempo1); + CHECK(tempo1 != tempo2); + } -TEST_CASE("Tempo | ComparisonEQ", "[Tempo]") -{ - const auto tempo1 = Tempo{100.}; - const auto tempo2 = Tempo{100.}; - CHECK(tempo1 == tempo2); -} - -TEST_CASE("Tempo | ComparisonNE", "[Tempo]") -{ - const auto tempo1 = Tempo{100.}; - const auto tempo2 = Tempo{200.}; - CHECK(tempo1 != tempo2); -} - -TEST_CASE("Tempo | RoundtripByteStreamEncoding", "[Tempo]") -{ - const auto tempo = Tempo{120.}; - std::vector<std::uint8_t> bytes(sizeInByteStream(tempo)); - const auto end = toNetworkByteStream(tempo, begin(bytes)); - const auto result = Tempo::fromNetworkByteStream(begin(bytes), end); - CHECK(tempo == result.first); + SECTION("RoundtripByteStreamEncoding") + { + const auto tempo = Tempo{120.}; + std::vector<std::uint8_t> bytes(sizeInByteStream(tempo)); + const auto end = toNetworkByteStream(tempo, begin(bytes)); + const auto result = Tempo::fromNetworkByteStream(begin(bytes), end); + CHECK(tempo == result.first); + } } } // namespace link diff --git a/src/ableton/link/tst_Timeline.cpp b/src/ableton/link/tst_Timeline.cpp index 8983204..43cf226 100644 --- a/src/ableton/link/tst_Timeline.cpp +++ b/src/ableton/link/tst_Timeline.cpp @@ -25,25 +25,29 @@ namespace ableton namespace link { -TEST_CASE("Timeline | TimeToBeats", "[Timeline]") +TEST_CASE("Timeline") { - const auto tl = Timeline{Tempo{60.}, Beats{-1.}, std::chrono::microseconds{1000000}}; - CHECK(Beats{2.5} == tl.toBeats(std::chrono::microseconds{4500000})); -} + const auto tl60 = Timeline{Tempo{60.}, Beats{-1.}, std::chrono::microseconds{1000000}}; + const auto tl120 = + Timeline{Tempo{120.}, Beats{5.5}, std::chrono::microseconds{12558940}}; -TEST_CASE("Timeline | BeatsToTime", "[Timeline]") -{ - const auto tl = Timeline{Tempo{60.}, Beats{-1.}, std::chrono::microseconds{1000000}}; - CHECK(std::chrono::microseconds{5200000} == tl.fromBeats(Beats{3.2})); -} + SECTION("TimeToBeats") + { + CHECK(Beats{2.5} == tl60.toBeats(std::chrono::microseconds{4500000})); + } -TEST_CASE("Timeline | RoundtripByteStreamEncoding", "[Timeline]") -{ - const auto tl = Timeline{Tempo{120.}, Beats{5.5}, std::chrono::microseconds{12558940}}; - std::vector<std::uint8_t> bytes(sizeInByteStream(tl)); - const auto end = toNetworkByteStream(tl, begin(bytes)); - const auto result = Timeline::fromNetworkByteStream(begin(bytes), end); - CHECK(tl == result.first); + SECTION("BeatsToTime") + { + CHECK(std::chrono::microseconds{5200000} == tl60.fromBeats(Beats{3.2})); + } + + SECTION("RoundtripByteStreamEncoding") + { + std::vector<std::uint8_t> bytes(sizeInByteStream(tl120)); + const auto end = toNetworkByteStream(tl120, begin(bytes)); + const auto result = Timeline::fromNetworkByteStream(begin(bytes), end); + CHECK(tl120 == result.first); + } } } // namespace link diff --git a/src/ableton/link/tst_TripleBuffer.cpp b/src/ableton/link/tst_TripleBuffer.cpp new file mode 100644 index 0000000..02f91d9 --- /dev/null +++ b/src/ableton/link/tst_TripleBuffer.cpp @@ -0,0 +1,165 @@ +/* Copyright 2022, Ableton AG, Berlin. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * If you would like to incorporate Link into a proprietary software application, + * please contact <link-devs@ableton.com>. + */ + + +#include <ableton/link/TripleBuffer.hpp> +#include <ableton/test/CatchWrapper.hpp> + +#include <array> +#include <cstdint> +#include <functional> +#include <random> +#include <thread> + +namespace ableton +{ +namespace link +{ +namespace +{ + +constexpr auto kNumTestOps = 1u << 18u; + +struct BigValue +{ + BigValue(const uint32_t s) + : seed{s} + { + // Generate random values to take some time and test read/write consistency. + std::minstd_rand generator{s}; + std::generate(values.begin(), values.end(), generator); + } + + uint32_t seed; // Seed that identifies this value + std::array<uint32_t, 40> values; // Seed-derived data larger than a cache line +}; + +void writeValues(TripleBuffer<BigValue>& buffer, const uint32_t numOps) +{ + for (uint32_t i = 0; i < numOps; ++i) + { + buffer.write(i); + } +} + +void readValues(TripleBuffer<BigValue>& buffer, const uint32_t numOps) +{ + auto prevValueSeed = 0u; + + for (uint32_t i = 0; i < numOps; ++i) + { + const auto thisValue = buffer.read(); + + assert(thisValue.seed >= prevValueSeed); + assert(thisValue.values == BigValue{thisValue.seed}.values); + + prevValueSeed = thisValue.seed; + } +} + +} // namespace + +TEST_CASE("TripleBuffer") +{ + SECTION("Construction") + { + TripleBuffer<int> buffer; + } + + SECTION("Reads default value before any writes") + { + TripleBuffer<int> buffer; + + CHECK(buffer.read() == int{}); + CHECK(buffer.read() == int{}); + CHECK(buffer.read() == int{}); + } + + SECTION("Reads initial value before any writes") + { + TripleBuffer<int> buffer{42}; + + CHECK(buffer.read() == 42); + CHECK(buffer.read() == 42); + CHECK(buffer.read() == 42); + } + + SECTION("Reads last written value") + { + TripleBuffer<int> buffer; + + buffer.write(42); + CHECK(buffer.read() == 42); + CHECK(buffer.read() == 42); + CHECK(buffer.read() == 42); + + buffer.write(43); + CHECK(buffer.read() == 43); + CHECK(buffer.read() == 43); + CHECK(buffer.read() == 43); + + buffer.write(44); + CHECK(buffer.read() == 44); + CHECK(buffer.read() == 44); + CHECK(buffer.read() == 44); + + buffer.write(45); + CHECK(buffer.read() == 45); + CHECK(buffer.read() == 45); + CHECK(buffer.read() == 45); + } + + SECTION("Reads new last written value") + { + TripleBuffer<int> buffer; + + buffer.write(42); + CHECK(*buffer.readNew() == 42); + CHECK(!buffer.readNew()); + CHECK(buffer.read() == 42); + + buffer.write(43); + CHECK(buffer.read() == 43); + CHECK(!buffer.readNew()); + CHECK(buffer.read() == 43); + + buffer.write(44); + CHECK(*buffer.readNew() == 44); + CHECK(!buffer.readNew()); + CHECK(buffer.read() == 44); + + buffer.write(45); + CHECK(buffer.read() == 45); + CHECK(!buffer.readNew()); + CHECK(buffer.read() == 45); + } + + SECTION("Threaded read and write") + { + TripleBuffer<BigValue> buffer{0u}; + std::thread writer{writeValues, std::ref(buffer), kNumTestOps}; + std::thread reader{readValues, std::ref(buffer), kNumTestOps}; + + writer.join(); + reader.join(); + } +} + +} // namespace link +} // namespace ableton diff --git a/third_party/catch/catch.hpp b/third_party/catch/catch.hpp index 4d1fc33..7e706f9 100644 --- a/third_party/catch/catch.hpp +++ b/third_party/catch/catch.hpp @@ -1,17 +1,21 @@ /* - * Catch v1.10.0 - * Generated: 2017-08-26 15:16:46.676990 + * Catch v2.13.7 + * Generated: 2021-07-28 20:29:27.753164 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp -#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 7 #ifdef __clang__ # pragma clang system_header @@ -19,36 +23,69 @@ # pragma GCC system_header #endif -// #included from: internal/catch_suppress_warnings.h +// start catch_suppress_warnings.h #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC -# pragma clang diagnostic ignored "-Wglobal-constructors" -# pragma clang diagnostic ignored "-Wvariadic-macros" -# pragma clang diagnostic ignored "-Wc99-extensions" -# pragma clang diagnostic ignored "-Wunused-variable" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" -# pragma clang diagnostic ignored "-Wc++98-compat" -# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" # pragma clang diagnostic ignored "-Wswitch-enum" # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ -# pragma GCC diagnostic ignored "-Wvariadic-macros" -# pragma GCC diagnostic ignored "-Wunused-variable" -# pragma GCC diagnostic ignored "-Wparentheses" + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details # pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic ignored "-Wpadded" #endif +// end catch_suppress_warnings.h #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html +#ifdef __APPLE__ +# include <TargetConditionals.h> +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS #endif +// end catch_platform.h + #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED @@ -56,93 +93,106 @@ # endif #endif -// #included from: internal/catch_notimplemented_exception.h -#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h -// #included from: catch_common.h -#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED +// start catch_common.h -// #included from: catch_compiler_capabilities.h -#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED +// start catch_compiler_capabilities.h -// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// Detect a number of compiler features - by compiler // The following features are defined: // -// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? -// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? -// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods -// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? -// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported -// CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? -// CATCH_CONFIG_CPP11_OVERRIDE : is override supported? -// CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) -// CATCH_CONFIG_CPP11_SHUFFLE : is std::shuffle supported? -// CATCH_CONFIG_CPP11_TYPE_TRAITS : are type_traits and enable_if supported? - -// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? - -// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? // CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too // **************** // In general each macro has a _NO_<feature name> form -// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. -// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 - #ifdef __cplusplus -# if __cplusplus >= 201103L -# define CATCH_CPP11_OR_GREATER +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER # endif -# if __cplusplus >= 201402L -# define CATCH_CPP14_OR_GREATER +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER # endif #endif -#ifdef __clang__ +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) -# if __has_feature(cxx_nullptr) -# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR -# endif +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) -# if __has_feature(cxx_noexcept) -# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ # endif -# if defined(CATCH_CPP11_OR_GREATER) -# define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) -# define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ - _Pragma( "clang diagnostic pop" ) - -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic pop" ) -# endif +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// -// We know some environments not to support full POSIX signals -#if defined(__CYGWIN__) || defined(__QNX__) - -# if !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# endif +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS #endif #ifdef __OS400__ @@ -151,223 +201,268 @@ #endif //////////////////////////////////////////////////////////////////////////////// -// Cygwin -#ifdef __CYGWIN__ - -// Required for some versions of Cygwin to declare gettimeofday -// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin -# define _BSD_SOURCE - -#endif // __CYGWIN__ - -//////////////////////////////////////////////////////////////////////////////// -// Borland -#ifdef __BORLANDC__ - -#endif // __BORLANDC__ +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif //////////////////////////////////////////////////////////////////////////////// -// EDG -#ifdef __EDG_VERSION__ - -#endif // __EDG_VERSION__ +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif //////////////////////////////////////////////////////////////////////////////// -// Digital Mars -#ifdef __DMC__ - -#endif // __DMC__ +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif //////////////////////////////////////////////////////////////////////////////// -// GCC -#ifdef __GNUC__ +// Cygwin +#ifdef __CYGWIN__ -# if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) -# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR -# endif +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) -// - otherwise more recent versions define __cplusplus >= 201103L -// and will get picked up below +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING -#endif // __GNUC__ +# endif +#endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ -#ifdef _MSC_VER +#if defined(_MSC_VER) -#define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) -#if (_MSC_VER >= 1600) -# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR -# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR -#endif +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif -#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) -#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT -#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -#define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE -#define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS -#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 +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ #endif // _MSC_VER -//////////////////////////////////////////////////////////////////////////////// - -// Use variadic macros if the compiler supports them -#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ - ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ - ( defined __GNUC__ && __GNUC__ >= 3 ) || \ - ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) - -#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED #endif -// Use __COUNTER__ if the compiler supports it -#if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ - ( defined __GNUC__ && ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3 )) ) || \ - ( defined __clang__ && __clang_major__ >= 3 ) - -#define CATCH_INTERNAL_CONFIG_COUNTER +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN #endif //////////////////////////////////////////////////////////////////////////////// -// C++ language feature support -// catch all support for C++11 -#if defined(CATCH_CPP11_OR_GREATER) +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif -# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) -# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR -# endif +//////////////////////////////////////////////////////////////////////////////// -# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT -# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT -# endif +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif -# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -# endif +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif -# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM -# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM -# endif +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include(<string_view>) && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER) + # include <cstddef> + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include <ciso646> + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif -# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE -# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE -# endif +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif -# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS -# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS -# endif +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif -# if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) -# define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG -# endif +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif -# if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) -# define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE -# endif -# if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) -# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR -# endif -# if !defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) -# define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE -# endif -# if !defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) -# define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS -# endif +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif -#endif // __cplusplus >= 201103L +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif -// Now set the actual defines based on the above + anything the user has configured -#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_NULLPTR +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_NOEXCEPT + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_GENERATED_METHODS + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_IS_ENUM + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_TUPLE + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC #endif -#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) -# define CATCH_CONFIG_VARIADIC_MACROS + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_LONG_LONG + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_OVERRIDE + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_UNIQUE_PTR +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #endif -// Use of __COUNTER__ is suppressed if __JETBRAINS_IDE__ is #defined (meaning we're being parsed by a JetBrains IDE for -// analytics) because, at time of writing, __COUNTER__ is not properly handled by it. -// This does not affect compilation -#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) && !defined(__JETBRAINS_IDE__) -# define CATCH_CONFIG_COUNTER +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_NO_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_SHUFFLE +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS #endif -# if defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_NO_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_TYPE_TRAITS -# endif -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) -# define CATCH_CONFIG_WINDOWS_SEH +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS #endif -// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. -#if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_CONFIG_POSIX_SIGNALS +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS #endif -#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) #endif -// noexcept support: -#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) -# define CATCH_NOEXCEPT noexcept -# define CATCH_NOEXCEPT_IS(x) noexcept(x) -#else -# define CATCH_NOEXCEPT throw() -# define CATCH_NOEXCEPT_IS(x) +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif -// nullptr support -#ifdef CATCH_CONFIG_CPP11_NULLPTR -# define CATCH_NULL nullptr -#else -# define CATCH_NULL NULL +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif -// override support -#ifdef CATCH_CONFIG_CPP11_OVERRIDE -# define CATCH_OVERRIDE override +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) #else -# define CATCH_OVERRIDE +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) #endif -// unique_ptr support -#ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR -# define CATCH_AUTO_PTR( T ) std::unique_ptr<T> -#else -# define CATCH_AUTO_PTR( T ) std::auto_ptr<T> +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #endif +// end catch_compiler_capabilities.h #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) #ifdef CATCH_CONFIG_COUNTER @@ -376,95 +471,48 @@ # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) #endif -#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr -#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) +#include <iosfwd> +#include <string> +#include <cstdint> -#include <sstream> -#include <algorithm> +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); namespace Catch { - struct IConfig; - struct CaseSensitive { enum Choice { Yes, No }; }; class NonCopyable { -#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; NonCopyable& operator = ( NonCopyable && ) = delete; -#else - NonCopyable( NonCopyable const& info ); - NonCopyable& operator = ( NonCopyable const& ); -#endif protected: - NonCopyable() {} + NonCopyable(); virtual ~NonCopyable(); }; - class SafeBool { - public: - typedef void (SafeBool::*type)() const; - - static type makeSafe( bool value ) { - return value ? &SafeBool::trueValue : 0; - } - private: - void trueValue() const {} - }; - - template<typename ContainerT> - void deleteAll( ContainerT& container ) { - typename ContainerT::const_iterator it = container.begin(); - typename ContainerT::const_iterator itEnd = container.end(); - for(; it != itEnd; ++it ) - delete *it; - } - template<typename AssociativeContainerT> - void deleteAllValues( AssociativeContainerT& container ) { - typename AssociativeContainerT::const_iterator it = container.begin(); - typename AssociativeContainerT::const_iterator itEnd = container.end(); - for(; it != itEnd; ++it ) - delete it->second; - } - - bool startsWith( std::string const& s, std::string const& prefix ); - bool startsWith( std::string const& s, char prefix ); - bool endsWith( std::string const& s, std::string const& suffix ); - bool endsWith( std::string const& s, char suffix ); - bool contains( std::string const& s, std::string const& infix ); - void toLowerInPlace( std::string& s ); - std::string toLower( std::string const& s ); - std::string trim( std::string const& str ); - bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); - - struct pluralise { - pluralise( std::size_t count, std::string const& label ); - - friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + struct SourceLineInfo { - std::size_t m_count; - std::string m_label; - }; + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} - struct SourceLineInfo { + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; - SourceLineInfo(); - SourceLineInfo( char const* _file, std::size_t _line ); -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - SourceLineInfo(SourceLineInfo const& other) = default; - SourceLineInfo( SourceLineInfo && ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo& operator = ( SourceLineInfo && ) = default; -# endif - bool empty() const; - bool operator == ( SourceLineInfo const& other ) const; - bool operator < ( SourceLineInfo const& other ) const; + bool empty() const noexcept { return file[0] == '\0'; } + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; char const* file; std::size_t line; @@ -472,24 +520,17 @@ namespace Catch { std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); - // This is just here to avoid compiler warnings with macro constants and boolean literals - inline bool isTrue( bool value ){ return value; } - inline bool alwaysTrue() { return true; } - inline bool alwaysFalse() { return false; } - - void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); - - void seedRng( IConfig const& config ); - unsigned int rngSeed(); + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as // >> stuff +StreamEndStop struct StreamEndStop { - std::string operator+() { - return std::string(); - } + std::string operator+() const; }; template<typename T> T const& operator + ( T const& value, StreamEndStop ) { @@ -497,363 +538,812 @@ namespace Catch { } } -#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) ) -#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) ) +// end catch_common.h namespace Catch { - class NotImplementedException : public std::exception - { - public: - NotImplementedException( SourceLineInfo const& lineInfo ); - - virtual ~NotImplementedException() CATCH_NOEXCEPT {} - - virtual const char* what() const CATCH_NOEXCEPT; - - private: - std::string m_what; - SourceLineInfo m_lineInfo; + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); }; } // end namespace Catch -/////////////////////////////////////////////////////////////////////////////// -#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) +#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 ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -// #included from: internal/catch_context.h -#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h -// #included from: catch_interfaces_generators.h -#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED +// start catch_interfaces_testcase.h -#include <string> +#include <vector> namespace Catch { - struct IGeneratorInfo { - virtual ~IGeneratorInfo(); - virtual bool moveNext() = 0; - virtual std::size_t getCurrentIndex() const = 0; + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); }; - struct IGeneratorsForTest { - virtual ~IGeneratorsForTest(); + class TestCase; + struct IConfig; - virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; - virtual bool moveNext() = 0; + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector<TestCase> const& getAllTests() const = 0; + virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0; }; - IGeneratorsForTest* createGeneratorsForTest(); + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); -} // end namespace Catch +} -// #included from: catch_ptr.hpp -#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED +// end catch_interfaces_testcase.h +// start catch_stringref.h -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif +#include <cstddef> +#include <string> +#include <iosfwd> +#include <cassert> namespace Catch { - // An intrusive reference counting smart pointer. - // T must implement addRef() and release() methods - // typically implementing the IShared interface - template<typename T> - class Ptr { + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { public: - Ptr() : m_p( CATCH_NULL ){} - Ptr( T* p ) : m_p( p ){ - if( m_p ) - m_p->addRef(); + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); } - Ptr( Ptr const& other ) : m_p( other.m_p ){ - if( m_p ) - m_p->addRef(); + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); } - ~Ptr(){ - if( m_p ) - m_p->release(); + + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; } - void reset() { - if( m_p ) - m_p->release(); - m_p = CATCH_NULL; + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; } - Ptr& operator = ( T* p ){ - Ptr temp( p ); - swap( temp ); - return *this; + constexpr auto size() const noexcept -> size_type { + return m_size; } - Ptr& operator = ( Ptr const& other ){ - Ptr temp( other ); - swap( temp ); - return *this; + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; + + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; + + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; + + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; } - void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } - T* get() const{ return m_p; } - T& operator*() const { return *m_p; } - T* operator->() const { return m_p; } - bool operator !() const { return m_p == CATCH_NULL; } - operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } - private: - T* m_p; + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } }; - struct IShared : NonCopyable { - virtual ~IShared(); - virtual void addRef() const = 0; - virtual void release() const = 0; - }; + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; - template<typename T = IShared> - struct SharedImpl : T { + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch - SharedImpl() : m_rc( 0 ){} +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} - virtual void addRef() const { - ++m_rc; - } - virtual void release() const { - if( --m_rc == 0 ) - delete this; - } +// end catch_stringref.h +// start catch_preprocessor.hpp - mutable unsigned int m_rc; - }; -} // end namespace Catch +#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__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) -#ifdef __clang__ -#pragma clang diagnostic pop +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) #endif -namespace Catch { +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif - class TestCase; - class Stream; - struct IResultCapture; - struct IRunner; - struct IGeneratorsForTest; - struct IConfig; +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) - struct IContext - { - virtual ~IContext(); +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) - virtual IResultCapture* getResultCapture() = 0; - virtual IRunner* getRunner() = 0; - virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; - virtual bool advanceGeneratorsForCurrentTest() = 0; - virtual Ptr<IConfig const> getConfig() const = 0; - }; +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>()) +#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<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif - struct IMutableContext : IContext - { - virtual ~IMutableContext(); - virtual void setResultCapture( IResultCapture* resultCapture ) = 0; - virtual void setRunner( IRunner* runner ) = 0; - virtual void setConfig( Ptr<IConfig const> const& config ) = 0; - }; +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template<typename...> struct TypeList {};\ + template<typename...Ts>\ + constexpr auto get_wrapper() noexcept -> TypeList<Ts...> { return {}; }\ + template<template<typename...> class...> struct TemplateTypeList{};\ + template<template<typename...> class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList<Cs...> { return {}; }\ + template<typename...>\ + struct append;\ + template<typename...>\ + struct rewrap;\ + template<template<typename...> class, typename...>\ + struct create;\ + template<template<typename...> class, typename>\ + struct convert;\ + \ + template<typename T> \ + struct append<T> { using type = T; };\ + template< template<typename...> class L1, typename...E1, template<typename...> class L2, typename...E2, typename...Rest>\ + struct append<L1<E1...>, L2<E2...>, Rest...> { using type = typename append<L1<E1...,E2...>, Rest...>::type; };\ + template< template<typename...> class L1, typename...E1, typename...Rest>\ + struct append<L1<E1...>, TypeList<mpl_::na>, Rest...> { using type = L1<E1...>; };\ + \ + template< template<typename...> class Container, template<typename...> class List, typename...elems>\ + struct rewrap<TemplateTypeList<Container>, List<elems...>> { using type = TypeList<Container<elems...>>; };\ + template< template<typename...> class Container, template<typename...> class List, class...Elems, typename...Elements>\ + struct rewrap<TemplateTypeList<Container>, List<Elems...>, Elements...> { using type = typename append<TypeList<Container<Elems...>>, typename rewrap<TemplateTypeList<Container>, Elements...>::type>::type; };\ + \ + template<template <typename...> class Final, template< typename...> class...Containers, typename...Types>\ + struct create<Final, TemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<TemplateTypeList<Containers>, Types...>::type...>::type; };\ + template<template <typename...> class Final, template <typename...> class List, typename...Ts>\ + struct convert<Final, List<Ts...>> { using type = typename append<Final<>,TypeList<Ts>...>::type; }; + +#define INTERNAL_CATCH_NTTP_1(signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> struct Nttp{};\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \ + template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...> struct NttpTemplateTypeList{};\ + template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Cs>\ + constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList<Cs...> { return {}; } \ + \ + template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>> { using type = TypeList<Container<__VA_ARGS__>>; };\ + template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature), typename...Elements>\ + struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>, Elements...> { using type = typename append<TypeList<Container<__VA_ARGS__>>, typename rewrap<NttpTemplateTypeList<Container>, Elements...>::type>::type; };\ + template<template <typename...> class Final, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Containers, typename...Types>\ + struct create<Final, NttpTemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<NttpTemplateTypeList<Containers>, Types...>::type...>::type; }; + +#define INTERNAL_CATCH_DECLARE_SIG_TEST0(TestName) +#define INTERNAL_CATCH_DECLARE_SIG_TEST1(TestName, signature)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() +#define INTERNAL_CATCH_DECLARE_SIG_TEST_X(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() + +#define INTERNAL_CATCH_DEFINE_SIG_TEST0(TestName) +#define INTERNAL_CATCH_DEFINE_SIG_TEST1(TestName, signature)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() +#define INTERNAL_CATCH_DEFINE_SIG_TEST_X(TestName, signature,...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() + +#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)\ + template<typename Type>\ + void reg_test(TypeList<Type>, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<Type>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + } - IContext& getCurrentContext(); - IMutableContext& getCurrentMutableContext(); - void cleanUpContext(); - Stream createStream( std::string const& streamName ); +#define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<__VA_ARGS__>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + } -} +#define INTERNAL_CATCH_NTTP_REGISTER_METHOD0(TestName, signature, ...)\ + template<typename Type>\ + void reg_test(TypeList<Type>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestName<Type>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\ + } -// #included from: internal/catch_test_registry.hpp -#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED +#define INTERNAL_CATCH_NTTP_REGISTER_METHOD(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + void reg_test(Nttp<__VA_ARGS__>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestName<__VA_ARGS__>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\ + } -// #included from: catch_interfaces_testcase.h -#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0(TestName, ClassName) +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1(TestName, ClassName, signature)\ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<TestType> { \ + void test();\ + } -#include <vector> +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X(TestName, ClassName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<__VA_ARGS__> { \ + void test();\ + } -namespace Catch { +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0(TestName) +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1(TestName, signature)\ + template<typename TestType> \ + void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<TestType>::test() +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \ + void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<__VA_ARGS__>::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_NTTP_0 +#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__),INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_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_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__) +#else +#define INTERNAL_CATCH_NTTP_0(signature) +#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1,INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_0)( __VA_ARGS__)) +#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_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__)) +#endif - class TestSpec; +// end catch_preprocessor.hpp +// start catch_meta.hpp - struct ITestCase : IShared { - virtual void invoke () const = 0; - protected: - virtual ~ITestCase(); - }; - class TestCase; - struct IConfig; +#include <type_traits> - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); - virtual std::vector<TestCase> const& getAllTests() const = 0; - virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0; +namespace Catch { + template<typename T> + struct always_false : std::false_type {}; + + template <typename> struct true_given : std::true_type {}; + struct is_callable_tester { + template <typename Fun, typename... Args> + true_given<decltype(std::declval<Fun>()(std::declval<Args>()...))> static test(int); + template <typename...> + std::false_type static test(...); }; - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); + template <typename T> + struct is_callable; + + template <typename Fun, typename... Args> + struct is_callable<Fun(Args...)> : decltype(is_callable_tester::test<Fun, Args...>(0)) {}; +#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 + // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is + // replaced with std::invoke_result here. + template <typename Func, typename... U> + using FunctionReturnType = std::remove_reference_t<std::remove_cv_t<std::invoke_result_t<Func, U...>>>; +#else + // Keep ::type here because we still support C++11 + template <typename Func, typename... U> + using FunctionReturnType = typename std::remove_reference<typename std::remove_cv<typename std::result_of<Func(U...)>::type>::type>::type; +#endif + +} // namespace Catch + +namespace mpl_{ + struct na; } +// end catch_meta.hpp namespace Catch { template<typename C> -class MethodTestCase : public SharedImpl<ITestCase> { - +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); public: - MethodTestCase( void (C::*method)() ) : m_method( method ) {} + TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} - virtual void invoke() const { + void invoke() const override { C obj; - (obj.*m_method)(); + (obj.*m_testAsMethod)(); } - -private: - virtual ~MethodTestCase() {} - - void (C::*m_method)(); }; -typedef void(*TestFunction)(); +auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*; -struct NameAndDesc { - NameAndDesc( const char* _name = "", const char* _description= "" ) - : name( _name ), description( _description ) - {} +template<typename C> +auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsMethod<C>( testAsMethod ); +} - const char* name; - const char* description; +struct NameAndTags { + NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept; + StringRef name; + StringRef tags; }; -void registerTestCase - ( ITestCase* testCase, - char const* className, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ); - -struct AutoReg { - - AutoReg - ( TestFunction function, - SourceLineInfo const& lineInfo, - NameAndDesc const& nameAndDesc ); - - template<typename C> - AutoReg - ( void (C::*method)(), - char const* className, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ) { +struct AutoReg : NonCopyable { + AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept; + ~AutoReg(); +}; - registerTestCase - ( new MethodTestCase<C>( method ), - className, - nameAndDesc, - lineInfo ); - } +} // end namespace Catch - ~AutoReg(); +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( TestName, TestFunc, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... ) \ + namespace{ \ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature));\ + } \ + } \ + INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) + #endif -private: - AutoReg( AutoReg const& ); - void operator= ( AutoReg const& ); -}; + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) ) + #endif -void registerTestCaseFunction - ( TestFunction function, - SourceLineInfo const& lineInfo, - NameAndDesc const& nameAndDesc ); + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) + #endif -} // end namespace Catch + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) + #endif +#endif -#ifdef CATCH_CONFIG_VARIADIC_MACROS /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ static void TestName(); \ - CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ - CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ - CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ \ - struct TestName : ClassName{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ void test(); \ }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); /* NOLINT */ \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ } \ - CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ void TestName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ - CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ - Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -#else /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ - static void TestName(); \ - CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ - static void TestName() - #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ - INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, Signature, ... )\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + INTERNAL_CATCH_DECLARE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature));\ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){\ + INTERNAL_CATCH_TYPE_GEN\ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + INTERNAL_CATCH_NTTP_REG_GEN(TestFunc,INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + template<typename...Types> \ + struct TestName{\ + TestName(){\ + int index = 0; \ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\ + using expander = int[];\ + (void)expander{(reg_test(Types{}, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ + }\ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + TestName<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();\ + return 0;\ + }();\ + }\ + }\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc,INTERNAL_CATCH_REMOVE_PARENS(Signature)) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) +#endif - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ - CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) ) +#endif - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ - CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ - namespace{ \ - struct TestCaseName : ClassName{ \ - void test(); \ - }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); /* NOLINT */ \ - } \ - CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ - void TestCaseName::test() - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ - INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, Signature, TmplTypes, TypesList) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + template<typename TestType> static void TestFuncName(); \ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_TYPE_GEN \ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ + template<typename... Types> \ + struct TestName { \ + void reg_tests() { \ + int index = 0; \ + using expander = int[]; \ + 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<Types> ), 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 */\ + } \ + }; \ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + using TestInit = typename create<TestName, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type; \ + TestInit t; \ + t.reg_tests(); \ + return 0; \ + }(); \ + } \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template<typename TestType> \ + static void TestFuncName() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename T,__VA_ARGS__) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, typename T, __VA_ARGS__ ) ) +#endif - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ - CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ - Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, Signature, __VA_ARGS__ ) ) +#endif + #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(TestName, TestFunc, Name, Tags, TmplList)\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + template<typename TestType> static void TestFunc(); \ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){\ + INTERNAL_CATCH_TYPE_GEN\ + template<typename... Types> \ + struct TestName { \ + void reg_tests() { \ + int index = 0; \ + using expander = int[]; \ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\ + } \ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + using TestInit = typename convert<TestName, TmplList>::type; \ + TestInit t; \ + t.reg_tests(); \ + return 0; \ + }(); \ + }}\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template<typename TestType> \ + static void TestFunc() + + #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(Name, Tags, TmplList) \ + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), Name, Tags, TmplList ) + + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \ + INTERNAL_CATCH_TYPE_GEN\ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature));\ + INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + template<typename...Types> \ + struct TestNameClass{\ + TestNameClass(){\ + int index = 0; \ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\ + using expander = int[];\ + (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ + }\ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + TestNameClass<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();\ + return 0;\ + }();\ + }\ + }\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) #endif -// #included from: internal/catch_capture.hpp -#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____C_L_A_S_S____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList)\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \ + void test();\ + };\ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestNameClass) {\ + INTERNAL_CATCH_TYPE_GEN \ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + template<typename...Types>\ + struct TestNameClass{\ + void reg_tests(){\ + int index = 0;\ + using expander = int[];\ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ + constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ + constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + "<" + std::string(types_list[index % num_types]) + ">", Tags } ), index++)... };/* NOLINT */ \ + }\ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + using TestInit = typename create<TestNameClass, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type;\ + TestInit t;\ + t.reg_tests();\ + return 0;\ + }(); \ + }\ + }\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template<typename TestType> \ + void TestName<TestType>::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, typename T, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, typename T,__VA_ARGS__ ) ) +#endif -// #included from: catch_result_builder.h -#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, Signature, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) ) +#endif -// #included from: catch_result_type.h -#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, TmplList) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \ + void test();\ + };\ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \ + INTERNAL_CATCH_TYPE_GEN\ + template<typename...Types>\ + struct TestNameClass{\ + void reg_tests(){\ + int index = 0;\ + using expander = int[];\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \ + }\ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + using TestInit = typename convert<TestNameClass, TmplList>::type;\ + TestInit t;\ + t.reg_tests();\ + return 0;\ + }(); \ + }}\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template<typename TestType> \ + void TestName<TestType>::test() + +#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList) \ + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____ ), INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_M_P_L_A_T_E____T_E_S_T____F_U_N_C____ ), ClassName, Name, Tags, TmplList ) + +// end catch_test_registry.h +// start catch_capture.hpp + +// start catch_assertionhandler.h + +// start catch_assertioninfo.h + +// start catch_result_type.h namespace Catch { @@ -878,12 +1368,8 @@ namespace Catch { }; }; - inline bool isOk( ResultWas::OfType resultType ) { - return ( resultType & ResultWas::FailureBit ) == 0; - } - inline bool isJustInfo( int flags ) { - return flags == ResultWas::Info; - } + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); // ResultDisposition::Flags enum struct ResultDisposition { enum Flags { @@ -894,547 +1380,123 @@ namespace Catch { SuppressFail = 0x08 // Failures are reported but do not fail the test }; }; - inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { - return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) ); - } + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); - inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } - inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } - inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + bool shouldContinueOnFailure( int flags ); + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ); } // end namespace Catch -// #included from: catch_assertionresult.h -#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED - -#include <string> - +// end catch_result_type.h namespace Catch { - struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; - - struct DecomposedExpression - { - virtual ~DecomposedExpression() {} - virtual bool isBinaryExpression() const { - return false; - } - virtual void reconstructExpression( std::string& dest ) const = 0; - - // Only simple binary comparisons can be decomposed. - // If more complex check is required then wrap sub-expressions in parentheses. - template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( T const& ); - template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( T const& ); - template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( T const& ); - template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( T const& ); - template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator % ( T const& ); - template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( T const& ); - template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( T const& ); - - private: - DecomposedExpression& operator = (DecomposedExpression const&); - }; - struct AssertionInfo { - AssertionInfo(); - AssertionInfo( char const * _macroName, - SourceLineInfo const& _lineInfo, - char const * _capturedExpression, - ResultDisposition::Flags _resultDisposition, - char const * _secondArg = ""); - - char const * macroName; + StringRef macroName; SourceLineInfo lineInfo; - char const * capturedExpression; + StringRef capturedExpression; ResultDisposition::Flags resultDisposition; - char const * secondArg; - }; - - struct AssertionResultData - { - AssertionResultData() : decomposedExpression( CATCH_NULL ) - , resultType( ResultWas::Unknown ) - , negated( false ) - , parenthesized( false ) {} - - void negate( bool parenthesize ) { - negated = !negated; - parenthesized = parenthesize; - if( resultType == ResultWas::Ok ) - resultType = ResultWas::ExpressionFailed; - else if( resultType == ResultWas::ExpressionFailed ) - resultType = ResultWas::Ok; - } - - std::string const& reconstructExpression() const { - if( decomposedExpression != CATCH_NULL ) { - decomposedExpression->reconstructExpression( reconstructedExpression ); - if( parenthesized ) { - reconstructedExpression.insert( 0, 1, '(' ); - reconstructedExpression.append( 1, ')' ); - } - if( negated ) { - reconstructedExpression.insert( 0, 1, '!' ); - } - decomposedExpression = CATCH_NULL; - } - return reconstructedExpression; - } - mutable DecomposedExpression const* decomposedExpression; - mutable std::string reconstructedExpression; - std::string message; - ResultWas::OfType resultType; - bool negated; - bool parenthesized; - }; - - class AssertionResult { - public: - AssertionResult(); - AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); - ~AssertionResult(); -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - AssertionResult( AssertionResult const& ) = default; - AssertionResult( AssertionResult && ) = default; - AssertionResult& operator = ( AssertionResult const& ) = default; - AssertionResult& operator = ( AssertionResult && ) = default; -# endif - - bool isOk() const; - bool succeeded() const; - ResultWas::OfType getResultType() const; - bool hasExpression() const; - bool hasMessage() const; - std::string getExpression() const; - std::string getExpressionInMacro() const; - bool hasExpandedExpression() const; - std::string getExpandedExpression() const; - std::string getMessage() const; - SourceLineInfo getSourceInfo() const; - std::string getTestMacroName() const; - void discardDecomposedExpression() const; - void expandDecomposedExpression() const; - - protected: - AssertionInfo m_info; - AssertionResultData m_resultData; + // We want to delete this constructor but a compiler bug in 4.8 means + // the struct is then treated as non-aggregate + //AssertionInfo() = delete; }; } // end namespace Catch -// #included from: catch_matchers.hpp -#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED - -namespace Catch { -namespace Matchers { - namespace Impl { - - template<typename ArgT> struct MatchAllOf; - template<typename ArgT> struct MatchAnyOf; - template<typename ArgT> struct MatchNotOf; - - class MatcherUntypedBase { - public: - std::string toString() const { - if( m_cachedToString.empty() ) - m_cachedToString = describe(); - return m_cachedToString; - } - - protected: - virtual ~MatcherUntypedBase(); - virtual std::string describe() const = 0; - mutable std::string m_cachedToString; - private: - MatcherUntypedBase& operator = ( MatcherUntypedBase const& ); - }; - - template<typename ObjectT> - struct MatcherMethod { - virtual bool match( ObjectT const& arg ) const = 0; - }; - template<typename PtrT> - struct MatcherMethod<PtrT*> { - virtual bool match( PtrT* arg ) const = 0; - }; - - template<typename ObjectT, typename ComparatorT = ObjectT> - struct MatcherBase : MatcherUntypedBase, MatcherMethod<ObjectT> { +// end catch_assertioninfo.h +// start catch_decomposer.h - MatchAllOf<ComparatorT> operator && ( MatcherBase const& other ) const; - MatchAnyOf<ComparatorT> operator || ( MatcherBase const& other ) const; - MatchNotOf<ComparatorT> operator ! () const; - }; +// start catch_tostring.h - template<typename ArgT> - struct MatchAllOf : MatcherBase<ArgT> { - virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if (!m_matchers[i]->match(arg)) - return false; - } - return true; - } - virtual std::string describe() const CATCH_OVERRIDE { - std::string description; - description.reserve( 4 + m_matchers.size()*32 ); - description += "( "; - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if( i != 0 ) - description += " and "; - description += m_matchers[i]->toString(); - } - description += " )"; - return description; - } - - MatchAllOf<ArgT>& operator && ( MatcherBase<ArgT> const& other ) { - m_matchers.push_back( &other ); - return *this; - } - - std::vector<MatcherBase<ArgT> const*> m_matchers; - }; - template<typename ArgT> - struct MatchAnyOf : MatcherBase<ArgT> { - - virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if (m_matchers[i]->match(arg)) - return true; - } - return false; - } - virtual std::string describe() const CATCH_OVERRIDE { - std::string description; - description.reserve( 4 + m_matchers.size()*32 ); - description += "( "; - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if( i != 0 ) - description += " or "; - description += m_matchers[i]->toString(); - } - description += " )"; - return description; - } - - MatchAnyOf<ArgT>& operator || ( MatcherBase<ArgT> const& other ) { - m_matchers.push_back( &other ); - return *this; - } - - std::vector<MatcherBase<ArgT> const*> m_matchers; - }; - - template<typename ArgT> - struct MatchNotOf : MatcherBase<ArgT> { - - MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} - - virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { - return !m_underlyingMatcher.match( arg ); - } - - virtual std::string describe() const CATCH_OVERRIDE { - return "not " + m_underlyingMatcher.toString(); - } - MatcherBase<ArgT> const& m_underlyingMatcher; - }; - - template<typename ObjectT, typename ComparatorT> - MatchAllOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator && ( MatcherBase const& other ) const { - return MatchAllOf<ComparatorT>() && *this && other; - } - template<typename ObjectT, typename ComparatorT> - MatchAnyOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator || ( MatcherBase const& other ) const { - return MatchAnyOf<ComparatorT>() || *this || other; - } - template<typename ObjectT, typename ComparatorT> - MatchNotOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator ! () const { - return MatchNotOf<ComparatorT>( *this ); - } - - } // namespace Impl - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - // - deprecated: prefer ||, && and ! - template<typename T> - Impl::MatchNotOf<T> Not( Impl::MatcherBase<T> const& underlyingMatcher ) { - return Impl::MatchNotOf<T>( underlyingMatcher ); - } - template<typename T> - Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) { - return Impl::MatchAllOf<T>() && m1 && m2; - } - template<typename T> - Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) { - return Impl::MatchAllOf<T>() && m1 && m2 && m3; - } - template<typename T> - Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) { - return Impl::MatchAnyOf<T>() || m1 || m2; - } - template<typename T> - Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) { - return Impl::MatchAnyOf<T>() || m1 || m2 || m3; - } - -} // namespace Matchers - -using namespace Matchers; -using Matchers::Impl::MatcherBase; +#include <vector> +#include <cstddef> +#include <type_traits> +#include <string> +// start catch_stream.h -} // namespace Catch +#include <iosfwd> +#include <cstddef> +#include <ostream> namespace Catch { - struct TestFailureException{}; + std::ostream& cout(); + std::ostream& cerr(); + std::ostream& clog(); - template<typename T> class ExpressionLhs; + class StringRef; - struct CopyableStream { - CopyableStream() {} - CopyableStream( CopyableStream const& other ) { - oss << other.oss.str(); - } - CopyableStream& operator=( CopyableStream const& other ) { - oss.str(std::string()); - oss << other.oss.str(); - return *this; - } - std::ostringstream oss; + struct IStream { + virtual ~IStream(); + virtual std::ostream& stream() const = 0; }; - class ResultBuilder : public DecomposedExpression { + auto makeStream( StringRef const &filename ) -> IStream const*; + + class ReusableStringStream : NonCopyable { + std::size_t m_index; + std::ostream* m_oss; public: - ResultBuilder( char const* macroName, - SourceLineInfo const& lineInfo, - char const* capturedExpression, - ResultDisposition::Flags resultDisposition, - char const* secondArg = "" ); - ~ResultBuilder(); + ReusableStringStream(); + ~ReusableStringStream(); - template<typename T> - ExpressionLhs<T const&> operator <= ( T const& operand ); - ExpressionLhs<bool> operator <= ( bool value ); + auto str() const -> std::string; template<typename T> - ResultBuilder& operator << ( T const& value ) { - stream().oss << value; + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; return *this; } - - ResultBuilder& setResultType( ResultWas::OfType result ); - ResultBuilder& setResultType( bool result ); - - void endExpression( DecomposedExpression const& expr ); - - virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE; - - AssertionResult build() const; - AssertionResult build( DecomposedExpression const& expr ) const; - - void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); - void captureResult( ResultWas::OfType resultType ); - void captureExpression(); - void captureExpectedException( std::string const& expectedMessage ); - void captureExpectedException( Matchers::Impl::MatcherBase<std::string> const& matcher ); - void handleResult( AssertionResult const& result ); - void react(); - bool shouldDebugBreak() const; - bool allowThrows() const; - - template<typename ArgT, typename MatcherT> - void captureMatch( ArgT const& arg, MatcherT const& matcher, char const* matcherString ); - - void setExceptionGuard(); - void unsetExceptionGuard(); - - private: - AssertionInfo m_assertionInfo; - AssertionResultData m_data; - - CopyableStream &stream() - { - if(!m_usedStream) - { - m_usedStream = true; - m_stream().oss.str(""); - } - return m_stream(); - } - - static CopyableStream &m_stream() - { - static CopyableStream s; - return s; - } - - bool m_shouldDebugBreak; - bool m_shouldThrow; - bool m_guardException; - bool m_usedStream; + auto get() -> std::ostream& { return *m_oss; } }; +} -} // namespace Catch - -// Include after due to circular dependency: -// #included from: catch_expression_lhs.hpp -#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED - -// #included from: catch_evaluate.hpp -#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED +// end catch_stream.h +// start catch_interfaces_enum_values_registry.h -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4389) // '==' : signed/unsigned mismatch -#pragma warning(disable:4018) // more "signed/unsigned mismatch" -#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) -#endif - -#include <cstddef> +#include <vector> namespace Catch { -namespace Internal { - enum Operator { - IsEqualTo, - IsNotEqualTo, - IsLessThan, - IsGreaterThan, - IsLessThanOrEqualTo, - IsGreaterThanOrEqualTo - }; + namespace Detail { + struct EnumInfo { + StringRef m_name; + std::vector<std::pair<int, StringRef>> m_values; - template<Operator Op> struct OperatorTraits { static const char* getName(){ return "*error*"; } }; - template<> struct OperatorTraits<IsEqualTo> { static const char* getName(){ return "=="; } }; - template<> struct OperatorTraits<IsNotEqualTo> { static const char* getName(){ return "!="; } }; - template<> struct OperatorTraits<IsLessThan> { static const char* getName(){ return "<"; } }; - template<> struct OperatorTraits<IsGreaterThan> { static const char* getName(){ return ">"; } }; - template<> struct OperatorTraits<IsLessThanOrEqualTo> { static const char* getName(){ return "<="; } }; - template<> struct OperatorTraits<IsGreaterThanOrEqualTo>{ static const char* getName(){ return ">="; } }; + ~EnumInfo(); - template<typename T> - T& removeConst(T const &t) { return const_cast<T&>(t); } -#ifdef CATCH_CONFIG_CPP11_NULLPTR - inline std::nullptr_t removeConst(std::nullptr_t) { return nullptr; } -#endif + StringRef lookup( int value ) const; + }; + } // namespace Detail - // So the compare overloads can be operator agnostic we convey the operator as a template - // enum, which is used to specialise an Evaluator for doing the comparison. - template<typename T1, typename T2, Operator Op> - struct Evaluator{}; + struct IMutableEnumValuesRegistry { + virtual ~IMutableEnumValuesRegistry(); - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsEqualTo> { - static bool evaluate( T1 const& lhs, T2 const& rhs) { - return bool(removeConst(lhs) == removeConst(rhs) ); - } - }; - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsNotEqualTo> { - static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return bool(removeConst(lhs) != removeConst(rhs) ); - } - }; - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsLessThan> { - static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return bool(removeConst(lhs) < removeConst(rhs) ); - } - }; - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsGreaterThan> { - static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return bool(removeConst(lhs) > removeConst(rhs) ); - } - }; - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsGreaterThanOrEqualTo> { - static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return bool(removeConst(lhs) >= removeConst(rhs) ); - } - }; - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsLessThanOrEqualTo> { - static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return bool(removeConst(lhs) <= removeConst(rhs) ); - } - }; + virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values ) = 0; - // Special case for comparing a pointer to an int (deduced for p==0) - template<typename T> - struct Evaluator<int const&, T* const&, IsEqualTo> { - static bool evaluate( int lhs, T* rhs) { - return reinterpret_cast<void const*>( lhs ) == rhs; - } - }; - template<typename T> - struct Evaluator<T* const&, int const&, IsEqualTo> { - static bool evaluate( T* lhs, int rhs) { - return lhs == reinterpret_cast<void const*>( rhs ); - } - }; - template<typename T> - struct Evaluator<int const&, T* const&, IsNotEqualTo> { - static bool evaluate( int lhs, T* rhs) { - return reinterpret_cast<void const*>( lhs ) != rhs; - } - }; - template<typename T> - struct Evaluator<T* const&, int const&, IsNotEqualTo> { - static bool evaluate( T* lhs, int rhs) { - return lhs != reinterpret_cast<void const*>( rhs ); + template<typename E> + Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list<E> values ) { + static_assert(sizeof(int) >= sizeof(E), "Cannot serialize enum to int"); + std::vector<int> intValues; + intValues.reserve( values.size() ); + for( auto enumValue : values ) + intValues.push_back( static_cast<int>( enumValue ) ); + return registerEnum( enumName, allEnums, intValues ); } }; - template<typename T> - struct Evaluator<long const&, T* const&, IsEqualTo> { - static bool evaluate( long lhs, T* rhs) { - return reinterpret_cast<void const*>( lhs ) == rhs; - } - }; - template<typename T> - struct Evaluator<T* const&, long const&, IsEqualTo> { - static bool evaluate( T* lhs, long rhs) { - return lhs == reinterpret_cast<void const*>( rhs ); - } - }; - template<typename T> - struct Evaluator<long const&, T* const&, IsNotEqualTo> { - static bool evaluate( long lhs, T* rhs) { - return reinterpret_cast<void const*>( lhs ) != rhs; - } - }; - template<typename T> - struct Evaluator<T* const&, long const&, IsNotEqualTo> { - static bool evaluate( T* lhs, long rhs) { - return lhs != reinterpret_cast<void const*>( rhs ); - } - }; +} // Catch -} // end of namespace Internal -} // end of namespace Catch +// end catch_interfaces_enum_values_registry.h -#ifdef _MSC_VER -#pragma warning(pop) +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include <string_view> #endif -// #included from: catch_tostring.h -#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED - -#include <sstream> -#include <iomanip> -#include <limits> -#include <vector> -#include <cstddef> - #ifdef __OBJC__ -// #included from: catch_objc_arc.hpp -#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED +// start catch_objc_arc.hpp #import <Foundation/Foundation.h> @@ -1476,863 +1538,1319 @@ inline id performOptionalSelector( id obj, SEL sel ) { #define CATCH_ARC_STRONG __strong #endif +// end catch_objc_arc.hpp #endif -#ifdef CATCH_CONFIG_CPP11_TUPLE -#include <tuple> -#endif - -#ifdef CATCH_CONFIG_CPP11_IS_ENUM -#include <type_traits> +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless #endif namespace Catch { + namespace Detail { -// Why we're here. -template<typename T> -std::string toString( T const& value ); + extern const std::string unprintableString; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template<typename T> + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } -// Built in overloads + template<typename T> + class IsStreamInsertable { + template<typename Stream, typename U> + static auto test(int) + -> decltype(std::declval<Stream&>() << std::declval<U>(), std::true_type()); -std::string toString( std::string const& value ); -std::string toString( std::wstring const& value ); -std::string toString( const char* const value ); -std::string toString( char* const value ); -std::string toString( const wchar_t* const value ); -std::string toString( wchar_t* const value ); -std::string toString( int value ); -std::string toString( unsigned long value ); -std::string toString( unsigned int value ); -std::string toString( const double value ); -std::string toString( const float value ); -std::string toString( bool value ); -std::string toString( char value ); -std::string toString( signed char value ); -std::string toString( unsigned char value ); + template<typename, typename> + static auto test(...)->std::false_type; -#ifdef CATCH_CONFIG_CPP11_LONG_LONG -std::string toString( long long value ); -std::string toString( unsigned long long value ); + public: + static const bool value = decltype(test<std::ostream, const T&>(0))::value; + }; + + template<typename E> + std::string convertUnknownEnumToString( E e ); + + template<typename T> + typename std::enable_if< + !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value, + std::string>::type convertUnstreamable( T const& ) { + return Detail::unprintableString; + } + template<typename T> + typename std::enable_if< + !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value, + std::string>::type convertUnstreamable(T const& ex) { + return ex.what(); + } + + template<typename T> + typename std::enable_if< + std::is_enum<T>::value + , std::string>::type convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template<typename T> + std::string clrReferenceToString( T^ ref ) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr<System::Byte> p = &bytes[0]; + return std::string(reinterpret_cast<char const *>(p), bytes->Length); + } #endif -#ifdef CATCH_CONFIG_CPP11_NULLPTR -std::string toString( std::nullptr_t ); + } // namespace Detail + + // If we decide for C++14, change these to enable_if_ts + template <typename T, typename = void> + struct StringMaker { + template <typename Fake = T> + static + typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type + convert(const Fake& value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template <typename Fake = T> + static + typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type + convert( const Fake& value ) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); #endif + } + }; -#ifdef __OBJC__ - std::string toString( NSString const * const& nsstring ); - std::string toString( NSString * CATCH_ARC_STRONG & nsstring ); - std::string toString( NSObject* const& nsObject ); + namespace Detail { + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template <typename T> + std::string stringify(const T& e) { + return ::Catch::StringMaker<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e); + } + + template<typename E> + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<E>::type>(e)); + } + +#if defined(_MANAGED) + template <typename T> + std::string stringify( T^ e ) { + return ::Catch::StringMaker<T^>::convert(e); + } #endif -namespace Detail { + } // namespace Detail - extern const std::string unprintableString; + // Some predefined specializations - #if !defined(CATCH_CONFIG_CPP11_STREAM_INSERTABLE_CHECK) - struct BorgType { - template<typename T> BorgType( T const& ); + template<> + struct StringMaker<std::string> { + static std::string convert(const std::string& str); }; - struct TrueType { char sizer[1]; }; - struct FalseType { char sizer[2]; }; - - TrueType& testStreamable( std::ostream& ); - FalseType testStreamable( FalseType ); +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker<std::string_view> { + static std::string convert(std::string_view str); + }; +#endif - FalseType operator<<( std::ostream const&, BorgType const& ); + template<> + struct StringMaker<char const *> { + static std::string convert(char const * str); + }; + template<> + struct StringMaker<char *> { + static std::string convert(char * str); + }; - template<typename T> - struct IsStreamInsertable { - static std::ostream &s; - static T const&t; - enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; +#ifdef CATCH_CONFIG_WCHAR + template<> + struct StringMaker<std::wstring> { + static std::string convert(const std::wstring& wstr); }; -#else - template<typename T> - class IsStreamInsertable { - template<typename SS, typename TT> - static auto test(int) - -> decltype( std::declval<SS&>() << std::declval<TT>(), std::true_type() ); - template<typename, typename> - static auto test(...) -> std::false_type; +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker<std::wstring_view> { + static std::string convert(std::wstring_view str); + }; +# endif - public: - static const bool value = decltype(test<std::ostream,const T&>(0))::value; + template<> + struct StringMaker<wchar_t const *> { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker<wchar_t *> { + static std::string convert(wchar_t * str); }; #endif -#if defined(CATCH_CONFIG_CPP11_IS_ENUM) - template<typename T, - bool IsEnum = std::is_enum<T>::value - > - struct EnumStringMaker - { - static std::string convert( T const& ) { return unprintableString; } + // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, + // while keeping string semantics? + template<int SZ> + struct StringMaker<char[SZ]> { + static std::string convert(char const* str) { + return ::Catch::Detail::stringify(std::string{ str }); + } }; - - template<typename T> - struct EnumStringMaker<T,true> - { - static std::string convert( T const& v ) - { - return ::Catch::toString( - static_cast<typename std::underlying_type<T>::type>(v) - ); + template<int SZ> + struct StringMaker<signed char[SZ]> { + static std::string convert(signed char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) }); } }; -#endif - template<bool C> - struct StringMakerBase { -#if defined(CATCH_CONFIG_CPP11_IS_ENUM) - template<typename T> - static std::string convert( T const& v ) - { - return EnumStringMaker<T>::convert( v ); + template<int SZ> + struct StringMaker<unsigned char[SZ]> { + static std::string convert(unsigned char const* str) { + return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) }); } -#else - template<typename T> - static std::string convert( T const& ) { return unprintableString; } -#endif }; +#if defined(CATCH_CONFIG_CPP17_BYTE) template<> - struct StringMakerBase<true> { - template<typename T> - static std::string convert( T const& _value ) { - std::ostringstream oss; - oss << _value; - return oss.str(); - } + struct StringMaker<std::byte> { + static std::string convert(std::byte value); + }; +#endif // defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker<int> { + static std::string convert(int value); + }; + template<> + struct StringMaker<long> { + static std::string convert(long value); + }; + template<> + struct StringMaker<long long> { + static std::string convert(long long value); + }; + template<> + struct StringMaker<unsigned int> { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker<unsigned long> { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker<unsigned long long> { + static std::string convert(unsigned long long value); }; - std::string rawMemoryToString( const void *object, std::size_t size ); + template<> + struct StringMaker<bool> { + static std::string convert(bool b); + }; - template<typename T> - std::string rawMemoryToString( const T& object ) { - return rawMemoryToString( &object, sizeof(object) ); - } + template<> + struct StringMaker<char> { + static std::string convert(char c); + }; + template<> + struct StringMaker<signed char> { + static std::string convert(signed char c); + }; + template<> + struct StringMaker<unsigned char> { + static std::string convert(unsigned char c); + }; -} // end namespace Detail + template<> + struct StringMaker<std::nullptr_t> { + static std::string convert(std::nullptr_t); + }; -template<typename T> -struct StringMaker : - Detail::StringMakerBase<Detail::IsStreamInsertable<T>::value> {}; + template<> + struct StringMaker<float> { + static std::string convert(float value); + static int precision; + }; -template<typename T> -struct StringMaker<T*> { - template<typename U> - static std::string convert( U* p ) { - if( !p ) - return "NULL"; - else - return Detail::rawMemoryToString( p ); - } -}; + template<> + struct StringMaker<double> { + static std::string convert(double value); + static int precision; + }; -template<typename R, typename C> -struct StringMaker<R C::*> { - static std::string convert( R C::* p ) { - if( !p ) - return "NULL"; - else - return Detail::rawMemoryToString( p ); + template <typename T> + struct StringMaker<T*> { + template <typename U> + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template <typename R, typename C> + struct StringMaker<R C::*> { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template <typename T> + struct StringMaker<T^> { + static std::string convert( T^ ref ) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template<typename InputIterator, typename Sentinel = InputIterator> + std::string rangeToString(InputIterator first, Sentinel last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } } -}; -namespace Detail { - template<typename InputIterator> - std::string rangeToString( InputIterator first, InputIterator last ); -} - -//template<typename T, typename Allocator> -//struct StringMaker<std::vector<T, Allocator> > { -// static std::string convert( std::vector<T,Allocator> const& v ) { -// return Detail::rangeToString( v.begin(), v.end() ); -// } -//}; - -template<typename T, typename Allocator> -std::string toString( std::vector<T,Allocator> const& v ) { - return Detail::rangeToString( v.begin(), v.end() ); -} - -#ifdef CATCH_CONFIG_CPP11_TUPLE - -// toString for tuples -namespace TupleDetail { - template< - typename Tuple, - std::size_t N = 0, - bool = (N < std::tuple_size<Tuple>::value) - > - struct ElementPrinter { - static void print( const Tuple& tuple, std::ostream& os ) - { - os << ( N ? ", " : " " ) - << Catch::toString(std::get<N>(tuple)); - ElementPrinter<Tuple,N+1>::print(tuple,os); - } - }; +#ifdef __OBJC__ + template<> + struct StringMaker<NSString*> { + static std::string convert(NSString * nsstring) { + if (!nsstring) + return "nil"; + return std::string("@") + [nsstring UTF8String]; + } + }; + template<> + struct StringMaker<NSObject*> { + static std::string convert(NSObject* nsObject) { + return ::Catch::Detail::stringify([nsObject description]); + } - template< - typename Tuple, - std::size_t N - > - struct ElementPrinter<Tuple,N,false> { - static void print( const Tuple&, std::ostream& ) {} - }; + }; + namespace Detail { + inline std::string stringify( NSString* nsstring ) { + return StringMaker<NSString*>::convert( nsstring ); + } -} + } // namespace Detail +#endif // __OBJC__ -template<typename ...Types> -struct StringMaker<std::tuple<Types...>> { +} // namespace Catch - static std::string convert( const std::tuple<Types...>& tuple ) - { - std::ostringstream os; - os << '{'; - TupleDetail::ElementPrinter<std::tuple<Types...>>::print( tuple, os ); - os << " }"; - return os.str(); - } -}; -#endif // CATCH_CONFIG_CPP11_TUPLE +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in -namespace Detail { - template<typename T> - std::string makeString( T const& value ) { - return StringMaker<T>::convert( value ); - } -} // end namespace Detail +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER +#endif -/// \brief converts any type to a string -/// -/// The default template forwards on to ostringstream - except when an -/// ostringstream overload does not exist - in which case it attempts to detect -/// that and writes {?}. -/// Overload (not specialise) this template for custom typs that you don't want -/// to provide an ostream overload for. -template<typename T> -std::string toString( T const& value ) { - return StringMaker<T>::convert( value ); +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include <utility> +namespace Catch { + template<typename T1, typename T2> + struct StringMaker<std::pair<T1, T2> > { + static std::string convert(const std::pair<T1, T2>& pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; } +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) +#include <optional> +namespace Catch { + template<typename T> + struct StringMaker<std::optional<T> > { + static std::string convert(const std::optional<T>& optional) { + ReusableStringStream rss; + if (optional.has_value()) { + rss << ::Catch::Detail::stringify(*optional); + } else { + rss << "{ }"; + } + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include <tuple> +namespace Catch { namespace Detail { - template<typename InputIterator> - std::string rangeToString( InputIterator first, InputIterator last ) { - std::ostringstream oss; - oss << "{ "; - if( first != last ) { - oss << Catch::toString( *first ); - for( ++first ; first != last ; ++first ) - oss << ", " << Catch::toString( *first ); - } - oss << " }"; - return oss.str(); + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size<Tuple>::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get<N>(tuple)); + TupleElementPrinter<Tuple, N + 1>::print(tuple, os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter<Tuple, N, false> { + static void print(const Tuple&, std::ostream&) {} + }; + } -} -} // end namespace Catch + template<typename ...Types> + struct StringMaker<std::tuple<Types...>> { + static std::string convert(const std::tuple<Types...>& tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include <variant> namespace Catch { + template<> + struct StringMaker<std::monostate> { + static std::string convert(const std::monostate&) { + return "{ }"; + } + }; -template<typename LhsT, Internal::Operator Op, typename RhsT> -class BinaryExpression; + template<typename... Elements> + struct StringMaker<std::variant<Elements...>> { + static std::string convert(const std::variant<Elements...>& variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto& value) { + return ::Catch::Detail::stringify(value); + }, + variant + ); + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER -template<typename ArgT, typename MatcherT> -class MatchExpression; +namespace Catch { + // Import begin/ end from std here + using std::begin; + using std::end; + + namespace detail { + template <typename...> + struct void_type { + using type = void; + }; -// Wraps the LHS of an expression and overloads comparison operators -// for also capturing those and RHS (if any) -template<typename T> -class ExpressionLhs : public DecomposedExpression { -public: - ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ), m_truthy(false) {} + template <typename T, typename = void> + struct is_range_impl : std::false_type { + }; - ExpressionLhs& operator = ( const ExpressionLhs& ); + template <typename T> + struct is_range_impl<T, typename void_type<decltype(begin(std::declval<T>()))>::type> : std::true_type { + }; + } // namespace detail - template<typename RhsT> - BinaryExpression<T, Internal::IsEqualTo, RhsT const&> - operator == ( RhsT const& rhs ) { - return captureExpression<Internal::IsEqualTo>( rhs ); - } + template <typename T> + struct is_range : detail::is_range_impl<T> { + }; - template<typename RhsT> - BinaryExpression<T, Internal::IsNotEqualTo, RhsT const&> - operator != ( RhsT const& rhs ) { - return captureExpression<Internal::IsNotEqualTo>( rhs ); - } +#if defined(_MANAGED) // Managed types are never ranges + template <typename T> + struct is_range<T^> { + static const bool value = false; + }; +#endif - template<typename RhsT> - BinaryExpression<T, Internal::IsLessThan, RhsT const&> - operator < ( RhsT const& rhs ) { - return captureExpression<Internal::IsLessThan>( rhs ); + template<typename Range> + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); } - template<typename RhsT> - BinaryExpression<T, Internal::IsGreaterThan, RhsT const&> - operator > ( RhsT const& rhs ) { - return captureExpression<Internal::IsGreaterThan>( rhs ); + // Handle vector<bool> specially + template<typename Allocator> + std::string rangeToString( std::vector<bool, Allocator> const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); } - template<typename RhsT> - BinaryExpression<T, Internal::IsLessThanOrEqualTo, RhsT const&> - operator <= ( RhsT const& rhs ) { - return captureExpression<Internal::IsLessThanOrEqualTo>( rhs ); - } + template<typename R> + struct StringMaker<R, typename std::enable_if<is_range<R>::value && !::Catch::Detail::IsStreamInsertable<R>::value>::type> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; - template<typename RhsT> - BinaryExpression<T, Internal::IsGreaterThanOrEqualTo, RhsT const&> - operator >= ( RhsT const& rhs ) { - return captureExpression<Internal::IsGreaterThanOrEqualTo>( rhs ); - } + template <typename T, int SZ> + struct StringMaker<T[SZ]> { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; - BinaryExpression<T, Internal::IsEqualTo, bool> operator == ( bool rhs ) { - return captureExpression<Internal::IsEqualTo>( rhs ); - } +} // namespace Catch - BinaryExpression<T, Internal::IsNotEqualTo, bool> operator != ( bool rhs ) { - return captureExpression<Internal::IsNotEqualTo>( rhs ); - } +// Separate std::chrono::duration specialization +#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#include <ctime> +#include <ratio> +#include <chrono> - void endExpression() { - m_truthy = m_lhs ? true : false; - m_rb - .setResultType( m_truthy ) - .endExpression( *this ); - } +namespace Catch { - virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { - dest = Catch::toString( m_lhs ); - } +template <class Ratio> +struct ratio_string { + static std::string symbol(); +}; -private: - template<Internal::Operator Op, typename RhsT> - BinaryExpression<T, Op, RhsT&> captureExpression( RhsT& rhs ) const { - return BinaryExpression<T, Op, RhsT&>( m_rb, m_lhs, rhs ); - } +template <class Ratio> +std::string ratio_string<Ratio>::symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); +} +template <> +struct ratio_string<std::atto> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::femto> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::pico> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::nano> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::micro> { + static std::string symbol(); +}; +template <> +struct ratio_string<std::milli> { + static std::string symbol(); +}; - template<Internal::Operator Op> - BinaryExpression<T, Op, bool> captureExpression( bool rhs ) const { - return BinaryExpression<T, Op, bool>( m_rb, m_lhs, rhs ); - } + //////////// + // std::chrono::duration specializations + template<typename Value, typename Ratio> + struct StringMaker<std::chrono::duration<Value, Ratio>> { + static std::string convert(std::chrono::duration<Value, Ratio> const& duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's'; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; -private: - ResultBuilder& m_rb; - T m_lhs; - bool m_truthy; -}; + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock> + template<typename Clock, typename Duration> + struct StringMaker<std::chrono::time_point<Clock, Duration>> { + static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point<system_clock> specialization + template<typename Duration> + struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> { + static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); -template<typename LhsT, Internal::Operator Op, typename RhsT> -class BinaryExpression : public DecomposedExpression { -public: - BinaryExpression( ResultBuilder& rb, LhsT lhs, RhsT rhs ) - : m_rb( rb ), m_lhs( lhs ), m_rhs( rhs ) {} +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &converted); +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif - BinaryExpression& operator = ( BinaryExpression& ); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; - void endExpression() const { - m_rb - .setResultType( Internal::Evaluator<LhsT, RhsT, Op>::evaluate( m_lhs, m_rhs ) ) - .endExpression( *this ); - } +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER + +#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \ +namespace Catch { \ + template<> struct StringMaker<enumName> { \ + static std::string convert( enumName value ) { \ + static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ + return static_cast<std::string>(enumInfo.lookup( static_cast<int>( value ) )); \ + } \ + }; \ +} - virtual bool isBinaryExpression() const CATCH_OVERRIDE { - return true; - } +#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) - virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { - std::string lhs = Catch::toString( m_lhs ); - std::string rhs = Catch::toString( m_rhs ); - char delim = lhs.size() + rhs.size() < 40 && - lhs.find('\n') == std::string::npos && - rhs.find('\n') == std::string::npos ? ' ' : '\n'; - dest.reserve( 7 + lhs.size() + rhs.size() ); - // 2 for spaces around operator - // 2 for operator - // 2 for parentheses (conditionally added later) - // 1 for negation (conditionally added later) - dest = lhs; - dest += delim; - dest += Internal::OperatorTraits<Op>::getName(); - dest += delim; - dest += rhs; - } +#ifdef _MSC_VER +#pragma warning(pop) +#endif -private: - ResultBuilder& m_rb; - LhsT m_lhs; - RhsT m_rhs; -}; +// end catch_tostring.h +#include <iosfwd> -template<typename ArgT, typename MatcherT> -class MatchExpression : public DecomposedExpression { -public: - MatchExpression( ArgT arg, MatcherT matcher, char const* matcherString ) - : m_arg( arg ), m_matcher( matcher ), m_matcherString( matcherString ) {} +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#pragma warning(disable:4800) // Forcing result to true or false +#endif - virtual bool isBinaryExpression() const CATCH_OVERRIDE { - return true; - } +namespace Catch { - virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { - std::string matcherAsString = m_matcher.toString(); - dest = Catch::toString( m_arg ); - dest += ' '; - if( matcherAsString == Detail::unprintableString ) - dest += m_matcherString; - else - dest += matcherAsString; - } + struct ITransientExpression { + auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + auto getResult() const -> bool { return m_result; } + virtual void streamReconstructedExpression( std::ostream &os ) const = 0; -private: - ArgT m_arg; - MatcherT m_matcher; - char const* m_matcherString; -}; + ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} -} // end namespace Catch + // We don't actually need a virtual destructor, but many static analysers + // complain if it's not here :-( + virtual ~ITransientExpression(); + bool m_isBinaryExpression; + bool m_result; -namespace Catch { + }; - template<typename T> - ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) { - return ExpressionLhs<T const&>( *this, operand ); - } + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); - inline ExpressionLhs<bool> ResultBuilder::operator <= ( bool value ) { - return ExpressionLhs<bool>( *this, value ); - } + template<typename LhsT, typename RhsT> + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; - template<typename ArgT, typename MatcherT> - void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher, - char const* matcherString ) { - MatchExpression<ArgT const&, MatcherT const&> expr( arg, matcher, matcherString ); - setResultType( matcher.match( arg ) ); - endExpression( expr ); - } + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } -} // namespace Catch + public: + BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : ITransientExpression{ true, comparisonResult }, + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} -// #included from: catch_message.h -#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + template<typename T> + auto operator && ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } -#include <string> + template<typename T> + auto operator || ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } -namespace Catch { + template<typename T> + auto operator == ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } - struct MessageInfo { - MessageInfo( std::string const& _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ); + template<typename T> + auto operator != ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } - std::string macroName; - SourceLineInfo lineInfo; - ResultWas::OfType type; - std::string message; - unsigned int sequence; + template<typename T> + auto operator > ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } - bool operator == ( MessageInfo const& other ) const { - return sequence == other.sequence; + template<typename T> + auto operator < ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); } - bool operator < ( MessageInfo const& other ) const { - return sequence < other.sequence; + + template<typename T> + auto operator >= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator <= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); } - private: - static unsigned int globalCount; }; - struct MessageBuilder { - MessageBuilder( std::string const& macroName, - SourceLineInfo const& lineInfo, - ResultWas::OfType type ) - : m_info( macroName, lineInfo, type ) - {} + template<typename LhsT> + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; - template<typename T> - MessageBuilder& operator << ( T const& value ) { - m_stream << value; - return *this; + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); } - MessageInfo m_info; - std::ostringstream m_stream; + public: + explicit UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, static_cast<bool>(lhs) }, + m_lhs( lhs ) + {} }; - class ScopedMessage { + // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) + template<typename LhsT, typename RhsT> + auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast<bool>(lhs == rhs); } + template<typename T> + auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; } + template<typename T> + auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; } + + template<typename LhsT, typename RhsT> + auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast<bool>(lhs != rhs); } + template<typename T> + auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; } + template<typename T> + auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; } + + template<typename LhsT> + class ExprLhs { + LhsT m_lhs; public: - ScopedMessage( MessageBuilder const& builder ); - ScopedMessage( ScopedMessage const& other ); - ~ScopedMessage(); + explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} - MessageInfo m_info; + template<typename RhsT> + auto operator == ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs }; + } + auto operator == ( bool rhs ) -> BinaryExpr<LhsT, bool> const { + return { m_lhs == rhs, m_lhs, "==", rhs }; + } + + template<typename RhsT> + auto operator != ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs }; + } + auto operator != ( bool rhs ) -> BinaryExpr<LhsT, bool> const { + return { m_lhs != rhs, m_lhs, "!=", rhs }; + } + + template<typename RhsT> + auto operator > ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs > rhs), m_lhs, ">", rhs }; + } + template<typename RhsT> + auto operator < ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs < rhs), m_lhs, "<", rhs }; + } + template<typename RhsT> + auto operator >= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs >= rhs), m_lhs, ">=", rhs }; + } + template<typename RhsT> + auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs <= rhs), m_lhs, "<=", rhs }; + } + template <typename RhsT> + auto operator | (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs | rhs), m_lhs, "|", rhs }; + } + template <typename RhsT> + auto operator & (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs & rhs), m_lhs, "&", rhs }; + } + template <typename RhsT> + auto operator ^ (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const { + return { static_cast<bool>(m_lhs ^ rhs), m_lhs, "^", rhs }; + } + + template<typename RhsT> + auto operator && ( RhsT const& ) -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<RhsT>::value, + "operator&& is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename RhsT> + auto operator || ( RhsT const& ) -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<RhsT>::value, + "operator|| is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + auto makeUnaryExpr() const -> UnaryExpr<LhsT> { + return UnaryExpr<LhsT>{ m_lhs }; + } + }; + + void handleExpression( ITransientExpression const& expr ); + + template<typename T> + void handleExpression( ExprLhs<T> const& expr ) { + handleExpression( expr.makeUnaryExpr() ); + } + + struct Decomposer { + template<typename T> + auto operator <= ( T const& lhs ) -> ExprLhs<T const&> { + return ExprLhs<T const&>{ lhs }; + } + + auto operator <=( bool value ) -> ExprLhs<bool> { + return ExprLhs<bool>{ value }; + } }; } // end namespace Catch -// #included from: catch_interfaces_capture.h -#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// end catch_decomposer.h +// start catch_interfaces_capture.h #include <string> +#include <chrono> namespace Catch { - class TestCase; class AssertionResult; struct AssertionInfo; struct SectionInfo; struct SectionEndInfo; struct MessageInfo; - class ScopedMessageBuilder; + struct MessageBuilder; struct Counts; + struct AssertionReaction; + struct SourceLineInfo; + + struct ITransientExpression; + struct IGeneratorTracker; + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + struct BenchmarkInfo; + template <typename Duration = std::chrono::duration<double, std::nano>> + struct BenchmarkStats; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING struct IResultCapture { virtual ~IResultCapture(); - virtual void assertionEnded( AssertionResult const& result ) = 0; virtual bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) = 0; virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; - virtual void pushScopedMessage( MessageInfo const& message ) = 0; - virtual void popScopedMessage( MessageInfo const& message ) = 0; - virtual std::string getCurrentTestName() const = 0; - virtual const AssertionResult* getLastResult() const = 0; + virtual auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; - virtual void exceptionEarlyReported() = 0; +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + virtual void benchmarkPreparing( std::string const& name ) = 0; + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; + virtual void benchmarkFailed( std::string const& error ) = 0; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; - virtual void handleFatalErrorCondition( std::string const& message ) = 0; + virtual void emplaceUnscopedMessage( MessageBuilder const& builder ) = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; virtual bool lastAssertionPassed() = 0; virtual void assertionPassed() = 0; - virtual void assertionRun() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; }; IResultCapture& getResultCapture(); } -// #included from: catch_debugger.h -#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED +// end catch_interfaces_capture.h +namespace Catch { + + struct TestFailureException{}; + struct AssertionResultData; + struct IResultCapture; + class RunContext; -// #included from: catch_platform.h -#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; -#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) -# define CATCH_PLATFORM_MAC -#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) -# define CATCH_PLATFORM_IPHONE -#elif defined(linux) || defined(__linux) || defined(__linux__) -# define CATCH_PLATFORM_LINUX -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) -# define CATCH_PLATFORM_WINDOWS -# if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) -# define CATCH_DEFINES_NOMINMAX -# endif -# if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) -# define CATCH_DEFINES_WIN32_LEAN_AND_MEAN -# endif -#endif + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + LazyExpression( bool isNegated ); + LazyExpression( LazyExpression const& other ); + LazyExpression& operator = ( LazyExpression const& ) = delete; -#include <string> + explicit operator bool() const; -namespace Catch{ + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; - bool isDebuggerActive(); - void writeToDebugConsole( std::string const& text ); -} + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + }; -#ifdef CATCH_PLATFORM_MAC + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; - // The following code snippet based on: - // http://cocoawithlove.com/2008/03/break-into-debugger.html - #if defined(__ppc64__) || defined(__ppc__) - #define CATCH_TRAP() \ - __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ - : : : "memory","r0","r3","r4" ) /* NOLINT */ - #else - #define CATCH_TRAP() __asm__("int $3\n" : : /* NOLINT */ ) - #endif + public: + AssertionHandler + ( StringRef const& macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } -#elif defined(CATCH_PLATFORM_LINUX) - // 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. - #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) - #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ - #else // Fall back to the generic way. - #include <signal.h> + template<typename T> + void handleExpr( ExprLhs<T> const& expr ) { + handleExpr( expr.makeUnaryExpr() ); + } + void handleExpr( ITransientExpression const& expr ); - #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 + void handleMessage(ResultWas::OfType resultType, StringRef const& message); -#ifdef CATCH_TRAP - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } -#else - #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); -#endif + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + void setCompleted(); + + // query + auto allowThrows() const -> bool; + }; -// #included from: catch_interfaces_runner.h -#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ); + +} // namespace Catch + +// end catch_assertionhandler.h +// start catch_message.h + +#include <string> +#include <vector> namespace Catch { - class TestCase; - struct IRunner { - virtual ~IRunner(); - virtual bool aborting() const = 0; + struct MessageInfo { + MessageInfo( StringRef const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + StringRef macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const; + bool operator < ( MessageInfo const& other ) const; + private: + static unsigned int globalCount; }; -} + + struct MessageStream { + + template<typename T> + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder( StringRef const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ); + + template<typename T> + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage& duplicate ) = delete; + ScopedMessage( ScopedMessage&& old ); + ~ScopedMessage(); + + MessageInfo m_info; + bool m_moved; + }; + + class Capturer { + std::vector<MessageInfo> m_messages; + IResultCapture& m_resultCapture = getResultCapture(); + size_t m_captured = 0; + public: + Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); + ~Capturer(); + + void captureValue( size_t index, std::string const& value ); + + template<typename T> + void captureValues( size_t index, T const& value ) { + captureValue( index, Catch::Detail::stringify( value ) ); + } + + template<typename T, typename... Ts> + void captureValues( size_t index, T const& value, Ts const&... values ) { + captureValue( index, Catch::Detail::stringify(value) ); + captureValues( index+1, values... ); + } + }; + +} // end namespace Catch + +// end catch_message.h +#if !defined(CATCH_CONFIG_DISABLE) #if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) -# define CATCH_INTERNAL_STRINGIFY(expr) #expr + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ #else -# define CATCH_INTERNAL_STRINGIFY(expr) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" #endif -#if defined(CATCH_CONFIG_FAST_COMPILE) -/////////////////////////////////////////////////////////////////////////////// -// We can speedup compilation significantly by breaking into debugger lower in -// the callstack, because then we don't have to expand CATCH_BREAK_INTO_DEBUGGER -// macro in each assertion -#define INTERNAL_CATCH_REACT( resultBuilder ) \ - resultBuilder.react(); +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) /////////////////////////////////////////////////////////////////////////////// // Another way to speed-up compilation is to omit local try-catch for REQUIRE* // macros. -// This can potentially cause false negative, if the test code catches -// the exception before it propagates back up to the runner. -#define INTERNAL_CATCH_TEST_NO_TRY( macroName, resultDisposition, expr ) \ - do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition ); \ - __catchResult.setExceptionGuard(); \ - CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - ( __catchResult <= expr ).endExpression(); \ - CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ - __catchResult.unsetExceptionGuard(); \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::isTrue( false && static_cast<bool>( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look -// The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. - -#define INTERNAL_CHECK_THAT_NO_TRY( macroName, matcher, resultDisposition, arg ) \ - do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - __catchResult.setExceptionGuard(); \ - __catchResult.captureMatch( arg, matcher, CATCH_INTERNAL_STRINGIFY(matcher) ); \ - __catchResult.unsetExceptionGuard(); \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::alwaysFalse() ) +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); } -#else -/////////////////////////////////////////////////////////////////////////////// -// In the event of a failure works out if the debugger needs to be invoked -// and/or an exception thrown and takes appropriate action. -// This needs to be done as a macro so the debugger will stop in the user -// source code rather than in Catch library code -#define INTERNAL_CATCH_REACT( resultBuilder ) \ - if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ - resultBuilder.react(); #endif +#define INTERNAL_CATCH_REACT( handler ) handler.complete(); + /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ) \ +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition ); \ - try { \ + CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - ( __catchResult <= expr ).endExpression(); \ - CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ - } \ - catch( ... ) { \ - __catchResult.useActiveException( resultDisposition ); \ - } \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::isTrue( false && static_cast<bool>( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look - // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( (void)0, (false) && static_cast<bool>( !!(__VA_ARGS__) ) ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_IF( macroName, resultDisposition, expr ) \ - INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ if( Catch::getResultCapture().lastAssertionPassed() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, expr ) \ - INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ if( !Catch::getResultCapture().lastAssertionPassed() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, expr ) \ +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition ); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ try { \ - static_cast<void>(expr); \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ } \ catch( ... ) { \ - __catchResult.useActiveException( resultDisposition ); \ + catchAssertionHandler.handleUnexpectedInflightException(); \ } \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::alwaysFalse() ) + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, matcher, expr ) \ +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition, CATCH_INTERNAL_STRINGIFY(matcher) ); \ - if( __catchResult.allowThrows() ) \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ try { \ - static_cast<void>(expr); \ - __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ } \ catch( ... ) { \ - __catchResult.captureExpectedException( matcher ); \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ } \ else \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::alwaysFalse() ) + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ - if( __catchResult.allowThrows() ) \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ try { \ static_cast<void>(expr); \ - __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ } \ - catch( exceptionType ) { \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ } \ catch( ... ) { \ - __catchResult.useActiveException( resultDisposition ); \ + catchAssertionHandler.handleUnexpectedInflightException(); \ } \ else \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::alwaysFalse() ) + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) /////////////////////////////////////////////////////////////////////////////// -#ifdef CATCH_CONFIG_VARIADIC_MACROS - #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ - do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ - __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ - __catchResult.captureResult( messageType ); \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::alwaysFalse() ) -#else - #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, log ) \ - do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ - __catchResult << log + ::Catch::StreamEndStop(); \ - __catchResult.captureResult( messageType ); \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::alwaysFalse() ) -#endif +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ + auto varName = Catch::Capturer( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__ ); \ + varName.captureValues( 0, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_INFO( macroName, log ) \ - Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ +#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ + Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - try { \ - __catchResult.captureMatch( arg, matcher, CATCH_INTERNAL_STRINGIFY(matcher) ); \ - } catch( ... ) { \ - __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ - } \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::alwaysFalse() ) + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher##_catch_sr ); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) -// #included from: internal/catch_section.h -#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED +#endif // CATCH_CONFIG_DISABLE -// #included from: catch_section_info.h -#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED +// end catch_capture.hpp +// start catch_section.h -// #included from: catch_totals.hpp -#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED +// start catch_section_info.h + +// start catch_totals.h #include <cstddef> namespace Catch { struct Counts { - Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} - - Counts operator - ( Counts const& other ) const { - Counts diff; - diff.passed = passed - other.passed; - diff.failed = failed - other.failed; - diff.failedButOk = failedButOk - other.failedButOk; - return diff; - } - Counts& operator += ( Counts const& other ) { - passed += other.passed; - failed += other.failed; - failedButOk += other.failedButOk; - return *this; - } + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); - std::size_t total() const { - return passed + failed + failedButOk; - } - bool allPassed() const { - return failed == 0 && failedButOk == 0; - } - bool allOk() const { - return failed == 0; - } + std::size_t total() const; + bool allPassed() const; + bool allOk() const; - std::size_t passed; - std::size_t failed; - std::size_t failedButOk; + std::size_t passed = 0; + std::size_t failed = 0; + std::size_t failedButOk = 0; }; struct Totals { - Totals operator - ( Totals const& other ) const { - Totals diff; - diff.assertions = assertions - other.assertions; - diff.testCases = testCases - other.testCases; - return diff; - } + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); - Totals delta( Totals const& prevTotals ) const { - Totals diff = *this - prevTotals; - if( diff.assertions.failed > 0 ) - ++diff.testCases.failed; - else if( diff.assertions.failedButOk > 0 ) - ++diff.testCases.failedButOk; - else - ++diff.testCases.passed; - return diff; - } - - Totals& operator += ( Totals const& other ) { - assertions += other.assertions; - testCases += other.testCases; - return *this; - } + Totals delta( Totals const& prevTotals ) const; + int error = 0; Counts assertions; Counts testCases; }; } +// end catch_totals.h #include <string> namespace Catch { @@ -2340,19 +2858,20 @@ namespace Catch { struct SectionInfo { SectionInfo ( SourceLineInfo const& _lineInfo, + std::string const& _name ); + + // Deprecated + SectionInfo + ( SourceLineInfo const& _lineInfo, std::string const& _name, - std::string const& _description = std::string() ); + std::string const& ) : SectionInfo( _lineInfo, _name ) {} std::string name; - std::string description; + std::string description; // !Deprecated: this will always be empty SourceLineInfo lineInfo; }; struct SectionEndInfo { - SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) - : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) - {} - SectionInfo sectionInfo; Counts prevAssertions; double durationInSeconds; @@ -2360,36 +2879,29 @@ namespace Catch { } // end namespace Catch -// #included from: catch_timer.h -#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED +// end catch_section_info.h +// start catch_timer.h -#ifdef _MSC_VER +#include <cstdint> namespace Catch { - typedef unsigned long long UInt64; -} -#else -#include <stdint.h> -namespace Catch { - typedef uint64_t UInt64; -} -#endif -namespace Catch { + auto getCurrentNanosecondsSinceEpoch() -> uint64_t; + auto getEstimatedClockResolution() -> uint64_t; + class Timer { + uint64_t m_nanoseconds = 0; public: - Timer() : m_ticks( 0 ) {} void start(); - unsigned int getElapsedMicroseconds() const; - unsigned int getElapsedMilliseconds() const; - double getElapsedSeconds() const; - - private: - UInt64 m_ticks; + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; }; } // namespace Catch +// end catch_timer.h #include <string> namespace Catch { @@ -2400,7 +2912,7 @@ namespace Catch { ~Section(); // This indicates whether the section should be executed or not - operator bool() const; + explicit operator bool() const; private: SectionInfo m_info; @@ -2413,203 +2925,25 @@ namespace Catch { } // end namespace Catch -#ifdef CATCH_CONFIG_VARIADIC_MACROS - #define INTERNAL_CATCH_SECTION( ... ) \ - if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) -#else - #define INTERNAL_CATCH_SECTION( name, desc ) \ - if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) -#endif - -// #included from: internal/catch_generators.hpp -#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED - -#include <vector> -#include <string> -#include <stdlib.h> - -namespace Catch { - -template<typename T> -struct IGenerator { - virtual ~IGenerator() {} - virtual T getValue( std::size_t index ) const = 0; - virtual std::size_t size () const = 0; -}; - -template<typename T> -class BetweenGenerator : public IGenerator<T> { -public: - BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} - - virtual T getValue( std::size_t index ) const { - return m_from+static_cast<int>( index ); - } - - virtual std::size_t size() const { - return static_cast<std::size_t>( 1+m_to-m_from ); - } - -private: - - T m_from; - T m_to; -}; - -template<typename T> -class ValuesGenerator : public IGenerator<T> { -public: - ValuesGenerator(){} - - void add( T value ) { - m_values.push_back( value ); - } - - virtual T getValue( std::size_t index ) const { - return m_values[index]; - } - - virtual std::size_t size() const { - return m_values.size(); - } - -private: - std::vector<T> m_values; -}; - -template<typename T> -class CompositeGenerator { -public: - CompositeGenerator() : m_totalSize( 0 ) {} - - // *** Move semantics, similar to auto_ptr *** - CompositeGenerator( CompositeGenerator& other ) - : m_fileInfo( other.m_fileInfo ), - m_totalSize( 0 ) - { - move( other ); - } - - CompositeGenerator& setFileInfo( const char* fileInfo ) { - m_fileInfo = fileInfo; - return *this; - } - - ~CompositeGenerator() { - deleteAll( m_composed ); - } - - operator T () const { - size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); - - typename std::vector<const IGenerator<T>*>::const_iterator it = m_composed.begin(); - typename std::vector<const IGenerator<T>*>::const_iterator itEnd = m_composed.end(); - for( size_t index = 0; it != itEnd; ++it ) - { - const IGenerator<T>* generator = *it; - if( overallIndex >= index && overallIndex < index + generator->size() ) - { - return generator->getValue( overallIndex-index ); - } - index += generator->size(); - } - CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); - return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so - } - - void add( const IGenerator<T>* generator ) { - m_totalSize += generator->size(); - m_composed.push_back( generator ); - } - - CompositeGenerator& then( CompositeGenerator& other ) { - move( other ); - return *this; - } - - CompositeGenerator& then( T value ) { - ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); - valuesGen->add( value ); - add( valuesGen ); - return *this; - } - -private: - - void move( CompositeGenerator& other ) { - m_composed.insert( m_composed.end(), other.m_composed.begin(), other.m_composed.end() ); - m_totalSize += other.m_totalSize; - other.m_composed.clear(); - } - - std::vector<const IGenerator<T>*> m_composed; - std::string m_fileInfo; - size_t m_totalSize; -}; - -namespace Generators -{ - template<typename T> - CompositeGenerator<T> between( T from, T to ) { - CompositeGenerator<T> generators; - generators.add( new BetweenGenerator<T>( from, to ) ); - return generators; - } - - template<typename T> - CompositeGenerator<T> values( T val1, T val2 ) { - CompositeGenerator<T> generators; - ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); - valuesGen->add( val1 ); - valuesGen->add( val2 ); - generators.add( valuesGen ); - return generators; - } - - template<typename T> - CompositeGenerator<T> values( T val1, T val2, T val3 ){ - CompositeGenerator<T> generators; - ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); - valuesGen->add( val1 ); - valuesGen->add( val2 ); - valuesGen->add( val3 ); - generators.add( valuesGen ); - return generators; - } - - template<typename T> - CompositeGenerator<T> values( T val1, T val2, T val3, T val4 ) { - CompositeGenerator<T> generators; - ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); - valuesGen->add( val1 ); - valuesGen->add( val2 ); - valuesGen->add( val3 ); - valuesGen->add( val4 ); - generators.add( valuesGen ); - return generators; - } - -} // end namespace Generators - -using namespace Generators; - -} // end namespace Catch - -#define INTERNAL_CATCH_LINESTR2( line ) #line -#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) +#define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) +#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -// #included from: internal/catch_interfaces_exception.h -#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED +// end catch_section.h +// start catch_interfaces_exception.h -#include <string> -#include <vector> - -// #included from: catch_interfaces_registry_hub.h -#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED +// start catch_interfaces_registry_hub.h #include <string> +#include <memory> namespace Catch { @@ -2620,6 +2954,11 @@ namespace Catch { struct IReporterRegistry; struct IReporterFactory; struct ITagAliasRegistry; + struct IMutableEnumValuesRegistry; + + class StartupExceptionRegistry; + + using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; struct IRegistryHub { virtual ~IRegistryHub(); @@ -2627,32 +2966,44 @@ namespace Catch { virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; }; struct IMutableRegistryHub { virtual ~IMutableRegistryHub(); - virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) = 0; - virtual void registerListener( Ptr<IReporterFactory> const& factory ) = 0; + virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0; + virtual void registerListener( IReporterFactoryPtr const& factory ) = 0; virtual void registerTest( TestCase const& testInfo ) = 0; virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; + virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0; }; - IRegistryHub& getRegistryHub(); + IRegistryHub const& getRegistryHub(); IMutableRegistryHub& getMutableRegistryHub(); void cleanUp(); std::string translateActiveException(); } -namespace Catch { +// end catch_interfaces_registry_hub.h +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \ + static std::string translatorName( signature ) +#endif - typedef std::string(*exceptionTranslateFunction)(); +#include <exception> +#include <string> +#include <vector> + +namespace Catch { + using exceptionTranslateFunction = std::string(*)(); struct IExceptionTranslator; - typedef std::vector<const IExceptionTranslator*> ExceptionTranslators; + using ExceptionTranslators = std::vector<std::unique_ptr<IExceptionTranslator const>>; struct IExceptionTranslator { virtual ~IExceptionTranslator(); @@ -2674,16 +3025,20 @@ namespace Catch { : m_translateFunction( translateFunction ) {} - virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE { + std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + return ""; +#else try { if( it == itEnd ) - throw; + std::rethrow_exception(std::current_exception()); else return (*it)->translate( it+1, itEnd ); } catch( T& ex ) { return m_translateFunction( ex ); } +#endif } protected: @@ -2702,61 +3057,56 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ static std::string translatorName( signature ); \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ static std::string translatorName( signature ) #define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) -// #included from: internal/catch_approx.hpp -#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED +// end catch_interfaces_exception.h +// start catch_approx.h -#include <cmath> -#include <limits> - -#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) #include <type_traits> -#endif namespace Catch { namespace Detail { class Approx { + private: + bool equalityComparisonImpl(double other) const; + // Validates the new margin (margin >= 0) + // out-of-line to avoid including stdexcept in the header + void setMargin(double margin); + // Validates the new epsilon (0 < epsilon < 1) + // out-of-line to avoid including stdexcept in the header + void setEpsilon(double epsilon); + public: - explicit Approx ( double value ) - : m_epsilon( std::numeric_limits<float>::epsilon()*100 ), - m_margin( 0.0 ), - m_scale( 1.0 ), - m_value( value ) - {} + explicit Approx ( double value ); - static Approx custom() { - return Approx( 0 ); - } + static Approx custom(); -#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) + Approx operator-() const; template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - Approx operator()( T value ) { + Approx operator()( T const& value ) { Approx approx( static_cast<double>(value) ); - approx.epsilon( m_epsilon ); - approx.margin( m_margin ); - approx.scale( m_scale ); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; return approx; } template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - explicit Approx( T value ): Approx(static_cast<double>(value)) + explicit Approx( T const& value ): Approx(static_cast<double>(value)) {} template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> friend bool operator == ( const T& lhs, Approx const& rhs ) { - // Thanks to Richard Harris for his help refining this formula - auto lhs_v = double(lhs); - bool relativeOK = std::fabs(lhs_v - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale + (std::max)(std::fabs(lhs_v), std::fabs(rhs.m_value))); - if (relativeOK) { - return true; - } - return std::fabs(lhs_v - rhs.m_value) < rhs.m_margin; + auto lhs_v = static_cast<double>(lhs); + return rhs.equalityComparisonImpl(lhs_v); } template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> @@ -2765,139 +3115,418 @@ namespace Detail { } template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator != ( T lhs, Approx const& rhs ) { + friend bool operator != ( T const& lhs, Approx const& rhs ) { return !operator==( lhs, rhs ); } template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator != ( Approx const& lhs, T rhs ) { + friend bool operator != ( Approx const& lhs, T const& rhs ) { return !operator==( rhs, lhs ); } template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator <= ( T lhs, Approx const& rhs ) { - return double(lhs) < rhs.m_value || lhs == rhs; + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; } template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator <= ( Approx const& lhs, T rhs ) { - return lhs.m_value < double(rhs) || lhs == rhs; + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; } template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator >= ( T lhs, Approx const& rhs ) { - return double(lhs) > rhs.m_value || lhs == rhs; + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; } template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator >= ( Approx const& lhs, T rhs ) { - return lhs.m_value > double(rhs) || lhs == rhs; + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; } template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - Approx& epsilon( T newEpsilon ) { - m_epsilon = double(newEpsilon); + Approx& epsilon( T const& newEpsilon ) { + double epsilonAsDouble = static_cast<double>(newEpsilon); + setEpsilon(epsilonAsDouble); return *this; } template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - Approx& margin( T newMargin ) { - m_margin = double(newMargin); + Approx& margin( T const& newMargin ) { + double marginAsDouble = static_cast<double>(newMargin); + setMargin(marginAsDouble); return *this; } template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - Approx& scale( T newScale ) { - m_scale = double(newScale); + Approx& scale( T const& newScale ) { + m_scale = static_cast<double>(newScale); return *this; } -#else + std::string toString() const; - Approx operator()( double value ) { - Approx approx( value ); - approx.epsilon( m_epsilon ); - approx.margin( m_margin ); - approx.scale( m_scale ); - return approx; - } + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; +} // end namespace Detail + +namespace literals { + Detail::Approx operator "" _a(long double val); + Detail::Approx operator "" _a(unsigned long long val); +} // end namespace literals + +template<> +struct StringMaker<Catch::Detail::Approx> { + static std::string convert(Catch::Detail::Approx const& value); +}; + +} // end namespace Catch + +// end catch_approx.h +// start catch_string_manip.h + +#include <string> +#include <iosfwd> +#include <vector> + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + //! Returns a new string without whitespace at the start/end + std::string trim( std::string const& str ); + //! Returns a substring of the original ref without whitespace. Beware lifetimes! + StringRef trim(StringRef ref); + + // !!! Be aware, returns refs into original string - make sure original string outlives them + std::vector<StringRef> splitStringRef( StringRef str, char delimiter ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; +} + +// end catch_string_manip.h +#ifndef CATCH_CONFIG_DISABLE_MATCHERS +// start catch_capture_matchers.h + +// start catch_matchers.h + +#include <string> +#include <vector> + +namespace Catch { +namespace Matchers { + namespace Impl { + + template<typename ArgT> struct MatchAllOf; + template<typename ArgT> struct MatchAnyOf; + template<typename ArgT> struct MatchNotOf; + + class MatcherUntypedBase { + public: + MatcherUntypedBase() = default; + MatcherUntypedBase ( MatcherUntypedBase const& ) = default; + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete; + std::string toString() const; + + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + }; + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#endif + + template<typename ObjectT> + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + +#if defined(__OBJC__) + // Hack to fix Catch GH issue #1661. Could use id for generic Object support. + // use of const for Object pointers is very uncommon and under ARC it causes some kind of signature mismatch that breaks compilation + template<> + struct MatcherMethod<NSString*> { + virtual bool match( NSString* arg ) const = 0; + }; +#endif + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + + template<typename T> + struct MatcherBase : MatcherUntypedBase, MatcherMethod<T> { + + MatchAllOf<T> operator && ( MatcherBase const& other ) const; + MatchAnyOf<T> operator || ( MatcherBase const& other ) const; + MatchNotOf<T> operator ! () const; + }; - friend bool operator == ( double lhs, Approx const& rhs ) { - // Thanks to Richard Harris for his help refining this formula - bool relativeOK = std::fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( std::fabs(lhs), std::fabs(rhs.m_value) ) ); - if (relativeOK) { + template<typename ArgT> + struct MatchAllOf : MatcherBase<ArgT> { + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (!matcher->match(arg)) + return false; + } return true; } - return std::fabs(lhs - rhs.m_value) < rhs.m_margin; - } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " and "; + description += matcher->toString(); + } + description += " )"; + return description; + } - friend bool operator == ( Approx const& lhs, double rhs ) { - return operator==( rhs, lhs ); - } + MatchAllOf<ArgT> operator && ( MatcherBase<ArgT> const& other ) { + auto copy(*this); + copy.m_matchers.push_back( &other ); + return copy; + } - friend bool operator != ( double lhs, Approx const& rhs ) { - return !operator==( lhs, rhs ); - } + std::vector<MatcherBase<ArgT> const*> m_matchers; + }; + template<typename ArgT> + struct MatchAnyOf : MatcherBase<ArgT> { - friend bool operator != ( Approx const& lhs, double rhs ) { - return !operator==( rhs, lhs ); - } + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (matcher->match(arg)) + return true; + } + return false; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " or "; + description += matcher->toString(); + } + description += " )"; + return description; + } - friend bool operator <= ( double lhs, Approx const& rhs ) { - return lhs < rhs.m_value || lhs == rhs; - } + MatchAnyOf<ArgT> operator || ( MatcherBase<ArgT> const& other ) { + auto copy(*this); + copy.m_matchers.push_back( &other ); + return copy; + } - friend bool operator <= ( Approx const& lhs, double rhs ) { - return lhs.m_value < rhs || lhs == rhs; - } + std::vector<MatcherBase<ArgT> const*> m_matchers; + }; - friend bool operator >= ( double lhs, Approx const& rhs ) { - return lhs > rhs.m_value || lhs == rhs; - } + template<typename ArgT> + struct MatchNotOf : MatcherBase<ArgT> { - friend bool operator >= ( Approx const& lhs, double rhs ) { - return lhs.m_value > rhs || lhs == rhs; - } + MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} - Approx& epsilon( double newEpsilon ) { - m_epsilon = newEpsilon; - return *this; - } + bool match( ArgT const& arg ) const override { + return !m_underlyingMatcher.match( arg ); + } - Approx& margin( double newMargin ) { - m_margin = newMargin; - return *this; - } + std::string describe() const override { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase<ArgT> const& m_underlyingMatcher; + }; - Approx& scale( double newScale ) { - m_scale = newScale; - return *this; + template<typename T> + MatchAllOf<T> MatcherBase<T>::operator && ( MatcherBase const& other ) const { + return MatchAllOf<T>() && *this && other; } -#endif - - std::string toString() const { - std::ostringstream oss; - oss << "Approx( " << Catch::toString( m_value ) << " )"; - return oss.str(); + template<typename T> + MatchAnyOf<T> MatcherBase<T>::operator || ( MatcherBase const& other ) const { + return MatchAnyOf<T>() || *this || other; + } + template<typename T> + MatchNotOf<T> MatcherBase<T>::operator ! () const { + return MatchNotOf<T>( *this ); } - private: - double m_epsilon; - double m_margin; - double m_scale; - double m_value; - }; -} + } // namespace Impl -template<> -inline std::string toString<Detail::Approx>( Detail::Approx const& value ) { - return value.toString(); +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + +// end catch_matchers.h +// start catch_matchers_exception.hpp + +namespace Catch { +namespace Matchers { +namespace Exception { + +class ExceptionMessageMatcher : public MatcherBase<std::exception> { + std::string m_message; +public: + + ExceptionMessageMatcher(std::string const& message): + m_message(message) + {} + + bool match(std::exception const& ex) const override; + + std::string describe() const override; +}; + +} // namespace Exception + +Exception::ExceptionMessageMatcher Message(std::string const& message); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_exception.hpp +// start catch_matchers_floating.h + +namespace Catch { +namespace Matchers { + + namespace Floating { + + enum class FloatingPointKind : uint8_t; + + struct WithinAbsMatcher : MatcherBase<double> { + WithinAbsMatcher(double target, double margin); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_margin; + }; + + struct WithinUlpsMatcher : MatcherBase<double> { + WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + uint64_t m_ulps; + FloatingPointKind m_type; + }; + + // Given IEEE-754 format for floats and doubles, we can assume + // that float -> double promotion is lossless. Given this, we can + // assume that if we do the standard relative comparison of + // |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get + // the same result if we do this for floats, as if we do this for + // doubles that were promoted from floats. + struct WithinRelMatcher : MatcherBase<double> { + WithinRelMatcher(double target, double epsilon); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_epsilon; + }; + + } // namespace Floating + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff); + Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff); + Floating::WithinAbsMatcher WithinAbs(double target, double margin); + Floating::WithinRelMatcher WithinRel(double target, double eps); + // defaults epsilon to 100*numeric_limits<double>::epsilon() + Floating::WithinRelMatcher WithinRel(double target); + Floating::WithinRelMatcher WithinRel(float target, float eps); + // defaults epsilon to 100*numeric_limits<float>::epsilon() + Floating::WithinRelMatcher WithinRel(float target); + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_floating.h +// start catch_matchers_generic.hpp + +#include <functional> +#include <string> + +namespace Catch { +namespace Matchers { +namespace Generic { + +namespace Detail { + std::string finalizeDescription(const std::string& desc); } -} // end namespace Catch +template <typename T> +class PredicateMatcher : public MatcherBase<T> { + std::function<bool(T const&)> m_predicate; + std::string m_description; +public: + + PredicateMatcher(std::function<bool(T const&)> const& elem, std::string const& descr) + :m_predicate(std::move(elem)), + m_description(Detail::finalizeDescription(descr)) + {} + + bool match( T const& item ) const override { + return m_predicate(item); + } -// #included from: internal/catch_matchers_string.h -#define TWOBLUECUBES_CATCH_MATCHERS_STRING_H_INCLUDED + std::string describe() const override { + return m_description; + } +}; + +} // namespace Generic + + // The following functions create the actual matcher objects. + // The user has to explicitly specify type to the function, because + // inferring std::function<bool(T const&)> is hard (but possible) and + // requires a lot of TMP. + template<typename T> + Generic::PredicateMatcher<T> Predicate(std::function<bool(T const&)> const& predicate, std::string const& description = "") { + return Generic::PredicateMatcher<T>(predicate, description); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_generic.hpp +// start catch_matchers_string.h + +#include <string> namespace Catch { namespace Matchers { @@ -2916,7 +3545,7 @@ namespace Matchers { struct StringMatcherBase : MatcherBase<std::string> { StringMatcherBase( std::string const& operation, CasedString const& comparator ); - virtual std::string describe() const CATCH_OVERRIDE; + std::string describe() const override; CasedString m_comparator; std::string m_operation; @@ -2924,19 +3553,29 @@ namespace Matchers { struct EqualsMatcher : StringMatcherBase { EqualsMatcher( CasedString const& comparator ); - virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + bool match( std::string const& source ) const override; }; struct ContainsMatcher : StringMatcherBase { ContainsMatcher( CasedString const& comparator ); - virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + bool match( std::string const& source ) const override; }; struct StartsWithMatcher : StringMatcherBase { StartsWithMatcher( CasedString const& comparator ); - virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + bool match( std::string const& source ) const override; }; struct EndsWithMatcher : StringMatcherBase { EndsWithMatcher( CasedString const& comparator ); - virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + bool match( std::string const& source ) const override; + }; + + struct RegexMatcher : MatcherBase<std::string> { + RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity ); + bool match( std::string const& matchee ) const override; + std::string describe() const override; + + private: + std::string m_regex; + CaseSensitive::Choice m_caseSensitivity; }; } // namespace StdString @@ -2948,76 +3587,145 @@ namespace Matchers { StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); } // namespace Matchers } // namespace Catch -// #included from: internal/catch_matchers_vector.h -#define TWOBLUECUBES_CATCH_MATCHERS_VECTOR_H_INCLUDED +// end catch_matchers_string.h +// start catch_matchers_vector.h + +#include <algorithm> namespace Catch { namespace Matchers { namespace Vector { - - template<typename T> - struct ContainsElementMatcher : MatcherBase<std::vector<T>, T> { + template<typename T, typename Alloc> + struct ContainsElementMatcher : MatcherBase<std::vector<T, Alloc>> { ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} - bool match(std::vector<T> const &v) const CATCH_OVERRIDE { - return std::find(v.begin(), v.end(), m_comparator) != v.end(); + bool match(std::vector<T, Alloc> const &v) const override { + for (auto const& el : v) { + if (el == m_comparator) { + return true; + } + } + return false; } - virtual std::string describe() const CATCH_OVERRIDE { - return "Contains: " + Catch::toString( m_comparator ); + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); } T const& m_comparator; }; - template<typename T> - struct ContainsMatcher : MatcherBase<std::vector<T>, std::vector<T> > { + template<typename T, typename AllocComp, typename AllocMatch> + struct ContainsMatcher : MatcherBase<std::vector<T, AllocMatch>> { - ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} + ContainsMatcher(std::vector<T, AllocComp> const &comparator) : m_comparator( comparator ) {} - bool match(std::vector<T> const &v) const CATCH_OVERRIDE { + bool match(std::vector<T, AllocMatch> const &v) const override { // !TBD: see note in EqualsMatcher if (m_comparator.size() > v.size()) return false; - for (size_t i = 0; i < m_comparator.size(); ++i) - if (std::find(v.begin(), v.end(), m_comparator[i]) == v.end()) + for (auto const& comparator : m_comparator) { + auto present = false; + for (const auto& el : v) { + if (el == comparator) { + present = true; + break; + } + } + if (!present) { return false; + } + } return true; } - virtual std::string describe() const CATCH_OVERRIDE { - return "Contains: " + Catch::toString( m_comparator ); + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); } - std::vector<T> const& m_comparator; + std::vector<T, AllocComp> const& m_comparator; }; - template<typename T> - struct EqualsMatcher : MatcherBase<std::vector<T>, std::vector<T> > { + template<typename T, typename AllocComp, typename AllocMatch> + struct EqualsMatcher : MatcherBase<std::vector<T, AllocMatch>> { - EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} + EqualsMatcher(std::vector<T, AllocComp> const &comparator) : m_comparator( comparator ) {} - bool match(std::vector<T> const &v) const CATCH_OVERRIDE { + bool match(std::vector<T, AllocMatch> const &v) const override { // !TBD: This currently works if all elements can be compared using != // - a more general approach would be via a compare template that defaults - // to using !=. but could be specialised for, e.g. std::vector<T> etc + // to using !=. but could be specialised for, e.g. std::vector<T, Alloc> etc // - then just call that directly if (m_comparator.size() != v.size()) return false; - for (size_t i = 0; i < v.size(); ++i) + for (std::size_t i = 0; i < v.size(); ++i) if (m_comparator[i] != v[i]) return false; return true; } - virtual std::string describe() const CATCH_OVERRIDE { - return "Equals: " + Catch::toString( m_comparator ); + std::string describe() const override { + return "Equals: " + ::Catch::Detail::stringify( m_comparator ); + } + std::vector<T, AllocComp> const& m_comparator; + }; + + template<typename T, typename AllocComp, typename AllocMatch> + struct ApproxMatcher : MatcherBase<std::vector<T, AllocMatch>> { + + ApproxMatcher(std::vector<T, AllocComp> const& comparator) : m_comparator( comparator ) {} + + bool match(std::vector<T, AllocMatch> const &v) const override { + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != approx(v[i])) + return false; + return true; + } + std::string describe() const override { + return "is approx: " + ::Catch::Detail::stringify( m_comparator ); + } + template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + ApproxMatcher& epsilon( T const& newEpsilon ) { + approx.epsilon(newEpsilon); + return *this; + } + template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + ApproxMatcher& margin( T const& newMargin ) { + approx.margin(newMargin); + return *this; + } + template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + ApproxMatcher& scale( T const& newScale ) { + approx.scale(newScale); + return *this; + } + + std::vector<T, AllocComp> const& m_comparator; + mutable Catch::Detail::Approx approx = Catch::Detail::Approx::custom(); + }; + + template<typename T, typename AllocComp, typename AllocMatch> + struct UnorderedEqualsMatcher : MatcherBase<std::vector<T, AllocMatch>> { + UnorderedEqualsMatcher(std::vector<T, AllocComp> const& target) : m_target(target) {} + bool match(std::vector<T, AllocMatch> const& vec) const override { + if (m_target.size() != vec.size()) { + return false; + } + return std::is_permutation(m_target.begin(), m_target.end(), vec.begin()); + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); } - std::vector<T> const& m_comparator; + private: + std::vector<T, AllocComp> const& m_target; }; } // namespace Vector @@ -3025,50 +3733,667 @@ namespace Matchers { // The following functions create the actual matcher objects. // This allows the types to be inferred + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + Vector::ContainsMatcher<T, AllocComp, AllocMatch> Contains( std::vector<T, AllocComp> const& comparator ) { + return Vector::ContainsMatcher<T, AllocComp, AllocMatch>( comparator ); + } + + template<typename T, typename Alloc = std::allocator<T>> + Vector::ContainsElementMatcher<T, Alloc> VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher<T, Alloc>( comparator ); + } + + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + Vector::EqualsMatcher<T, AllocComp, AllocMatch> Equals( std::vector<T, AllocComp> const& comparator ) { + return Vector::EqualsMatcher<T, AllocComp, AllocMatch>( comparator ); + } + + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + Vector::ApproxMatcher<T, AllocComp, AllocMatch> Approx( std::vector<T, AllocComp> const& comparator ) { + return Vector::ApproxMatcher<T, AllocComp, AllocMatch>( comparator ); + } + + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch> UnorderedEquals(std::vector<T, AllocComp> const& target) { + return Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch>( target ); + } + +} // namespace Matchers +} // namespace Catch + +// end catch_matchers_vector.h +namespace Catch { + + template<typename ArgT, typename MatcherT> + class MatchExpr : public ITransientExpression { + ArgT const& m_arg; + MatcherT m_matcher; + StringRef m_matcherString; + public: + MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) + : ITransientExpression{ true, matcher.match( arg ) }, + m_arg( arg ), + m_matcher( matcher ), + m_matcherString( matcherString ) + {} + + void streamReconstructedExpression( std::ostream &os ) const override { + auto matcherAsString = m_matcher.toString(); + os << Catch::Detail::stringify( m_arg ) << ' '; + if( matcherAsString == Detail::unprintableString ) + os << m_matcherString; + else + os << matcherAsString; + } + }; + + using StringMatcher = Matchers::Impl::MatcherBase<std::string>; + + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ); + + template<typename ArgT, typename MatcherT> + auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) -> MatchExpr<ArgT, MatcherT> { + return MatchExpr<ArgT, MatcherT>( arg, matcher, matcherString ); + } + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher##_catch_sr ) ); \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(__VA_ARGS__ ); \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ex ) { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher##_catch_sr ) ); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +// end catch_capture_matchers.h +#endif +// start catch_generators.hpp + +// start catch_interfaces_generatortracker.h + + +#include <memory> + +namespace Catch { + + namespace Generators { + class GeneratorUntypedBase { + public: + GeneratorUntypedBase() = default; + virtual ~GeneratorUntypedBase(); + // Attempts to move the generator to the next element + // + // Returns true iff the move succeeded (and a valid element + // can be retrieved). + virtual bool next() = 0; + }; + using GeneratorBasePtr = std::unique_ptr<GeneratorUntypedBase>; + + } // namespace Generators + + struct IGeneratorTracker { + virtual ~IGeneratorTracker(); + virtual auto hasGenerator() const -> bool = 0; + virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; + virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0; + }; + +} // namespace Catch + +// end catch_interfaces_generatortracker.h +// start catch_enforce.h + +#include <exception> + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + template <typename Ex> + [[noreturn]] + void throw_exception(Ex const& e) { + throw e; + } +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + [[noreturn]] + void throw_exception(std::exception const& e); +#endif + + [[noreturn]] + void throw_logic_error(std::string const& msg); + [[noreturn]] + void throw_domain_error(std::string const& msg); + [[noreturn]] + void throw_runtime_error(std::string const& msg); + +} // namespace Catch; + +#define CATCH_MAKE_MSG(...) \ + (Catch::ReusableStringStream() << __VA_ARGS__).str() + +#define CATCH_INTERNAL_ERROR(...) \ + Catch::throw_logic_error(CATCH_MAKE_MSG( CATCH_INTERNAL_LINEINFO << ": Internal Catch2 error: " << __VA_ARGS__)) + +#define CATCH_ERROR(...) \ + Catch::throw_domain_error(CATCH_MAKE_MSG( __VA_ARGS__ )) + +#define CATCH_RUNTIME_ERROR(...) \ + Catch::throw_runtime_error(CATCH_MAKE_MSG( __VA_ARGS__ )) + +#define CATCH_ENFORCE( condition, ... ) \ + do{ if( !(condition) ) CATCH_ERROR( __VA_ARGS__ ); } while(false) + +// end catch_enforce.h +#include <memory> +#include <vector> +#include <cassert> + +#include <utility> +#include <exception> + +namespace Catch { + +class GeneratorException : public std::exception { + const char* const m_msg = ""; + +public: + GeneratorException(const char* msg): + m_msg(msg) + {} + + const char* what() const noexcept override final; +}; + +namespace Generators { + + // !TBD move this into its own location? + namespace pf{ + template<typename T, typename... Args> + std::unique_ptr<T> make_unique( Args&&... args ) { + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); + } + } + + template<typename T> + struct IGenerator : GeneratorUntypedBase { + virtual ~IGenerator() = default; + + // Returns the current element of the generator + // + // \Precondition The generator is either freshly constructed, + // or the last call to `next()` returned true + virtual T const& get() const = 0; + using type = T; + }; + template<typename T> - Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) { - return Vector::ContainsMatcher<T>( comparator ); + class SingleValueGenerator final : public IGenerator<T> { + T m_value; + public: + SingleValueGenerator(T&& value) : m_value(std::move(value)) {} + + T const& get() const override { + return m_value; + } + bool next() override { + return false; + } + }; + + template<typename T> + class FixedValuesGenerator final : public IGenerator<T> { + static_assert(!std::is_same<T, bool>::value, + "FixedValuesGenerator does not support bools because of std::vector<bool>" + "specialization, use SingleValue Generator instead."); + std::vector<T> m_values; + size_t m_idx = 0; + public: + FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {} + + T const& get() const override { + return m_values[m_idx]; + } + bool next() override { + ++m_idx; + return m_idx < m_values.size(); + } + }; + + template <typename T> + class GeneratorWrapper final { + std::unique_ptr<IGenerator<T>> m_generator; + public: + GeneratorWrapper(std::unique_ptr<IGenerator<T>> generator): + m_generator(std::move(generator)) + {} + T const& get() const { + return m_generator->get(); + } + bool next() { + return m_generator->next(); + } + }; + + template <typename T> + GeneratorWrapper<T> value(T&& value) { + return GeneratorWrapper<T>(pf::make_unique<SingleValueGenerator<T>>(std::forward<T>(value))); + } + template <typename T> + GeneratorWrapper<T> values(std::initializer_list<T> values) { + return GeneratorWrapper<T>(pf::make_unique<FixedValuesGenerator<T>>(values)); } template<typename T> - Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) { - return Vector::ContainsElementMatcher<T>( comparator ); + class Generators : public IGenerator<T> { + std::vector<GeneratorWrapper<T>> m_generators; + size_t m_current = 0; + + void populate(GeneratorWrapper<T>&& generator) { + m_generators.emplace_back(std::move(generator)); + } + void populate(T&& val) { + m_generators.emplace_back(value(std::forward<T>(val))); + } + template<typename U> + void populate(U&& val) { + populate(T(std::forward<U>(val))); + } + template<typename U, typename... Gs> + void populate(U&& valueOrGenerator, Gs &&... moreGenerators) { + populate(std::forward<U>(valueOrGenerator)); + populate(std::forward<Gs>(moreGenerators)...); + } + + public: + template <typename... Gs> + Generators(Gs &&... moreGenerators) { + m_generators.reserve(sizeof...(Gs)); + populate(std::forward<Gs>(moreGenerators)...); + } + + T const& get() const override { + return m_generators[m_current].get(); + } + + bool next() override { + if (m_current >= m_generators.size()) { + return false; + } + const bool current_status = m_generators[m_current].next(); + if (!current_status) { + ++m_current; + } + return m_current < m_generators.size(); + } + }; + + template<typename... Ts> + GeneratorWrapper<std::tuple<Ts...>> table( std::initializer_list<std::tuple<typename std::decay<Ts>::type...>> tuples ) { + return values<std::tuple<Ts...>>( tuples ); } + // Tag type to signal that a generator sequence should convert arguments to a specific type + template <typename T> + struct as {}; + + template<typename T, typename... Gs> + auto makeGenerators( GeneratorWrapper<T>&& generator, Gs &&... moreGenerators ) -> Generators<T> { + return Generators<T>(std::move(generator), std::forward<Gs>(moreGenerators)...); + } template<typename T> - Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) { - return Vector::EqualsMatcher<T>( comparator ); + auto makeGenerators( GeneratorWrapper<T>&& generator ) -> Generators<T> { + return Generators<T>(std::move(generator)); + } + template<typename T, typename... Gs> + auto makeGenerators( T&& val, Gs &&... moreGenerators ) -> Generators<T> { + return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... ); + } + template<typename T, typename U, typename... Gs> + auto makeGenerators( as<T>, U&& val, Gs &&... moreGenerators ) -> Generators<T> { + return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... ); } -} // namespace Matchers + auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; + + template<typename L> + // Note: The type after -> is weird, because VS2015 cannot parse + // the expression used in the typedef inside, when it is in + // return type. Yeah. + auto generate( StringRef generatorName, SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval<decltype(generatorExpression())>().get()) { + using UnderlyingType = typename decltype(generatorExpression())::type; + + IGeneratorTracker& tracker = acquireGeneratorTracker( generatorName, lineInfo ); + if (!tracker.hasGenerator()) { + tracker.setGenerator(pf::make_unique<Generators<UnderlyingType>>(generatorExpression())); + } + + auto const& generator = static_cast<IGenerator<UnderlyingType> const&>( *tracker.getGenerator() ); + return generator.get(); + } + +} // namespace Generators } // namespace Catch -// #included from: internal/catch_interfaces_tag_alias_registry.h -#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED +#define GENERATE( ... ) \ + Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [ ]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) +#define GENERATE_COPY( ... ) \ + Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [=]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) +#define GENERATE_REF( ... ) \ + Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [&]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) + +// end catch_generators.hpp +// start catch_generators_generic.hpp -// #included from: catch_tag_alias.h -#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED +namespace Catch { +namespace Generators { -#include <string> + template <typename T> + class TakeGenerator : public IGenerator<T> { + GeneratorWrapper<T> m_generator; + size_t m_returned = 0; + size_t m_target; + public: + TakeGenerator(size_t target, GeneratorWrapper<T>&& generator): + m_generator(std::move(generator)), + m_target(target) + { + assert(target != 0 && "Empty generators are not allowed"); + } + T const& get() const override { + return m_generator.get(); + } + bool next() override { + ++m_returned; + if (m_returned >= m_target) { + return false; + } + + const auto success = m_generator.next(); + // If the underlying generator does not contain enough values + // then we cut short as well + if (!success) { + m_returned = m_target; + } + return success; + } + }; + + template <typename T> + GeneratorWrapper<T> take(size_t target, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<T>(pf::make_unique<TakeGenerator<T>>(target, std::move(generator))); + } + + template <typename T, typename Predicate> + class FilterGenerator : public IGenerator<T> { + GeneratorWrapper<T> m_generator; + Predicate m_predicate; + public: + template <typename P = Predicate> + FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator): + m_generator(std::move(generator)), + m_predicate(std::forward<P>(pred)) + { + if (!m_predicate(m_generator.get())) { + // It might happen that there are no values that pass the + // filter. In that case we throw an exception. + auto has_initial_value = next(); + if (!has_initial_value) { + Catch::throw_exception(GeneratorException("No valid value found in filtered generator")); + } + } + } + + T const& get() const override { + return m_generator.get(); + } + + bool next() override { + bool success = m_generator.next(); + if (!success) { + return false; + } + while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true); + return success; + } + }; + + template <typename T, typename Predicate> + GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<T>(std::unique_ptr<IGenerator<T>>(pf::make_unique<FilterGenerator<T, Predicate>>(std::forward<Predicate>(pred), std::move(generator)))); + } + + template <typename T> + class RepeatGenerator : public IGenerator<T> { + static_assert(!std::is_same<T, bool>::value, + "RepeatGenerator currently does not support bools" + "because of std::vector<bool> specialization"); + GeneratorWrapper<T> m_generator; + mutable std::vector<T> m_returned; + size_t m_target_repeats; + size_t m_current_repeat = 0; + size_t m_repeat_index = 0; + public: + RepeatGenerator(size_t repeats, GeneratorWrapper<T>&& generator): + m_generator(std::move(generator)), + m_target_repeats(repeats) + { + assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); + } + + T const& get() const override { + if (m_current_repeat == 0) { + m_returned.push_back(m_generator.get()); + return m_returned.back(); + } + return m_returned[m_repeat_index]; + } + + bool next() override { + // There are 2 basic cases: + // 1) We are still reading the generator + // 2) We are reading our own cache + + // In the first case, we need to poke the underlying generator. + // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache + if (m_current_repeat == 0) { + const auto success = m_generator.next(); + if (!success) { + ++m_current_repeat; + } + return m_current_repeat < m_target_repeats; + } + + // In the second case, we need to move indices forward and check that we haven't run up against the end + ++m_repeat_index; + if (m_repeat_index == m_returned.size()) { + m_repeat_index = 0; + ++m_current_repeat; + } + return m_current_repeat < m_target_repeats; + } + }; + + template <typename T> + GeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<T>(pf::make_unique<RepeatGenerator<T>>(repeats, std::move(generator))); + } + + template <typename T, typename U, typename Func> + class MapGenerator : public IGenerator<T> { + // TBD: provide static assert for mapping function, for friendly error message + GeneratorWrapper<U> m_generator; + Func m_function; + // To avoid returning dangling reference, we have to save the values + T m_cache; + public: + template <typename F2 = Func> + MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) : + m_generator(std::move(generator)), + m_function(std::forward<F2>(function)), + m_cache(m_function(m_generator.get())) + {} + + T const& get() const override { + return m_cache; + } + bool next() override { + const auto success = m_generator.next(); + if (success) { + m_cache = m_function(m_generator.get()); + } + return success; + } + }; + + template <typename Func, typename U, typename T = FunctionReturnType<Func, U>> + GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { + return GeneratorWrapper<T>( + pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator)) + ); + } + + template <typename T, typename U, typename Func> + GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { + return GeneratorWrapper<T>( + pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator)) + ); + } + + template <typename T> + class ChunkGenerator final : public IGenerator<std::vector<T>> { + std::vector<T> m_chunk; + size_t m_chunk_size; + GeneratorWrapper<T> m_generator; + bool m_used_up = false; + public: + ChunkGenerator(size_t size, GeneratorWrapper<T> generator) : + m_chunk_size(size), m_generator(std::move(generator)) + { + m_chunk.reserve(m_chunk_size); + if (m_chunk_size != 0) { + m_chunk.push_back(m_generator.get()); + for (size_t i = 1; i < m_chunk_size; ++i) { + if (!m_generator.next()) { + Catch::throw_exception(GeneratorException("Not enough values to initialize the first chunk")); + } + m_chunk.push_back(m_generator.get()); + } + } + } + std::vector<T> const& get() const override { + return m_chunk; + } + bool next() override { + m_chunk.clear(); + for (size_t idx = 0; idx < m_chunk_size; ++idx) { + if (!m_generator.next()) { + return false; + } + m_chunk.push_back(m_generator.get()); + } + return true; + } + }; + + template <typename T> + GeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<std::vector<T>>( + pf::make_unique<ChunkGenerator<T>>(size, std::move(generator)) + ); + } + +} // namespace Generators +} // namespace Catch + +// end catch_generators_generic.hpp +// start catch_generators_specific.hpp + +// start catch_context.h + +#include <memory> namespace Catch { - struct TagAlias { - TagAlias( std::string const& _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + struct IResultCapture; + struct IRunner; + struct IConfig; + struct IMutableContext; - std::string tag; - SourceLineInfo lineInfo; + using IConfigPtr = std::shared_ptr<IConfig const>; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual IConfigPtr const& getConfig() const = 0; }; - struct RegistrarForTagAliases { - RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( IConfigPtr const& config ) = 0; + + private: + static IMutableContext *currentContext; + friend IMutableContext& getCurrentMutableContext(); + friend void cleanUpContext(); + static void createContext(); }; -} // end namespace Catch + inline IMutableContext& getCurrentMutableContext() + { + if( !IMutableContext::currentContext ) + IMutableContext::createContext(); + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return *IMutableContext::currentContext; + } + + inline IContext& getCurrentContext() + { + return getCurrentMutableContext(); + } -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } -// #included from: catch_option.hpp -#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + void cleanUpContext(); + + class SimplePcg32; + SimplePcg32& rng(); +} + +// end catch_context.h +// start catch_interfaces_config.h + +// start catch_option.hpp namespace Catch { @@ -3076,12 +4401,12 @@ namespace Catch { template<typename T> class Option { public: - Option() : nullableValue( CATCH_NULL ) {} + Option() : nullableValue( nullptr ) {} Option( T const& _value ) : nullableValue( new( storage ) T( _value ) ) {} Option( Option const& _other ) - : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) + : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) {} ~Option() { @@ -3105,7 +4430,7 @@ namespace Catch { void reset() { if( nullableValue ) nullableValue->~T(); - nullableValue = CATCH_NULL; + nullableValue = nullptr; } T& operator*() { return *nullableValue; } @@ -3117,50 +4442,316 @@ namespace Catch { return nullableValue ? *nullableValue : defaultValue; } - bool some() const { return nullableValue != CATCH_NULL; } - bool none() const { return nullableValue == CATCH_NULL; } + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } - bool operator !() const { return nullableValue == CATCH_NULL; } - operator SafeBool::type() const { - return SafeBool::makeSafe( some() ); + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); } private: T *nullableValue; - union { - char storage[sizeof(T)]; - - // These are here to force alignment for the storage - long double dummy1; - void (*dummy2)(); - long double dummy3; -#ifdef CATCH_CONFIG_CPP11_LONG_LONG - long long dummy4; -#endif - }; + alignas(alignof(T)) char storage[sizeof(T)]; }; } // end namespace Catch +// end catch_option.hpp +#include <chrono> +#include <iosfwd> +#include <string> +#include <vector> +#include <memory> + namespace Catch { - struct ITagAliasRegistry { - virtual ~ITagAliasRegistry(); - virtual Option<TagAlias> find( std::string const& alias ) const = 0; - virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + enum class Verbosity { + Quiet = 0, + Normal, + High + }; - static ITagAliasRegistry const& get(); + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01, + NoTests = 0x02 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + + struct IConfig : NonCopyable { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutNoTests() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual double minDuration() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual std::vector<std::string> const& getTestsOrTags() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector<std::string> const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + + virtual bool benchmarkNoAnalysis() const = 0; + virtual int benchmarkSamples() const = 0; + virtual double benchmarkConfidenceInterval() const = 0; + virtual unsigned int benchmarkResamples() const = 0; + virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0; + }; + + using IConfigPtr = std::shared_ptr<IConfig const>; +} + +// end catch_interfaces_config.h +// start catch_random_number_generator.h + +#include <cstdint> + +namespace Catch { + + // This is a simple implementation of C++11 Uniform Random Number + // Generator. It does not provide all operators, because Catch2 + // does not use it, but it should behave as expected inside stdlib's + // distributions. + // The implementation is based on the PCG family (http://pcg-random.org) + class SimplePcg32 { + using state_type = std::uint64_t; + public: + using result_type = std::uint32_t; + static constexpr result_type (min)() { + return 0; + } + static constexpr result_type (max)() { + return static_cast<result_type>(-1); + } + + // Provide some default initial state for the default constructor + SimplePcg32():SimplePcg32(0xed743cc4U) {} + + explicit SimplePcg32(result_type seed_); + + void seed(result_type seed_); + void discard(uint64_t skip); + + result_type operator()(); + + private: + friend bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs); + friend bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs); + + // In theory we also need operator<< and operator>> + // In practice we do not use them, so we will skip them for now + + std::uint64_t m_state; + // This part of the state determines which "stream" of the numbers + // is chosen -- we take it as a constant for Catch2, so we only + // need to deal with seeding the main state. + // Picked by reading 8 bytes from `/dev/random` :-) + static const std::uint64_t s_inc = (0x13ed0cc53f939476ULL << 1ULL) | 1ULL; }; } // end namespace Catch +// end catch_random_number_generator.h +#include <random> + +namespace Catch { +namespace Generators { + +template <typename Float> +class RandomFloatingGenerator final : public IGenerator<Float> { + Catch::SimplePcg32& m_rng; + std::uniform_real_distribution<Float> m_dist; + Float m_current_number; +public: + + RandomFloatingGenerator(Float a, Float b): + m_rng(rng()), + m_dist(a, b) { + static_cast<void>(next()); + } + + Float const& get() const override { + return m_current_number; + } + bool next() override { + m_current_number = m_dist(m_rng); + return true; + } +}; + +template <typename Integer> +class RandomIntegerGenerator final : public IGenerator<Integer> { + Catch::SimplePcg32& m_rng; + std::uniform_int_distribution<Integer> m_dist; + Integer m_current_number; +public: + + RandomIntegerGenerator(Integer a, Integer b): + m_rng(rng()), + m_dist(a, b) { + static_cast<void>(next()); + } + + Integer const& get() const override { + return m_current_number; + } + bool next() override { + m_current_number = m_dist(m_rng); + return true; + } +}; + +// TODO: Ideally this would be also constrained against the various char types, +// but I don't expect users to run into that in practice. +template <typename T> +typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value, +GeneratorWrapper<T>>::type +random(T a, T b) { + return GeneratorWrapper<T>( + pf::make_unique<RandomIntegerGenerator<T>>(a, b) + ); +} + +template <typename T> +typename std::enable_if<std::is_floating_point<T>::value, +GeneratorWrapper<T>>::type +random(T a, T b) { + return GeneratorWrapper<T>( + pf::make_unique<RandomFloatingGenerator<T>>(a, b) + ); +} + +template <typename T> +class RangeGenerator final : public IGenerator<T> { + T m_current; + T m_end; + T m_step; + bool m_positive; + +public: + RangeGenerator(T const& start, T const& end, T const& step): + m_current(start), + m_end(end), + m_step(step), + m_positive(m_step > T(0)) + { + assert(m_current != m_end && "Range start and end cannot be equal"); + assert(m_step != T(0) && "Step size cannot be zero"); + assert(((m_positive && m_current <= m_end) || (!m_positive && m_current >= m_end)) && "Step moves away from end"); + } + + RangeGenerator(T const& start, T const& end): + RangeGenerator(start, end, (start < end) ? T(1) : T(-1)) + {} + + T const& get() const override { + return m_current; + } + + bool next() override { + m_current += m_step; + return (m_positive) ? (m_current < m_end) : (m_current > m_end); + } +}; + +template <typename T> +GeneratorWrapper<T> range(T const& start, T const& end, T const& step) { + static_assert(std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, "Type must be numeric"); + return GeneratorWrapper<T>(pf::make_unique<RangeGenerator<T>>(start, end, step)); +} + +template <typename T> +GeneratorWrapper<T> range(T const& start, T const& end) { + static_assert(std::is_integral<T>::value && !std::is_same<T, bool>::value, "Type must be an integer"); + return GeneratorWrapper<T>(pf::make_unique<RangeGenerator<T>>(start, end)); +} + +template <typename T> +class IteratorGenerator final : public IGenerator<T> { + static_assert(!std::is_same<T, bool>::value, + "IteratorGenerator currently does not support bools" + "because of std::vector<bool> specialization"); + + std::vector<T> m_elems; + size_t m_current = 0; +public: + template <typename InputIterator, typename InputSentinel> + IteratorGenerator(InputIterator first, InputSentinel last):m_elems(first, last) { + if (m_elems.empty()) { + Catch::throw_exception(GeneratorException("IteratorGenerator received no valid values")); + } + } + + T const& get() const override { + return m_elems[m_current]; + } + + bool next() override { + ++m_current; + return m_current != m_elems.size(); + } +}; + +template <typename InputIterator, + typename InputSentinel, + typename ResultType = typename std::iterator_traits<InputIterator>::value_type> +GeneratorWrapper<ResultType> from_range(InputIterator from, InputSentinel to) { + return GeneratorWrapper<ResultType>(pf::make_unique<IteratorGenerator<ResultType>>(from, to)); +} + +template <typename Container, + typename ResultType = typename Container::value_type> +GeneratorWrapper<ResultType> from_range(Container const& cnt) { + return GeneratorWrapper<ResultType>(pf::make_unique<IteratorGenerator<ResultType>>(cnt.begin(), cnt.end())); +} + +} // namespace Generators +} // namespace Catch + +// end catch_generators_specific.hpp + // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections -// #included from: internal/catch_test_case_info.h -#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED +// start catch_test_case_info.h #include <string> -#include <set> +#include <vector> +#include <memory> #ifdef __clang__ #pragma clang diagnostic push @@ -3169,7 +4760,7 @@ namespace Catch { namespace Catch { - struct ITestCase; + struct ITestInvoker; struct TestCaseInfo { enum SpecialProperties{ @@ -3178,30 +4769,30 @@ namespace Catch { ShouldFail = 1 << 2, MayFail = 1 << 3, Throws = 1 << 4, - NonPortable = 1 << 5 + NonPortable = 1 << 5, + Benchmark = 1 << 6 }; TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, - std::set<std::string> const& _tags, + std::vector<std::string> const& _tags, SourceLineInfo const& _lineInfo ); - TestCaseInfo( TestCaseInfo const& other ); - - friend void setTags( TestCaseInfo& testCaseInfo, std::set<std::string> const& tags ); + friend void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ); bool isHidden() const; bool throws() const; bool okToFail() const; bool expectedToFail() const; + std::string tagsAsString() const; + std::string name; std::string className; std::string description; - std::set<std::string> tags; - std::set<std::string> lcaseTags; - std::string tagsAsString; + std::vector<std::string> tags; + std::vector<std::string> lcaseTags; SourceLineInfo lineInfo; SpecialProperties properties; }; @@ -3209,8 +4800,7 @@ namespace Catch { class TestCase : public TestCaseInfo { public: - TestCase( ITestCase* testCase, TestCaseInfo const& info ); - TestCase( TestCase const& other ); + TestCase( ITestInvoker* testCase, TestCaseInfo&& info ); TestCase withName( std::string const& _newName ) const; @@ -3218,19 +4808,16 @@ namespace Catch { TestCaseInfo const& getTestCaseInfo() const; - void swap( TestCase& other ); bool operator == ( TestCase const& other ) const; bool operator < ( TestCase const& other ) const; - TestCase& operator = ( TestCase const& other ); private: - Ptr<ITestCase> test; + std::shared_ptr<ITestInvoker> test; }; - TestCase makeTestCase( ITestCase* testCase, + TestCase makeTestCase( ITestInvoker* testCase, std::string const& className, - std::string const& name, - std::string const& description, + NameAndTags const& nameAndTags, SourceLineInfo const& lineInfo ); } @@ -3238,10 +4825,21 @@ namespace Catch { #pragma clang diagnostic pop #endif +// end catch_test_case_info.h +// start catch_interfaces_runner.h + +namespace Catch { + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +// end catch_interfaces_runner.h #ifdef __OBJC__ -// #included from: internal/catch_objc.hpp -#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED +// start catch_objc.hpp #import <objc/runtime.h> @@ -3265,7 +4863,7 @@ namespace Catch { namespace Catch { - class OcMethod : public SharedImpl<ITestCase> { + class OcMethod : public ITestInvoker { public: OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} @@ -3301,9 +4899,9 @@ namespace Catch { } } - inline size_t registerTestMethods() { - size_t noTestMethods = 0; - int noClasses = objc_getClassList( CATCH_NULL, 0 ); + inline std::size_t registerTestMethods() { + std::size_t noTestMethods = 0; + int noClasses = objc_getClassList( nullptr, 0 ); Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); objc_getClassList( classes, noClasses ); @@ -3322,7 +4920,7 @@ namespace Catch { std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); const char* className = class_getName( cls ); - getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo("",0) ) ); noTestMethods++; } } @@ -3332,6 +4930,8 @@ namespace Catch { return noTestMethods; } +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) + namespace Matchers { namespace Impl { namespace NSStringMatchers { @@ -3343,61 +4943,61 @@ namespace Catch { arcSafeRelease( m_substr ); } - virtual bool match( NSString* arg ) const CATCH_OVERRIDE { + bool match( NSString* str ) const override { return false; } - NSString* m_substr; + NSString* CATCH_ARC_STRONG m_substr; }; struct Equals : StringHolder { Equals( NSString* substr ) : StringHolder( substr ){} - virtual bool match( NSString* str ) const CATCH_OVERRIDE { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str isEqualToString:m_substr]; } - virtual std::string describe() const CATCH_OVERRIDE { - return "equals string: " + Catch::toString( m_substr ); + std::string describe() const override { + return "equals string: " + Catch::Detail::stringify( m_substr ); } }; struct Contains : StringHolder { Contains( NSString* substr ) : StringHolder( substr ){} - virtual bool match( NSString* str ) const { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location != NSNotFound; } - virtual std::string describe() const CATCH_OVERRIDE { - return "contains string: " + Catch::toString( m_substr ); + std::string describe() const override { + return "contains string: " + Catch::Detail::stringify( m_substr ); } }; struct StartsWith : StringHolder { StartsWith( NSString* substr ) : StringHolder( substr ){} - virtual bool match( NSString* str ) const { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == 0; } - virtual std::string describe() const CATCH_OVERRIDE { - return "starts with: " + Catch::toString( m_substr ); + std::string describe() const override { + return "starts with: " + Catch::Detail::stringify( m_substr ); } }; struct EndsWith : StringHolder { EndsWith( NSString* substr ) : StringHolder( substr ){} - virtual bool match( NSString* str ) const { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == [str length] - [m_substr length]; } - virtual std::string describe() const CATCH_OVERRIDE { - return "ends with: " + Catch::toString( m_substr ); + std::string describe() const override { + return "ends with: " + Catch::Detail::stringify( m_substr ); } }; @@ -3420,86 +5020,53 @@ namespace Catch { using namespace Matchers; +#endif // CATCH_CONFIG_DISABLE_MATCHERS + } // namespace Catch /////////////////////////////////////////////////////////////////////////////// -#define OC_TEST_CASE( name, desc )\ -+(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ -{\ +#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix +#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \ +{ \ return @ name; \ -}\ -+(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +} \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \ { \ return @ desc; \ } \ --(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) +-(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix ) -#endif - -#ifdef CATCH_IMPL +#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ ) -// !TBD: Move the leak detector code into a separate header -#ifdef CATCH_CONFIG_WINDOWS_CRTDBG -#include <crtdbg.h> -class LeakDetector { -public: - LeakDetector() { - int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); - flag |= _CRTDBG_LEAK_CHECK_DF; - flag |= _CRTDBG_ALLOC_MEM_DF; - _CrtSetDbgFlag(flag); - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); - // Change this to leaking allocation's number to break there - _CrtSetBreakAlloc(-1); - } -}; -#else -class LeakDetector {}; +// end catch_objc.hpp #endif -LeakDetector leakDetector; +// Benchmarking needs the externally-facing parts of reporters to work +#if defined(CATCH_CONFIG_EXTERNAL_INTERFACES) || defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +// start catch_external_interfaces.h -// #included from: internal/catch_impl.hpp -#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED +// start catch_reporter_bases.hpp -// Collect all the implementation files together here -// These are the equivalent of what would usually be cpp files +// start catch_interfaces_reporter.h -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wweak-vtables" -#endif - -// #included from: ../catch_session.hpp -#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED - -// #included from: internal/catch_commandline.hpp -#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED +// start catch_config.hpp -// #included from: catch_config.hpp -#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED - -// #included from: catch_test_spec_parser.hpp -#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED +// start catch_test_spec_parser.h #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif -// #included from: catch_test_spec.hpp -#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED +// start catch_test_spec.h #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif -// #included from: catch_wildcard_pattern.hpp -#define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED - -#include <stdexcept> +// start catch_wildcard_pattern.h namespace Catch { @@ -3513,123 +5080,86 @@ namespace Catch public: - WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) - : m_caseSensitivity( caseSensitivity ), - m_wildcard( NoWildcard ), - m_pattern( adjustCase( pattern ) ) - { - if( startsWith( m_pattern, '*' ) ) { - m_pattern = m_pattern.substr( 1 ); - m_wildcard = WildcardAtStart; - } - if( endsWith( m_pattern, '*' ) ) { - m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); - m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd ); - } - } - virtual ~WildcardPattern(); - virtual bool matches( std::string const& str ) const { - switch( m_wildcard ) { - case NoWildcard: - return m_pattern == adjustCase( str ); - case WildcardAtStart: - return endsWith( adjustCase( str ), m_pattern ); - case WildcardAtEnd: - return startsWith( adjustCase( str ), m_pattern ); - case WildcardAtBothEnds: - return contains( adjustCase( str ), m_pattern ); - } + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ); + virtual ~WildcardPattern() = default; + virtual bool matches( std::string const& str ) const; -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunreachable-code" -#endif - throw std::logic_error( "Unknown enum" ); -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - } private: - std::string adjustCase( std::string const& str ) const { - return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; - } + std::string normaliseString( std::string const& str ) const; CaseSensitive::Choice m_caseSensitivity; - WildcardPosition m_wildcard; + WildcardPosition m_wildcard = NoWildcard; std::string m_pattern; }; } +// end catch_wildcard_pattern.h #include <string> #include <vector> +#include <memory> namespace Catch { + struct IConfig; + class TestSpec { - struct Pattern : SharedImpl<> { + class Pattern { + public: + explicit Pattern( std::string const& name ); virtual ~Pattern(); virtual bool matches( TestCaseInfo const& testCase ) const = 0; + std::string const& name() const; + private: + std::string const m_name; }; + using PatternPtr = std::shared_ptr<Pattern>; + class NamePattern : public Pattern { public: - NamePattern( std::string const& name ) - : m_wildcardPattern( toLower( name ), CaseSensitive::No ) - {} - virtual ~NamePattern(); - virtual bool matches( TestCaseInfo const& testCase ) const { - return m_wildcardPattern.matches( toLower( testCase.name ) ); - } + explicit NamePattern( std::string const& name, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; private: WildcardPattern m_wildcardPattern; }; class TagPattern : public Pattern { public: - TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} - virtual ~TagPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const { - return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); - } + explicit TagPattern( std::string const& tag, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; private: std::string m_tag; }; class ExcludedPattern : public Pattern { public: - ExcludedPattern( Ptr<Pattern> const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} - virtual ~ExcludedPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + explicit ExcludedPattern( PatternPtr const& underlyingPattern ); + bool matches( TestCaseInfo const& testCase ) const override; private: - Ptr<Pattern> m_underlyingPattern; + PatternPtr m_underlyingPattern; }; struct Filter { - std::vector<Ptr<Pattern> > m_patterns; + std::vector<PatternPtr> m_patterns; - bool matches( TestCaseInfo const& testCase ) const { - // All patterns in a filter must match for the filter to be a match - for( std::vector<Ptr<Pattern> >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) { - if( !(*it)->matches( testCase ) ) - return false; - } - return true; - } + bool matches( TestCaseInfo const& testCase ) const; + std::string name() const; }; public: - bool hasFilters() const { - return !m_filters.empty(); - } - bool matches( TestCaseInfo const& testCase ) const { - // A TestSpec matches if any filter matches - for( std::vector<Filter>::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) - if( it->matches( testCase ) ) - return true; - return false; - } + struct FilterMatch { + std::string name; + std::vector<TestCase const*> tests; + }; + using Matches = std::vector<FilterMatch>; + using vectorStrings = std::vector<std::string>; + + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; + Matches matchesByFilter( std::vector<TestCase> const& testCases, IConfig const& config ) const; + const vectorStrings & getInvalidArgs() const; private: std::vector<Filter> m_filters; - + std::vector<std::string> m_invalidArgs; friend class TestSpecParser; }; } @@ -3638,112 +5168,79 @@ namespace Catch { #pragma clang diagnostic pop #endif +// end catch_test_spec.h +// start catch_interfaces_tag_alias_registry.h + +#include <string> + +namespace Catch { + + struct TagAlias; + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + // Nullptr if not present + virtual TagAlias const* find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// end catch_interfaces_tag_alias_registry.h namespace Catch { class TestSpecParser { enum Mode{ None, Name, QuotedName, Tag, EscapedName }; - Mode m_mode; - bool m_exclusion; - std::size_t m_start, m_pos; + Mode m_mode = None; + Mode lastMode = None; + bool m_exclusion = false; + std::size_t m_pos = 0; + std::size_t m_realPatternPos = 0; std::string m_arg; + std::string m_substring; + std::string m_patternName; std::vector<std::size_t> m_escapeChars; TestSpec::Filter m_currentFilter; TestSpec m_testSpec; - ITagAliasRegistry const* m_tagAliases; + ITagAliasRegistry const* m_tagAliases = nullptr; public: - TestSpecParser( ITagAliasRegistry const& tagAliases ) :m_mode(None), m_exclusion(false), m_start(0), m_pos(0), m_tagAliases( &tagAliases ) {} - - TestSpecParser& parse( std::string const& arg ) { - m_mode = None; - m_exclusion = false; - m_start = std::string::npos; - m_arg = m_tagAliases->expandAliases( arg ); - m_escapeChars.clear(); - for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) - visitChar( m_arg[m_pos] ); - if( m_mode == Name ) - addPattern<TestSpec::NamePattern>(); - return *this; - } - TestSpec testSpec() { - addFilter(); - return m_testSpec; - } + TestSpecParser( ITagAliasRegistry const& tagAliases ); + + TestSpecParser& parse( std::string const& arg ); + TestSpec testSpec(); + private: - void visitChar( char c ) { - if( m_mode == None ) { - switch( c ) { - case ' ': return; - case '~': m_exclusion = true; return; - case '[': return startNewMode( Tag, ++m_pos ); - case '"': return startNewMode( QuotedName, ++m_pos ); - case '\\': return escape(); - default: startNewMode( Name, m_pos ); break; - } - } - if( m_mode == Name ) { - if( c == ',' ) { - addPattern<TestSpec::NamePattern>(); - addFilter(); - } - else if( c == '[' ) { - if( subString() == "exclude:" ) - m_exclusion = true; - else - addPattern<TestSpec::NamePattern>(); - startNewMode( Tag, ++m_pos ); - } - else if( c == '\\' ) - escape(); - } - else if( m_mode == EscapedName ) - m_mode = Name; - else if( m_mode == QuotedName && c == '"' ) - addPattern<TestSpec::NamePattern>(); - else if( m_mode == Tag && c == ']' ) - addPattern<TestSpec::TagPattern>(); - } - void startNewMode( Mode mode, std::size_t start ) { - m_mode = mode; - m_start = start; - } - void escape() { - if( m_mode == None ) - m_start = m_pos; - m_mode = EscapedName; - m_escapeChars.push_back( m_pos ); - } - std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } - template<typename T> - void addPattern() { - std::string token = subString(); - for( size_t i = 0; i < m_escapeChars.size(); ++i ) - token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); - m_escapeChars.clear(); - if( startsWith( token, "exclude:" ) ) { - m_exclusion = true; - token = token.substr( 8 ); - } - if( !token.empty() ) { - Ptr<TestSpec::Pattern> pattern = new T( token ); - if( m_exclusion ) - pattern = new TestSpec::ExcludedPattern( pattern ); - m_currentFilter.m_patterns.push_back( pattern ); - } - m_exclusion = false; - m_mode = None; - } - void addFilter() { - if( !m_currentFilter.m_patterns.empty() ) { - m_testSpec.m_filters.push_back( m_currentFilter ); - m_currentFilter = TestSpec::Filter(); - } + bool visitChar( char c ); + void startNewMode( Mode mode ); + bool processNoneChar( char c ); + void processNameChar( char c ); + bool processOtherChar( char c ); + void endMode(); + void escape(); + bool isControlChar( char c ) const; + void saveLastMode(); + void revertBackToLastMode(); + void addFilter(); + bool separate(); + + // Handles common preprocessing of the pattern for name/tag patterns + std::string preprocessPattern(); + // Adds the current pattern as a test name + void addNamePattern(); + // Adds the current pattern as a tag + void addTagPattern(); + + inline void addCharToPattern(char c) { + m_substring += c; + m_patternName += c; + m_realPatternPos++; } + }; - inline TestSpec parseTestSpec( std::string const& arg ) { - return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); - } + TestSpec parseTestSpec( std::string const& arg ); } // namespace Catch @@ -3751,2595 +5248,4808 @@ namespace Catch { #pragma clang diagnostic pop #endif -// #included from: catch_interfaces_config.h -#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED +// end catch_test_spec_parser.h +// Libstdc++ doesn't like incomplete classes for unique_ptr -#include <iosfwd> -#include <string> +#include <memory> #include <vector> +#include <string> + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif namespace Catch { - struct Verbosity { enum Level { - NoOutput = 0, - Quiet, - Normal - }; }; + struct IStream; - struct WarnAbout { enum What { - Nothing = 0x00, - NoAssertions = 0x01 - }; }; + struct ConfigData { + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listTestNamesOnly = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + + int abortAfter = -1; + unsigned int rngSeed = 0; + + bool benchmarkNoAnalysis = false; + unsigned int benchmarkSamples = 100; + double benchmarkConfidenceInterval = 0.95; + unsigned int benchmarkResamples = 100000; + std::chrono::milliseconds::rep benchmarkWarmupTime = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; + double minDuration = -1; + RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; + UseColour::YesOrNo useColour = UseColour::Auto; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; - struct ShowDurations { enum OrNot { - DefaultForReporter, - Always, - Never - }; }; - struct RunTests { enum InWhatOrder { - InDeclarationOrder, - InLexicographicalOrder, - InRandomOrder - }; }; - struct UseColour { enum YesOrNo { - Auto, - Yes, - No - }; }; - struct WaitForKeypress { enum When { - Never, - BeforeStart = 1, - BeforeExit = 2, - BeforeStartAndExit = BeforeStart | BeforeExit - }; }; + std::string outputFilename; + std::string name; + std::string processName; +#ifndef CATCH_CONFIG_DEFAULT_REPORTER +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif + std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; +#undef CATCH_CONFIG_DEFAULT_REPORTER - class TestSpec; + std::vector<std::string> testsOrTags; + std::vector<std::string> sectionsToRun; + }; - struct IConfig : IShared { + class Config : public IConfig { + public: - virtual ~IConfig(); + Config() = default; + Config( ConfigData const& data ); + virtual ~Config() = default; - virtual bool allowThrows() const = 0; - virtual std::ostream& stream() const = 0; - virtual std::string name() const = 0; - virtual bool includeSuccessfulResults() const = 0; - virtual bool shouldDebugBreak() const = 0; - virtual bool warnAboutMissingAssertions() const = 0; - virtual int abortAfter() const = 0; - virtual bool showInvisibles() const = 0; - virtual ShowDurations::OrNot showDurations() const = 0; - virtual TestSpec const& testSpec() const = 0; - virtual RunTests::InWhatOrder runOrder() const = 0; - virtual unsigned int rngSeed() const = 0; - virtual UseColour::YesOrNo useColour() const = 0; - virtual std::vector<std::string> const& getSectionsToRun() const = 0; + std::string const& getFilename() const; + + bool listTests() const; + bool listTestNamesOnly() const; + bool listTags() const; + bool listReporters() const; + + std::string getProcessName() const; + std::string const& getReporterName() const; + + std::vector<std::string> const& getTestsOrTags() const override; + std::vector<std::string> const& getSectionsToRun() const override; + + TestSpec const& testSpec() const override; + bool hasTestFilters() const override; + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + std::ostream& stream() const override; + std::string name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutNoTests() const override; + ShowDurations::OrNot showDurations() const override; + double minDuration() const override; + RunTests::InWhatOrder runOrder() const override; + unsigned int rngSeed() const override; + UseColour::YesOrNo useColour() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + bool benchmarkNoAnalysis() const override; + int benchmarkSamples() const override; + double benchmarkConfidenceInterval() const override; + unsigned int benchmarkResamples() const override; + std::chrono::milliseconds benchmarkWarmupTime() const override; + + private: + + IStream const* openStream(); + ConfigData m_data; + + std::unique_ptr<IStream const> m_stream; + TestSpec m_testSpec; + bool m_hasTestFilters = false; }; -} -// #included from: catch_stream.h -#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED +} // end namespace Catch -// #included from: catch_streambuf.h -#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED +// end catch_config.hpp +// start catch_assertionresult.h -#include <streambuf> +#include <string> namespace Catch { - class StreamBufBase : public std::streambuf { + struct AssertionResultData + { + AssertionResultData() = delete; + + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { public: - virtual ~StreamBufBase() CATCH_NOEXCEPT; + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; }; -} -#include <streambuf> -#include <ostream> -#include <fstream> +} // end namespace Catch + +// end catch_assertionresult.h +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +// start catch_estimate.hpp + + // Statistics estimates + + +namespace Catch { + namespace Benchmark { + template <typename Duration> + struct Estimate { + Duration point; + Duration lower_bound; + Duration upper_bound; + double confidence_interval; + + template <typename Duration2> + operator Estimate<Duration2>() const { + return { point, lower_bound, upper_bound, confidence_interval }; + } + }; + } // namespace Benchmark +} // namespace Catch + +// end catch_estimate.hpp +// start catch_outlier_classification.hpp + +// Outlier information + +namespace Catch { + namespace Benchmark { + struct OutlierClassification { + int samples_seen = 0; + int low_severe = 0; // more than 3 times IQR below Q1 + int low_mild = 0; // 1.5 to 3 times IQR below Q1 + int high_mild = 0; // 1.5 to 3 times IQR above Q3 + int high_severe = 0; // more than 3 times IQR above Q3 + + int total() const { + return low_severe + low_mild + high_mild + high_severe; + } + }; + } // namespace Benchmark +} // namespace Catch + +// end catch_outlier_classification.hpp + +#include <iterator> +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + +#include <string> +#include <iosfwd> +#include <map> +#include <set> #include <memory> +#include <algorithm> namespace Catch { - std::ostream& cout(); - std::ostream& cerr(); - std::ostream& clog(); + struct ReporterConfig { + explicit ReporterConfig( IConfigPtr const& _fullConfig ); - struct IStream { - virtual ~IStream() CATCH_NOEXCEPT; - virtual std::ostream& stream() const = 0; + ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ); + + std::ostream& stream() const; + IConfigPtr fullConfig() const; + + private: + std::ostream* m_stream; + IConfigPtr m_fullConfig; }; - class FileStream : public IStream { - mutable std::ofstream m_ofs; - public: - FileStream( std::string const& filename ); - virtual ~FileStream() CATCH_NOEXCEPT; - public: // IStream - virtual std::ostream& stream() const CATCH_OVERRIDE; + struct ReporterPreferences { + bool shouldRedirectStdOut = false; + bool shouldReportAllAssertions = false; }; - class CoutStream : public IStream { - mutable std::ostream m_os; - public: - CoutStream(); - virtual ~CoutStream() CATCH_NOEXCEPT; + template<typename T> + struct LazyStat : Option<T> { + LazyStat& operator=( T const& _value ) { + Option<T>::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option<T>::reset(); + used = false; + } + bool used = false; + }; - public: // IStream - virtual std::ostream& stream() const CATCH_OVERRIDE; + struct TestRunInfo { + TestRunInfo( std::string const& _name ); + std::string name; }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ); - class DebugOutStream : public IStream { - CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf; - mutable std::ostream m_os; - public: - DebugOutStream(); - virtual ~DebugOutStream() CATCH_NOEXCEPT; + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ); - public: // IStream - virtual std::ostream& stream() const CATCH_OVERRIDE; + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = delete; + AssertionStats& operator = ( AssertionStats && ) = delete; + virtual ~AssertionStats(); + + AssertionResult assertionResult; + std::vector<MessageInfo> infoMessages; + Totals totals; }; -} -#include <memory> -#include <vector> -#include <string> -#include <stdexcept> + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ); + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; + virtual ~SectionStats(); -#ifndef CATCH_CONFIG_CONSOLE_WIDTH -#define CATCH_CONFIG_CONSOLE_WIDTH 80 -#endif + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; -namespace Catch { + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ); - struct ConfigData { + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; + virtual ~TestCaseStats(); - ConfigData() - : listTests( false ), - listTags( false ), - listReporters( false ), - listTestNamesOnly( false ), - listExtraInfo( false ), - showSuccessfulTests( false ), - shouldDebugBreak( false ), - noThrow( false ), - showHelp( false ), - showInvisibles( false ), - filenamesAsTags( false ), - libIdentify( false ), - abortAfter( -1 ), - rngSeed( 0 ), - verbosity( Verbosity::Normal ), - warnings( WarnAbout::Nothing ), - showDurations( ShowDurations::DefaultForReporter ), - runOrder( RunTests::InDeclarationOrder ), - useColour( UseColour::Auto ), - waitForKeypress( WaitForKeypress::Never ) - {} + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; - bool listTests; - bool listTags; - bool listReporters; - bool listTestNamesOnly; - bool listExtraInfo; - - bool showSuccessfulTests; - bool shouldDebugBreak; - bool noThrow; - bool showHelp; - bool showInvisibles; - bool filenamesAsTags; - bool libIdentify; - - int abortAfter; - unsigned int rngSeed; - - Verbosity::Level verbosity; - WarnAbout::What warnings; - ShowDurations::OrNot showDurations; - RunTests::InWhatOrder runOrder; - UseColour::YesOrNo useColour; - WaitForKeypress::When waitForKeypress; + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ); + TestGroupStats( GroupInfo const& _groupInfo ); - std::string outputFilename; + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; + virtual ~TestGroupStats(); + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ); + + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; + virtual ~TestRunStats(); + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + struct BenchmarkInfo { std::string name; - std::string processName; + double estimatedDuration; + int iterations; + int samples; + unsigned int resamples; + double clockResolution; + double clockCost; + }; - std::vector<std::string> reporterNames; - std::vector<std::string> testsOrTags; - std::vector<std::string> sectionsToRun; + template <class Duration> + struct BenchmarkStats { + BenchmarkInfo info; + + std::vector<Duration> samples; + Benchmark::Estimate<Duration> mean; + Benchmark::Estimate<Duration> standardDeviation; + Benchmark::OutlierClassification outliers; + double outlierVariance; + + template <typename Duration2> + operator BenchmarkStats<Duration2>() const { + std::vector<Duration2> samples2; + samples2.reserve(samples.size()); + std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](Duration d) { return Duration2(d); }); + return { + info, + std::move(samples2), + mean, + standardDeviation, + outliers, + outlierVariance, + }; + } }; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - class Config : public SharedImpl<IConfig> { - private: - Config( Config const& other ); - Config& operator = ( Config const& other ); - virtual void dummy(); - public: + struct IStreamingReporter { + virtual ~IStreamingReporter() = default; - Config() - {} + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + // static std::set<Verbosity> getSupportedVerbosities() - Config( ConfigData const& data ) - : m_data( data ), - m_stream( openStream() ) + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void reportInvalidArguments(std::string const&) {} + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + virtual void benchmarkPreparing( std::string const& ) {} + virtual void benchmarkStarting( BenchmarkInfo const& ) {} + virtual void benchmarkEnded( BenchmarkStats<> const& ) {} + virtual void benchmarkFailed( std::string const& ) {} +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + // Default empty implementation provided + virtual void fatalErrorEncountered( StringRef name ); + + virtual bool isMulti() const; + }; + using IStreamingReporterPtr = std::unique_ptr<IStreamingReporter>; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; + + struct IReporterRegistry { + using FactoryMap = std::map<std::string, IReporterFactoryPtr>; + using Listeners = std::vector<IReporterFactoryPtr>; + + virtual ~IReporterRegistry(); + virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; + +} // end namespace Catch + +// end catch_interfaces_reporter.h +#include <algorithm> +#include <cstring> +#include <cfloat> +#include <cstdio> +#include <cassert> +#include <memory> +#include <ostream> + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result); + + // Returns double formatted as %.3f (format expected on output) + std::string getFormattedDuration( double duration ); + + //! Should the reporter show + bool shouldShowDuration( IConfig const& config, double duration ); + + std::string serializeFilters( std::vector<std::string> const& container ); + + template<typename DerivedT> + struct StreamingReporterBase : IStreamingReporter { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + CATCH_ERROR( "Verbosity level not supported by this reporter" ); + } + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set<Verbosity> getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + ~StreamingReporterBase() override = default; + + void noMatchingTestCases(std::string const&) override {} + + void reportInvalidArguments(std::string const&) override {} + + void testRunStarting(TestRunInfo const& _testRunInfo) override { + currentTestRunInfo = _testRunInfo; + } + + void testGroupStarting(GroupInfo const& _groupInfo) override { + currentGroupInfo = _groupInfo; + } + + void testCaseStarting(TestCaseInfo const& _testInfo) override { + currentTestCaseInfo = _testInfo; + } + void sectionStarting(SectionInfo const& _sectionInfo) override { + m_sectionStack.push_back(_sectionInfo); + } + + void sectionEnded(SectionStats const& /* _sectionStats */) override { + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { + currentTestCaseInfo.reset(); + } + void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override { + currentGroupInfo.reset(); + } + void testRunEnded(TestRunStats const& /* _testRunStats */) override { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + void skipTest(TestCaseInfo const&) override { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + IConfigPtr m_config; + std::ostream& stream; + + LazyStat<TestRunInfo> currentTestRunInfo; + LazyStat<GroupInfo> currentGroupInfo; + LazyStat<TestCaseInfo> currentTestCaseInfo; + + std::vector<SectionInfo> m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + template<typename DerivedT> + struct CumulativeReporterBase : IStreamingReporter { + template<typename T, typename ChildNodeT> + struct Node { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + using ChildNodes = std::vector<std::shared_ptr<ChildNodeT>>; + T value; + ChildNodes children; + }; + struct SectionNode { + explicit SectionNode(SectionStats const& _stats) : stats(_stats) {} + virtual ~SectionNode() = default; + + bool operator == (SectionNode const& other) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == (std::shared_ptr<SectionNode> const& other) const { + return operator==(*other); + } + + SectionStats stats; + using ChildSections = std::vector<std::shared_ptr<SectionNode>>; + using Assertions = std::vector<AssertionStats>; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() (std::shared_ptr<SectionNode> const& node) const { + return ((node->stats.sectionInfo.name == m_other.name) && + (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); + } + void operator=(BySectionInfo const&) = delete; + + private: + SectionInfo const& m_other; + }; + + using TestCaseNode = Node<TestCaseStats, SectionNode>; + using TestGroupNode = Node<TestGroupStats, TestCaseNode>; + using TestRunNode = Node<TestRunStats, TestGroupNode>; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) { - if( !data.testsOrTags.empty() ) { - TestSpecParser parser( ITagAliasRegistry::get() ); - for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) - parser.parse( data.testsOrTags[i] ); - m_testSpec = parser.testSpec(); + m_reporterPrefs.shouldRedirectStdOut = false; + if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) + CATCH_ERROR( "Verbosity level not supported by this reporter" ); + } + ~CumulativeReporterBase() override = default; + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; + } + + static std::set<Verbosity> getSupportedVerbosities() { + return { Verbosity::Normal }; + } + + void testRunStarting( TestRunInfo const& ) override {} + void testGroupStarting( GroupInfo const& ) override {} + + void testCaseStarting( TestCaseInfo const& ) override {} + + void sectionStarting( SectionInfo const& sectionInfo ) override { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + std::shared_ptr<SectionNode> node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = std::make_shared<SectionNode>( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + auto it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = std::make_shared<SectionNode>( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; } + m_sectionStack.push_back( node ); + m_deepestSection = std::move(node); } - virtual ~Config() {} + void assertionStarting(AssertionInfo const&) override {} - std::string const& getFilename() const { - return m_data.outputFilename ; + bool assertionEnded(AssertionStats const& assertionStats) override { + assert(!m_sectionStack.empty()); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression(const_cast<AssertionResult&>( assertionStats.assertionResult ) ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back(assertionStats); + return true; } + void sectionEnded(SectionStats const& sectionStats) override { + assert(!m_sectionStack.empty()); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + void testCaseEnded(TestCaseStats const& testCaseStats) override { + auto node = std::make_shared<TestCaseNode>(testCaseStats); + assert(m_sectionStack.size() == 0); + node->children.push_back(m_rootSection); + m_testCases.push_back(node); + m_rootSection.reset(); - bool listTests() const { return m_data.listTests; } - bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } - bool listTags() const { return m_data.listTags; } - bool listReporters() const { return m_data.listReporters; } - bool listExtraInfo() const { return m_data.listExtraInfo; } + assert(m_deepestSection); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + void testGroupEnded(TestGroupStats const& testGroupStats) override { + auto node = std::make_shared<TestGroupNode>(testGroupStats); + node->children.swap(m_testCases); + m_testGroups.push_back(node); + } + void testRunEnded(TestRunStats const& testRunStats) override { + auto node = std::make_shared<TestRunNode>(testRunStats); + node->children.swap(m_testGroups); + m_testRuns.push_back(node); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; - std::string getProcessName() const { return m_data.processName; } + void skipTest(TestCaseInfo const&) override {} - std::vector<std::string> const& getReporterNames() const { return m_data.reporterNames; } - std::vector<std::string> const& getSectionsToRun() const CATCH_OVERRIDE { return m_data.sectionsToRun; } + IConfigPtr m_config; + std::ostream& stream; + std::vector<AssertionStats> m_assertions; + std::vector<std::vector<std::shared_ptr<SectionNode>>> m_sections; + std::vector<std::shared_ptr<TestCaseNode>> m_testCases; + std::vector<std::shared_ptr<TestGroupNode>> m_testGroups; - virtual TestSpec const& testSpec() const CATCH_OVERRIDE { return m_testSpec; } + std::vector<std::shared_ptr<TestRunNode>> m_testRuns; - bool showHelp() const { return m_data.showHelp; } + std::shared_ptr<SectionNode> m_rootSection; + std::shared_ptr<SectionNode> m_deepestSection; + std::vector<std::shared_ptr<SectionNode>> m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; - // IConfig interface - virtual bool allowThrows() const CATCH_OVERRIDE { return !m_data.noThrow; } - virtual std::ostream& stream() const CATCH_OVERRIDE { return m_stream->stream(); } - virtual std::string name() const CATCH_OVERRIDE { return m_data.name.empty() ? m_data.processName : m_data.name; } - virtual bool includeSuccessfulResults() const CATCH_OVERRIDE { return m_data.showSuccessfulTests; } - virtual bool warnAboutMissingAssertions() const CATCH_OVERRIDE { return m_data.warnings & WarnAbout::NoAssertions; } - virtual ShowDurations::OrNot showDurations() const CATCH_OVERRIDE { return m_data.showDurations; } - virtual RunTests::InWhatOrder runOrder() const CATCH_OVERRIDE { return m_data.runOrder; } - virtual unsigned int rngSeed() const CATCH_OVERRIDE { return m_data.rngSeed; } - virtual UseColour::YesOrNo useColour() const CATCH_OVERRIDE { return m_data.useColour; } - virtual bool shouldDebugBreak() const CATCH_OVERRIDE { return m_data.shouldDebugBreak; } - virtual int abortAfter() const CATCH_OVERRIDE { return m_data.abortAfter; } - virtual bool showInvisibles() const CATCH_OVERRIDE { return m_data.showInvisibles; } + template<char C> + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase<TestEventListenerBase> { + TestEventListenerBase( ReporterConfig const& _config ); + + static std::set<Verbosity> getSupportedVerbosities(); + + void assertionStarting(AssertionInfo const&) override; + bool assertionEnded(AssertionStats const&) override; + }; + +} // end namespace Catch + +// end catch_reporter_bases.hpp +// start catch_console_colour.h + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour&& other ) noexcept; + Colour& operator=( Colour&& other ) noexcept; + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); private: + bool m_moved = false; + }; - IStream const* openStream() { - if( m_data.outputFilename.empty() ) - return new CoutStream(); - else if( m_data.outputFilename[0] == '%' ) { - if( m_data.outputFilename == "%debug" ) - return new DebugOutStream(); - else - throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename ); + std::ostream& operator << ( std::ostream& os, Colour const& ); + +} // end namespace Catch + +// end catch_console_colour.h +// start catch_reporter_registrars.hpp + + +namespace Catch { + + template<typename T> + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr<T>( new T( config ) ); } - else - return new FileStream( m_data.outputFilename ); + + std::string getDescription() const override { + return T::getDescription(); + } + }; + + public: + + explicit ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, std::make_shared<ReporterFactory>() ); } - ConfigData m_data; + }; + + template<typename T> + class ListenerRegistrar { + + class ListenerFactory : public IReporterFactory { + + IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr<T>( new T( config ) ); + } + std::string getDescription() const override { + return std::string(); + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( std::make_shared<ListenerFactory>() ); + } + }; +} + +#if !defined(CATCH_CONFIG_DISABLE) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#else // CATCH_CONFIG_DISABLE + +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) + +#endif // CATCH_CONFIG_DISABLE + +// end catch_reporter_registrars.hpp +// Allow users to base their work off existing reporters +// start catch_reporter_compact.h + +namespace Catch { + + struct CompactReporter : StreamingReporterBase<CompactReporter> { + + using StreamingReporterBase::StreamingReporterBase; + + ~CompactReporter() override; + + static std::string getDescription(); + + void noMatchingTestCases(std::string const& spec) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionEnded(SectionStats const& _sectionStats) override; + + void testRunEnded(TestRunStats const& _testRunStats) override; - CATCH_AUTO_PTR( IStream const ) m_stream; - TestSpec m_testSpec; }; } // end namespace Catch -// #included from: catch_clara.h -#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED +// end catch_reporter_compact.h +// start catch_reporter_console.h -// Use Catch's value for console width (store Clara's off to the side, if present) -#ifdef CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH -#undef CLARA_CONFIG_CONSOLE_WIDTH +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled #endif -#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH -// Declare Clara inside the Catch namespace -#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { -// #included from: ../external/clara.h +namespace Catch { + // Fwd decls + struct SummaryColumn; + class TablePrinter; -// Version 0.0.2.4 + struct ConsoleReporter : StreamingReporterBase<ConsoleReporter> { + std::unique_ptr<TablePrinter> m_tablePrinter; -// Only use header guard if we are not using an outer namespace -#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + ConsoleReporter(ReporterConfig const& config); + ~ConsoleReporter() override; + static std::string getDescription(); -#ifndef STITCH_CLARA_OPEN_NAMESPACE -#define TWOBLUECUBES_CLARA_H_INCLUDED -#define STITCH_CLARA_OPEN_NAMESPACE -#define STITCH_CLARA_CLOSE_NAMESPACE -#else -#define STITCH_CLARA_CLOSE_NAMESPACE } -#endif + void noMatchingTestCases(std::string const& spec) override; + + void reportInvalidArguments(std::string const&arg) override; + + void assertionStarting(AssertionInfo const&) override; + + bool assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionStarting(SectionInfo const& _sectionInfo) override; + void sectionEnded(SectionStats const& _sectionStats) override; + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void benchmarkPreparing(std::string const& name) override; + void benchmarkStarting(BenchmarkInfo const& info) override; + void benchmarkEnded(BenchmarkStats<> const& stats) override; + void benchmarkFailed(std::string const& error) override; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + + void testCaseEnded(TestCaseStats const& _testCaseStats) override; + void testGroupEnded(TestGroupStats const& _testGroupStats) override; + void testRunEnded(TestRunStats const& _testRunStats) override; + void testRunStarting(TestRunInfo const& _testRunInfo) override; + private: + + void lazyPrint(); + + void lazyPrintWithoutClosingBenchmarkTable(); + void lazyPrintRunInfo(); + void lazyPrintGroupInfo(); + void printTestCaseAndSectionHeader(); + + void printClosedHeader(std::string const& _name); + void printOpenHeader(std::string const& _name); + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::string const& _string, std::size_t indent = 0); -#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + void printTotals(Totals const& totals); + void printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row); -// ----------- #included from tbc_text_format.h ----------- + void printTotalsDivider(Totals const& totals); + void printSummaryDivider(); + void printTestFilters(); -// Only use header guard if we are not using an outer namespace -#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) -#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE -#define TBC_TEXT_FORMAT_H_INCLUDED + private: + bool m_headerPrinted = false; + }; + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) #endif -#include <string> +// end catch_reporter_console.h +// start catch_reporter_junit.h + +// start catch_xmlwriter.h + #include <vector> -#include <sstream> -#include <algorithm> -#include <cctype> -// Use optional outer namespace -#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE -namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { -#endif +namespace Catch { + enum class XmlFormatting { + None = 0x00, + Indent = 0x01, + Newline = 0x02, + }; -namespace Tbc { + XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs); + XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs); -#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH - const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; -#else - const unsigned int consoleWidth = 80; -#endif + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; - struct TextAttributes { - TextAttributes() - : initialIndent( std::string::npos ), - indent( 0 ), - width( consoleWidth-1 ), - tabChar( '\t' ) - {} + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); - TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } - TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } - TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } - TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + void encodeTo( std::ostream& os ) const; - std::size_t initialIndent; // indent of first line, or npos - std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos - std::size_t width; // maximum width of text, including indent. Longer text will wrap - char tabChar; // If this char is seen the indent is changed to current pos + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; }; - class Text { + class XmlWriter { public: - Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) - : attr( _attr ) - { - std::string wrappableChars = " [({.,/|\\-"; - std::size_t indent = _attr.initialIndent != std::string::npos - ? _attr.initialIndent - : _attr.indent; - std::string remainder = _str; - - while( !remainder.empty() ) { - if( lines.size() >= 1000 ) { - lines.push_back( "... message truncated due to excessive size" ); - return; - } - std::size_t tabPos = std::string::npos; - std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); - std::size_t pos = remainder.find_first_of( '\n' ); - if( pos <= width ) { - width = pos; - } - pos = remainder.find_last_of( _attr.tabChar, width ); - if( pos != std::string::npos ) { - tabPos = pos; - if( remainder[width] == '\n' ) - width--; - remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); - } - if( width == remainder.size() ) { - spliceLine( indent, remainder, width ); - } - else if( remainder[width] == '\n' ) { - spliceLine( indent, remainder, width ); - if( width <= 1 || remainder.size() != 1 ) - remainder = remainder.substr( 1 ); - indent = _attr.indent; - } - else { - pos = remainder.find_last_of( wrappableChars, width ); - if( pos != std::string::npos && pos > 0 ) { - spliceLine( indent, remainder, pos ); - if( remainder[0] == ' ' ) - remainder = remainder.substr( 1 ); - } - else { - spliceLine( indent, remainder, width-1 ); - lines.back() += "-"; - } - if( lines.size() == 1 ) - indent = _attr.indent; - if( tabPos != std::string::npos ) - indent += tabPos; - } - } - } + class ScopedElement { + public: + ScopedElement( XmlWriter* writer, XmlFormatting fmt ); - void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { - lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); - _remainder = _remainder.substr( _pos ); - } + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; - typedef std::vector<std::string>::const_iterator const_iterator; + ~ScopedElement(); - const_iterator begin() const { return lines.begin(); } - const_iterator end() const { return lines.end(); } - std::string const& last() const { return lines.back(); } - std::size_t size() const { return lines.size(); } - std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } - std::string toString() const { - std::ostringstream oss; - oss << *this; - return oss.str(); - } + ScopedElement& writeText( std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent ); - friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { - for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); - it != itEnd; ++it ) { - if( it != _text.begin() ) - _stream << "\n"; - _stream << *it; + template<typename T> + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; } - return _stream; + + private: + mutable XmlWriter* m_writer = nullptr; + XmlFormatting m_fmt; + }; + + XmlWriter( std::ostream& os = Catch::cout() ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + ScopedElement scopedElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + XmlWriter& endElement(XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template<typename T> + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + ReusableStringStream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); } + XmlWriter& writeText( std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + XmlWriter& writeComment(std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + void writeStylesheetRef( std::string const& url ); + + XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + private: - std::string str; - TextAttributes attr; - std::vector<std::string> lines; + + void applyFormatting(XmlFormatting fmt); + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector<std::string> m_tags; + std::string m_indent; + std::ostream& m_os; }; -} // end namespace Tbc +} -#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE -} // end outer namespace -#endif +// end catch_xmlwriter.h +namespace Catch { -#endif // TBC_TEXT_FORMAT_H_INCLUDED + class JunitReporter : public CumulativeReporterBase<JunitReporter> { + public: + JunitReporter(ReporterConfig const& _config); -// ----------- end of #include from tbc_text_format.h ----------- -// ........... back in clara.h + ~JunitReporter() override; -#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + static std::string getDescription(); -// ----------- #included from clara_compilers.h ----------- + void noMatchingTestCases(std::string const& /*spec*/) override; -#ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED -#define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + void testRunStarting(TestRunInfo const& runInfo) override; -// Detect a number of compiler features - mostly C++11/14 conformance - by compiler -// The following features are defined: -// -// CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? -// CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? -// CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods -// CLARA_CONFIG_CPP11_OVERRIDE : is override supported? -// CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) + void testGroupStarting(GroupInfo const& groupInfo) override; -// CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + void testCaseStarting(TestCaseInfo const& testCaseInfo) override; + bool assertionEnded(AssertionStats const& assertionStats) override; -// CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? + void testCaseEnded(TestCaseStats const& testCaseStats) override; -// In general each macro has a _NO_<feature name> form -// (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. -// Many features, at point of detection, define an _INTERNAL_ macro, so they -// can be combined, en-mass, with the _NO_ forms later. + void testGroupEnded(TestGroupStats const& testGroupStats) override; -// All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 + void testRunEndedCumulative() override; -#ifdef __clang__ + void writeGroup(TestGroupNode const& groupNode, double suiteTime); -#if __has_feature(cxx_nullptr) -#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR -#endif + void writeTestCase(TestCaseNode const& testCaseNode); -#if __has_feature(cxx_noexcept) -#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT -#endif + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail ); -#endif // __clang__ + void writeAssertions(SectionNode const& sectionNode); + void writeAssertion(AssertionStats const& stats); -//////////////////////////////////////////////////////////////////////////////// -// GCC -#ifdef __GNUC__ + XmlWriter xml; + Timer suiteTimer; + std::string stdOutForSuite; + std::string stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; + }; -#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) -#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR -#endif +} // end namespace Catch -// - otherwise more recent versions define __cplusplus >= 201103L -// and will get picked up below +// end catch_reporter_junit.h +// start catch_reporter_xml.h -#endif // __GNUC__ +namespace Catch { + class XmlReporter : public StreamingReporterBase<XmlReporter> { + public: + XmlReporter(ReporterConfig const& _config); -//////////////////////////////////////////////////////////////////////////////// -// Visual C++ -#ifdef _MSC_VER + ~XmlReporter() override; -#if (_MSC_VER >= 1600) -#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR -#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR -#endif + static std::string getDescription(); -#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) -#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT -#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -#endif + virtual std::string getStylesheetRef() const; -#endif // _MSC_VER + void writeSourceInfo(SourceLineInfo const& sourceInfo); -//////////////////////////////////////////////////////////////////////////////// -// C++ language feature support + public: // StreamingReporterBase -// catch all support for C++11 -#if defined(__cplusplus) && __cplusplus >= 201103L + void noMatchingTestCases(std::string const& s) override; -#define CLARA_CPP11_OR_GREATER + void testRunStarting(TestRunInfo const& testInfo) override; -#if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) -#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR -#endif + void testGroupStarting(GroupInfo const& groupInfo) override; -#ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT -#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT -#endif + void testCaseStarting(TestCaseInfo const& testInfo) override; -#ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -#endif + void sectionStarting(SectionInfo const& sectionInfo) override; -#if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) -#define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE -#endif -#if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) -#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR -#endif + void assertionStarting(AssertionInfo const&) override; -#endif // __cplusplus >= 201103L + bool assertionEnded(AssertionStats const& assertionStats) override; -// Now set the actual defines based on the above + anything the user has configured -#if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) -#define CLARA_CONFIG_CPP11_NULLPTR -#endif -#if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) -#define CLARA_CONFIG_CPP11_NOEXCEPT -#endif -#if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) -#define CLARA_CONFIG_CPP11_GENERATED_METHODS -#endif -#if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) -#define CLARA_CONFIG_CPP11_OVERRIDE -#endif -#if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) -#define CLARA_CONFIG_CPP11_UNIQUE_PTR -#endif + void sectionEnded(SectionStats const& sectionStats) override; -// noexcept support: -#if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) -#define CLARA_NOEXCEPT noexcept -# define CLARA_NOEXCEPT_IS(x) noexcept(x) -#else -#define CLARA_NOEXCEPT throw() -# define CLARA_NOEXCEPT_IS(x) -#endif + void testCaseEnded(TestCaseStats const& testCaseStats) override; -// nullptr support -#ifdef CLARA_CONFIG_CPP11_NULLPTR -#define CLARA_NULL nullptr -#else -#define CLARA_NULL NULL -#endif + void testGroupEnded(TestGroupStats const& testGroupStats) override; -// override support -#ifdef CLARA_CONFIG_CPP11_OVERRIDE -#define CLARA_OVERRIDE override -#else -#define CLARA_OVERRIDE -#endif + void testRunEnded(TestRunStats const& testRunStats) override; -// unique_ptr support -#ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR -# define CLARA_AUTO_PTR( T ) std::unique_ptr<T> -#else -# define CLARA_AUTO_PTR( T ) std::auto_ptr<T> -#endif +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void benchmarkPreparing(std::string const& name) override; + void benchmarkStarting(BenchmarkInfo const&) override; + void benchmarkEnded(BenchmarkStats<> const&) override; + void benchmarkFailed(std::string const&) override; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING -#endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; + }; -// ----------- end of #include from clara_compilers.h ----------- -// ........... back in clara.h +} // end namespace Catch -#include <map> -#include <stdexcept> -#include <memory> +// end catch_reporter_xml.h -#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) -#define CLARA_PLATFORM_WINDOWS +// end catch_external_interfaces.h #endif -// Use optional outer namespace -#ifdef STITCH_CLARA_OPEN_NAMESPACE -STITCH_CLARA_OPEN_NAMESPACE -#endif +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +// start catch_benchmarking_all.hpp -namespace Clara { +// A proxy header that includes all of the benchmarking headers to allow +// concise include of the benchmarking features. You should prefer the +// individual includes in standard use. - struct UnpositionalTag {}; +// start catch_benchmark.hpp - extern UnpositionalTag _; + // Benchmark -#ifdef CLARA_CONFIG_MAIN - UnpositionalTag _; -#endif +// start catch_chronometer.hpp - namespace Detail { +// User-facing chronometer -#ifdef CLARA_CONSOLE_WIDTH - const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; -#else - const unsigned int consoleWidth = 80; + +// start catch_clock.hpp + +// Clocks + + +#include <chrono> +#include <ratio> + +namespace Catch { + namespace Benchmark { + template <typename Clock> + using ClockDuration = typename Clock::duration; + template <typename Clock> + using FloatDuration = std::chrono::duration<double, typename Clock::period>; + + template <typename Clock> + using TimePoint = typename Clock::time_point; + + using default_clock = std::chrono::steady_clock; + + template <typename Clock> + struct now { + TimePoint<Clock> operator()() const { + return Clock::now(); + } + }; + + using fp_seconds = std::chrono::duration<double, std::ratio<1>>; + } // namespace Benchmark +} // namespace Catch + +// end catch_clock.hpp +// start catch_optimizer.hpp + + // Hinting the optimizer + + +#if defined(_MSC_VER) +# include <atomic> // atomic_thread_fence #endif - using namespace Tbc; +namespace Catch { + namespace Benchmark { +#if defined(__GNUC__) || defined(__clang__) + template <typename T> + inline void keep_memory(T* p) { + asm volatile("" : : "g"(p) : "memory"); + } + inline void keep_memory() { + asm volatile("" : : : "memory"); + } + + namespace Detail { + inline void optimizer_barrier() { keep_memory(); } + } // namespace Detail +#elif defined(_MSC_VER) - inline bool startsWith( std::string const& str, std::string const& prefix ) { - return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; +#pragma optimize("", off) + template <typename T> + inline void keep_memory(T* p) { + // thanks @milleniumbug + *reinterpret_cast<char volatile*>(p) = *reinterpret_cast<char const volatile*>(p); } + // TODO equivalent keep_memory() +#pragma optimize("", on) - template<typename T> struct RemoveConstRef{ typedef T type; }; - template<typename T> struct RemoveConstRef<T&>{ typedef T type; }; - template<typename T> struct RemoveConstRef<T const&>{ typedef T type; }; - template<typename T> struct RemoveConstRef<T const>{ typedef T type; }; + namespace Detail { + inline void optimizer_barrier() { + std::atomic_thread_fence(std::memory_order_seq_cst); + } + } // namespace Detail - template<typename T> struct IsBool { static const bool value = false; }; - template<> struct IsBool<bool> { static const bool value = true; }; +#endif - template<typename T> - void convertInto( std::string const& _source, T& _dest ) { - std::stringstream ss; - ss << _source; - ss >> _dest; - if( ss.fail() ) - throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + template <typename T> + inline void deoptimize_value(T&& x) { + keep_memory(&x); } - inline void convertInto( std::string const& _source, std::string& _dest ) { - _dest = _source; + + template <typename Fn, typename... Args> + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> typename std::enable_if<!std::is_same<void, decltype(fn(args...))>::value>::type { + deoptimize_value(std::forward<Fn>(fn) (std::forward<Args...>(args...))); } - char toLowerCh(char c) { - return static_cast<char>( std::tolower( c ) ); - } - inline void convertInto( std::string const& _source, bool& _dest ) { - std::string sourceLC = _source; - std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh ); - if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) - _dest = true; - else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) - _dest = false; - else - throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); + + template <typename Fn, typename... Args> + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> typename std::enable_if<std::is_same<void, decltype(fn(args...))>::value>::type { + std::forward<Fn>(fn) (std::forward<Args...>(args...)); } + } // namespace Benchmark +} // namespace Catch - template<typename ConfigT> - struct IArgFunction { - virtual ~IArgFunction() {} -#ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS - IArgFunction() = default; - IArgFunction( IArgFunction const& ) = default; -#endif - virtual void set( ConfigT& config, std::string const& value ) const = 0; - virtual bool takesArg() const = 0; - virtual IArgFunction* clone() const = 0; - }; +// end catch_optimizer.hpp +// start catch_complete_invoke.hpp - template<typename ConfigT> - class BoundArgFunction { - public: - BoundArgFunction() : functionObj( CLARA_NULL ) {} - BoundArgFunction( IArgFunction<ConfigT>* _functionObj ) : functionObj( _functionObj ) {} - BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} - BoundArgFunction& operator = ( BoundArgFunction const& other ) { - IArgFunction<ConfigT>* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; - delete functionObj; - functionObj = newFunctionObj; - return *this; - } - ~BoundArgFunction() { delete functionObj; } +// Invoke with a special case for void + + +#include <type_traits> +#include <utility> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename T> + struct CompleteType { using type = T; }; + template <> + struct CompleteType<void> { struct type {}; }; + + template <typename T> + using CompleteType_t = typename CompleteType<T>::type; + + template <typename Result> + struct CompleteInvoker { + template <typename Fun, typename... Args> + static Result invoke(Fun&& fun, Args&&... args) { + return std::forward<Fun>(fun)(std::forward<Args>(args)...); + } + }; + template <> + struct CompleteInvoker<void> { + template <typename Fun, typename... Args> + static CompleteType_t<void> invoke(Fun&& fun, Args&&... args) { + std::forward<Fun>(fun)(std::forward<Args>(args)...); + return {}; + } + }; - void set( ConfigT& config, std::string const& value ) const { - functionObj->set( config, value ); + // invoke and not return void :( + template <typename Fun, typename... Args> + CompleteType_t<FunctionReturnType<Fun, Args...>> complete_invoke(Fun&& fun, Args&&... args) { + return CompleteInvoker<FunctionReturnType<Fun, Args...>>::invoke(std::forward<Fun>(fun), std::forward<Args>(args)...); } - bool takesArg() const { return functionObj->takesArg(); } - bool isSet() const { - return functionObj != CLARA_NULL; + const std::string benchmarkErrorMsg = "a benchmark failed to run successfully"; + } // namespace Detail + + template <typename Fun> + Detail::CompleteType_t<FunctionReturnType<Fun>> user_code(Fun&& fun) { + CATCH_TRY{ + return Detail::complete_invoke(std::forward<Fun>(fun)); + } CATCH_CATCH_ALL{ + getResultCapture().benchmarkFailed(translateActiveException()); + CATCH_RUNTIME_ERROR(Detail::benchmarkErrorMsg); } + } + } // namespace Benchmark +} // namespace Catch + +// end catch_complete_invoke.hpp +namespace Catch { + namespace Benchmark { + namespace Detail { + struct ChronometerConcept { + virtual void start() = 0; + virtual void finish() = 0; + virtual ~ChronometerConcept() = default; + }; + template <typename Clock> + struct ChronometerModel final : public ChronometerConcept { + void start() override { started = Clock::now(); } + void finish() override { finished = Clock::now(); } + + ClockDuration<Clock> elapsed() const { return finished - started; } + + TimePoint<Clock> started; + TimePoint<Clock> finished; + }; + } // namespace Detail + + struct Chronometer { + public: + template <typename Fun> + void measure(Fun&& fun) { measure(std::forward<Fun>(fun), is_callable<Fun(int)>()); } + + int runs() const { return k; } + + Chronometer(Detail::ChronometerConcept& meter, int k) + : impl(&meter) + , k(k) {} + private: - IArgFunction<ConfigT>* functionObj; - }; + template <typename Fun> + void measure(Fun&& fun, std::false_type) { + measure([&fun](int) { return fun(); }, std::true_type()); + } + + template <typename Fun> + void measure(Fun&& fun, std::true_type) { + Detail::optimizer_barrier(); + impl->start(); + for (int i = 0; i < k; ++i) invoke_deoptimized(fun, i); + impl->finish(); + Detail::optimizer_barrier(); + } - template<typename C> - struct NullBinder : IArgFunction<C>{ - virtual void set( C&, std::string const& ) const {} - virtual bool takesArg() const { return true; } - virtual IArgFunction<C>* clone() const { return new NullBinder( *this ); } + Detail::ChronometerConcept* impl; + int k; }; + } // namespace Benchmark +} // namespace Catch + +// end catch_chronometer.hpp +// start catch_environment.hpp + +// Environment information + - template<typename C, typename M> - struct BoundDataMember : IArgFunction<C>{ - BoundDataMember( M C::* _member ) : member( _member ) {} - virtual void set( C& p, std::string const& stringValue ) const { - convertInto( stringValue, p.*member ); +namespace Catch { + namespace Benchmark { + template <typename Duration> + struct EnvironmentEstimate { + Duration mean; + OutlierClassification outliers; + + template <typename Duration2> + operator EnvironmentEstimate<Duration2>() const { + return { mean, outliers }; } - virtual bool takesArg() const { return !IsBool<M>::value; } - virtual IArgFunction<C>* clone() const { return new BoundDataMember( *this ); } - M C::* member; - }; - template<typename C, typename M> - struct BoundUnaryMethod : IArgFunction<C>{ - BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} - virtual void set( C& p, std::string const& stringValue ) const { - typename RemoveConstRef<M>::type value; - convertInto( stringValue, value ); - (p.*member)( value ); - } - virtual bool takesArg() const { return !IsBool<M>::value; } - virtual IArgFunction<C>* clone() const { return new BoundUnaryMethod( *this ); } - void (C::*member)( M ); }; - template<typename C> - struct BoundNullaryMethod : IArgFunction<C>{ - BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} - virtual void set( C& p, std::string const& stringValue ) const { - bool value; - convertInto( stringValue, value ); - if( value ) - (p.*member)(); - } - virtual bool takesArg() const { return false; } - virtual IArgFunction<C>* clone() const { return new BoundNullaryMethod( *this ); } - void (C::*member)(); + template <typename Clock> + struct Environment { + using clock_type = Clock; + EnvironmentEstimate<FloatDuration<Clock>> clock_resolution; + EnvironmentEstimate<FloatDuration<Clock>> clock_cost; }; + } // namespace Benchmark +} // namespace Catch - template<typename C> - struct BoundUnaryFunction : IArgFunction<C>{ - BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} - virtual void set( C& obj, std::string const& stringValue ) const { - bool value; - convertInto( stringValue, value ); - if( value ) - function( obj ); - } - virtual bool takesArg() const { return false; } - virtual IArgFunction<C>* clone() const { return new BoundUnaryFunction( *this ); } - void (*function)( C& ); - }; +// end catch_environment.hpp +// start catch_execution_plan.hpp - template<typename C, typename T> - struct BoundBinaryFunction : IArgFunction<C>{ - BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} - virtual void set( C& obj, std::string const& stringValue ) const { - typename RemoveConstRef<T>::type value; - convertInto( stringValue, value ); - function( obj, value ); - } - virtual bool takesArg() const { return !IsBool<T>::value; } - virtual IArgFunction<C>* clone() const { return new BoundBinaryFunction( *this ); } - void (*function)( C&, T ); - }; + // Execution plan - } // namespace Detail - inline std::vector<std::string> argsToVector( int argc, char const* const* const argv ) { - std::vector<std::string> args( static_cast<std::size_t>( argc ) ); - for( std::size_t i = 0; i < static_cast<std::size_t>( argc ); ++i ) - args[i] = argv[i]; +// start catch_benchmark_function.hpp - return args; - } + // Dumb std::function implementation for consistent call overhead - class Parser { - enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; - Mode mode; - std::size_t from; - bool inQuotes; - public: - struct Token { - enum Type { Positional, ShortOpt, LongOpt }; - Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} - Type type; - std::string data; - }; +#include <cassert> +#include <type_traits> +#include <utility> +#include <memory> - Parser() : mode( None ), from( 0 ), inQuotes( false ){} +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename T> + using Decay = typename std::decay<T>::type; + template <typename T, typename U> + struct is_related + : std::is_same<Decay<T>, Decay<U>> {}; + + /// 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 + /// can account for it in the measurement. + /// Implementations of std::function with optimizations that aren't always applicable, like + /// small buffer optimizations, are not uncommon. + /// This is effectively an implementation of std::function without any such optimizations; + /// it may be slow, but it is consistently slow. + struct BenchmarkFunction { + private: + struct callable { + virtual void call(Chronometer meter) const = 0; + virtual callable* clone() const = 0; + virtual ~callable() = default; + }; + template <typename Fun> + struct model : public callable { + model(Fun&& fun) : fun(std::move(fun)) {} + model(Fun const& fun) : fun(fun) {} + + model<Fun>* clone() const override { return new model<Fun>(*this); } + + void call(Chronometer meter) const override { + call(meter, is_callable<Fun(Chronometer)>()); + } + void call(Chronometer meter, std::true_type) const { + fun(meter); + } + void call(Chronometer meter, std::false_type) const { + meter.measure(fun); + } - void parseIntoTokens( std::vector<std::string> const& args, std::vector<Token>& tokens ) { - const std::string doubleDash = "--"; - for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) - parseIntoTokens( args[i], tokens); - } + Fun fun; + }; - void parseIntoTokens( std::string const& arg, std::vector<Token>& tokens ) { - for( std::size_t i = 0; i < arg.size(); ++i ) { - char c = arg[i]; - if( c == '"' ) - inQuotes = !inQuotes; - mode = handleMode( i, c, arg, tokens ); - } - mode = handleMode( arg.size(), '\0', arg, tokens ); - } - Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) { - switch( mode ) { - case None: return handleNone( i, c ); - case MaybeShortOpt: return handleMaybeShortOpt( i, c ); - case ShortOpt: - case LongOpt: - case SlashOpt: return handleOpt( i, c, arg, tokens ); - case Positional: return handlePositional( i, c, arg, tokens ); - default: throw std::logic_error( "Unknown mode" ); - } - } + struct do_nothing { void operator()() const {} }; - Mode handleNone( std::size_t i, char c ) { - if( inQuotes ) { - from = i; - return Positional; - } - switch( c ) { - case '-': return MaybeShortOpt; -#ifdef CLARA_PLATFORM_WINDOWS - case '/': from = i+1; return SlashOpt; -#endif - default: from = i; return Positional; - } - } - Mode handleMaybeShortOpt( std::size_t i, char c ) { - switch( c ) { - case '-': from = i+1; return LongOpt; - default: from = i; return ShortOpt; + template <typename T> + BenchmarkFunction(model<T>* c) : f(c) {} + + public: + BenchmarkFunction() + : f(new model<do_nothing>{ {} }) {} + + template <typename Fun, + typename std::enable_if<!is_related<Fun, BenchmarkFunction>::value, int>::type = 0> + BenchmarkFunction(Fun&& fun) + : f(new model<typename std::decay<Fun>::type>(std::forward<Fun>(fun))) {} + + BenchmarkFunction(BenchmarkFunction&& that) + : f(std::move(that.f)) {} + + BenchmarkFunction(BenchmarkFunction const& that) + : f(that.f->clone()) {} + + BenchmarkFunction& operator=(BenchmarkFunction&& that) { + f = std::move(that.f); + return *this; + } + + BenchmarkFunction& operator=(BenchmarkFunction const& that) { + f.reset(that.f->clone()); + return *this; + } + + void operator()(Chronometer meter) const { f->call(meter); } + + private: + std::unique_ptr<callable> f; + }; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// end catch_benchmark_function.hpp +// start catch_repeat.hpp + +// repeat algorithm + + +#include <type_traits> +#include <utility> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Fun> + struct repeater { + void operator()(int k) const { + for (int i = 0; i < k; ++i) { + fun(); + } + } + Fun fun; + }; + template <typename Fun> + repeater<typename std::decay<Fun>::type> repeat(Fun&& fun) { + return { std::forward<Fun>(fun) }; } - } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch - Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) { - if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) - return mode; +// end catch_repeat.hpp +// start catch_run_for_at_least.hpp - std::string optName = arg.substr( from, i-from ); - if( mode == ShortOpt ) - for( std::size_t j = 0; j < optName.size(); ++j ) - tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); - else if( mode == SlashOpt && optName.size() == 1 ) - tokens.push_back( Token( Token::ShortOpt, optName ) ); - else - tokens.push_back( Token( Token::LongOpt, optName ) ); - return None; - } - Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) { - if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) - return mode; +// Run a function for a minimum amount of time - std::string data = arg.substr( from, i-from ); - tokens.push_back( Token( Token::Positional, data ) ); - return None; - } - }; - template<typename ConfigT> - struct CommonArgProperties { - CommonArgProperties() {} - CommonArgProperties( Detail::BoundArgFunction<ConfigT> const& _boundField ) : boundField( _boundField ) {} +// start catch_measure.hpp - Detail::BoundArgFunction<ConfigT> boundField; - std::string description; - std::string detail; - std::string placeholder; // Only value if boundField takes an arg +// Measure - bool takesArg() const { - return !placeholder.empty(); - } - void validate() const { - if( !boundField.isSet() ) - throw std::logic_error( "option not bound" ); - } - }; - struct OptionArgProperties { - std::vector<std::string> shortNames; - std::string longName; - bool hasShortName( std::string const& shortName ) const { - return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); - } - bool hasLongName( std::string const& _longName ) const { - return _longName == longName; - } - }; - struct PositionalArgProperties { - PositionalArgProperties() : position( -1 ) {} - int position; // -1 means non-positional (floating) +// start catch_timing.hpp - bool isFixedPositional() const { - return position != -1; - } - }; +// Timing + + +#include <tuple> +#include <type_traits> + +namespace Catch { + namespace Benchmark { + template <typename Duration, typename Result> + struct Timing { + Duration elapsed; + Result result; + int iterations; + }; + template <typename Clock, typename Func, typename... Args> + using TimingOf = Timing<ClockDuration<Clock>, Detail::CompleteType_t<FunctionReturnType<Func, Args...>>>; + } // namespace Benchmark +} // namespace Catch - template<typename ConfigT> - class CommandLine { +// end catch_timing.hpp +#include <utility> - struct Arg : CommonArgProperties<ConfigT>, OptionArgProperties, PositionalArgProperties { - Arg() {} - Arg( Detail::BoundArgFunction<ConfigT> const& _boundField ) : CommonArgProperties<ConfigT>( _boundField ) {} +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Clock, typename Fun, typename... Args> + TimingOf<Clock, Fun, Args...> measure(Fun&& fun, Args&&... args) { + auto start = Clock::now(); + auto&& r = Detail::complete_invoke(fun, std::forward<Args>(args)...); + auto end = Clock::now(); + auto delta = end - start; + return { delta, std::forward<decltype(r)>(r), 1 }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch - using CommonArgProperties<ConfigT>::placeholder; // !TBD +// end catch_measure.hpp +#include <utility> +#include <type_traits> - std::string dbgName() const { - if( !longName.empty() ) - return "--" + longName; - if( !shortNames.empty() ) - return "-" + shortNames[0]; - return "positional args"; +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Clock, typename Fun> + TimingOf<Clock, Fun, int> measure_one(Fun&& fun, int iters, std::false_type) { + return Detail::measure<Clock>(fun, iters); } - std::string commands() const { - std::ostringstream oss; - bool first = true; - std::vector<std::string>::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); - for(; it != itEnd; ++it ) { - if( first ) - first = false; - else - oss << ", "; - oss << "-" << *it; + template <typename Clock, typename Fun> + TimingOf<Clock, Fun, Chronometer> measure_one(Fun&& fun, int iters, std::true_type) { + Detail::ChronometerModel<Clock> meter; + auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters)); + + return { meter.elapsed(), std::move(result), iters }; + } + + template <typename Clock, typename Fun> + using run_for_at_least_argument_t = typename std::conditional<is_callable<Fun(Chronometer)>::value, Chronometer, int>::type; + + struct optimized_away_error : std::exception { + const char* what() const noexcept override { + return "could not measure benchmark, maybe it was optimized away"; } - if( !longName.empty() ) { - if( !first ) - oss << ", "; - oss << "--" << longName; + }; + + template <typename Clock, typename Fun> + TimingOf<Clock, Fun, run_for_at_least_argument_t<Clock, Fun>> run_for_at_least(ClockDuration<Clock> how_long, int seed, Fun&& fun) { + auto iters = seed; + while (iters < (1 << 30)) { + auto&& Timing = measure_one<Clock>(fun, iters, is_callable<Fun(Chronometer)>()); + + if (Timing.elapsed >= how_long) { + return { Timing.elapsed, std::move(Timing.result), iters }; + } + iters *= 2; } - if( !placeholder.empty() ) - oss << " <" << placeholder << ">"; - return oss.str(); + Catch::throw_exception(optimized_away_error{}); + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// end catch_run_for_at_least.hpp +#include <algorithm> +#include <iterator> + +namespace Catch { + namespace Benchmark { + template <typename Duration> + struct ExecutionPlan { + int iterations_per_sample; + Duration estimated_duration; + Detail::BenchmarkFunction benchmark; + Duration warmup_time; + int warmup_iterations; + + template <typename Duration2> + operator ExecutionPlan<Duration2>() const { + return { iterations_per_sample, estimated_duration, benchmark, warmup_time, warmup_iterations }; + } + + template <typename Clock> + std::vector<FloatDuration<Clock>> run(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const { + // warmup a bit + Detail::run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_iterations, Detail::repeat(now<Clock>{})); + + std::vector<FloatDuration<Clock>> times; + times.reserve(cfg.benchmarkSamples()); + std::generate_n(std::back_inserter(times), cfg.benchmarkSamples(), [this, env] { + Detail::ChronometerModel<Clock> model; + this->benchmark(Chronometer(model, iterations_per_sample)); + auto sample_time = model.elapsed() - env.clock_cost.mean; + if (sample_time < FloatDuration<Clock>::zero()) sample_time = FloatDuration<Clock>::zero(); + return sample_time / iterations_per_sample; + }); + return times; } }; + } // namespace Benchmark +} // namespace Catch - typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; +// end catch_execution_plan.hpp +// start catch_estimate_clock.hpp - friend void addOptName( Arg& arg, std::string const& optName ) - { - if( optName.empty() ) - return; - if( Detail::startsWith( optName, "--" ) ) { - if( !arg.longName.empty() ) - throw std::logic_error( "Only one long opt may be specified. '" - + arg.longName - + "' already specified, now attempting to add '" - + optName + "'" ); - arg.longName = optName.substr( 2 ); - } - else if( Detail::startsWith( optName, "-" ) ) - arg.shortNames.push_back( optName.substr( 1 ) ); - else - throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); - } - friend void setPositionalArg( Arg& arg, int position ) - { - arg.position = position; - } + // Environment measurement - class ArgBuilder { - public: - ArgBuilder( Arg* arg ) : m_arg( arg ) {} - // Bind a non-boolean data member (requires placeholder string) - template<typename C, typename M> - void bind( M C::* field, std::string const& placeholder ) { - m_arg->boundField = new Detail::BoundDataMember<C,M>( field ); - m_arg->placeholder = placeholder; +// start catch_stats.hpp + +// Statistical analysis tools + + +#include <algorithm> +#include <functional> +#include <vector> +#include <iterator> +#include <numeric> +#include <tuple> +#include <cmath> +#include <utility> +#include <cstddef> +#include <random> + +namespace Catch { + namespace Benchmark { + namespace Detail { + using sample = std::vector<double>; + + double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, std::vector<double>::iterator last); + + template <typename Iterator> + OutlierClassification classify_outliers(Iterator first, Iterator last) { + std::vector<double> copy(first, last); + + auto q1 = weighted_average_quantile(1, 4, copy.begin(), copy.end()); + auto q3 = weighted_average_quantile(3, 4, copy.begin(), copy.end()); + auto iqr = q3 - q1; + auto los = q1 - (iqr * 3.); + auto lom = q1 - (iqr * 1.5); + auto him = q3 + (iqr * 1.5); + auto his = q3 + (iqr * 3.); + + OutlierClassification o; + for (; first != last; ++first) { + auto&& t = *first; + if (t < los) ++o.low_severe; + else if (t < lom) ++o.low_mild; + else if (t > his) ++o.high_severe; + else if (t > him) ++o.high_mild; + ++o.samples_seen; + } + return o; } - // Bind a boolean data member (no placeholder required) - template<typename C> - void bind( bool C::* field ) { - m_arg->boundField = new Detail::BoundDataMember<C,bool>( field ); + + template <typename Iterator> + double mean(Iterator first, Iterator last) { + auto count = last - first; + double sum = std::accumulate(first, last, 0.); + return sum / count; } - // Bind a method taking a single, non-boolean argument (requires a placeholder string) - template<typename C, typename M> - void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { - m_arg->boundField = new Detail::BoundUnaryMethod<C,M>( unaryMethod ); - m_arg->placeholder = placeholder; + template <typename URng, typename Iterator, typename Estimator> + sample resample(URng& rng, int resamples, Iterator first, Iterator last, Estimator& estimator) { + auto n = last - first; + std::uniform_int_distribution<decltype(n)> dist(0, n - 1); + + sample out; + out.reserve(resamples); + std::generate_n(std::back_inserter(out), resamples, [n, first, &estimator, &dist, &rng] { + std::vector<double> resampled; + resampled.reserve(n); + std::generate_n(std::back_inserter(resampled), n, [first, &dist, &rng] { return first[dist(rng)]; }); + return estimator(resampled.begin(), resampled.end()); + }); + std::sort(out.begin(), out.end()); + return out; } - // Bind a method taking a single, boolean argument (no placeholder string required) - template<typename C> - void bind( void (C::* unaryMethod)( bool ) ) { - m_arg->boundField = new Detail::BoundUnaryMethod<C,bool>( unaryMethod ); + template <typename Estimator, typename Iterator> + sample jackknife(Estimator&& estimator, Iterator first, Iterator last) { + auto n = last - first; + auto second = std::next(first); + sample results; + results.reserve(n); + + for (auto it = first; it != last; ++it) { + std::iter_swap(it, first); + results.push_back(estimator(second, last)); + } + + return results; } - // Bind a method that takes no arguments (will be called if opt is present) - template<typename C> - void bind( void (C::* nullaryMethod)() ) { - m_arg->boundField = new Detail::BoundNullaryMethod<C>( nullaryMethod ); + inline double normal_cdf(double x) { + return std::erfc(-x / std::sqrt(2.0)) / 2.0; } - // Bind a free function taking a single argument - the object to operate on (no placeholder string required) - template<typename C> - void bind( void (* unaryFunction)( C& ) ) { - m_arg->boundField = new Detail::BoundUnaryFunction<C>( unaryFunction ); + double erfc_inv(double x); + + double normal_quantile(double p); + + template <typename Iterator, typename Estimator> + Estimate<double> bootstrap(double confidence_level, Iterator first, Iterator last, sample const& resample, Estimator&& estimator) { + auto n_samples = last - first; + + double point = estimator(first, last); + // Degenerate case with a single sample + if (n_samples == 1) return { point, point, point, confidence_level }; + + sample jack = jackknife(estimator, first, last); + double jack_mean = mean(jack.begin(), jack.end()); + double sum_squares, sum_cubes; + std::tie(sum_squares, sum_cubes) = std::accumulate(jack.begin(), jack.end(), std::make_pair(0., 0.), [jack_mean](std::pair<double, double> sqcb, double x) -> std::pair<double, double> { + auto d = jack_mean - x; + auto d2 = d * d; + auto d3 = d2 * d; + return { sqcb.first + d2, sqcb.second + d3 }; + }); + + double accel = sum_cubes / (6 * std::pow(sum_squares, 1.5)); + int n = static_cast<int>(resample.size()); + double prob_n = std::count_if(resample.begin(), resample.end(), [point](double x) { return x < point; }) / (double)n; + // degenerate case with uniform samples + if (prob_n == 0) return { point, point, point, confidence_level }; + + double bias = normal_quantile(prob_n); + double z1 = normal_quantile((1. - confidence_level) / 2.); + + auto cumn = [n](double x) -> int { + return std::lround(normal_cdf(x) * n); }; + auto a = [bias, accel](double b) { return bias + b / (1. - accel * b); }; + double b1 = bias + z1; + double b2 = bias - z1; + double a1 = a(b1); + double a2 = a(b2); + auto lo = (std::max)(cumn(a1), 0); + auto hi = (std::min)(cumn(a2), n - 1); + + return { point, resample[lo], resample[hi], confidence_level }; } - // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) - template<typename C, typename T> - void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { - m_arg->boundField = new Detail::BoundBinaryFunction<C, T>( binaryFunction ); - m_arg->placeholder = placeholder; + double outlier_variance(Estimate<double> mean, Estimate<double> stddev, int n); + + struct bootstrap_analysis { + Estimate<double> mean; + Estimate<double> standard_deviation; + double outlier_variance; + }; + + bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector<double>::iterator first, std::vector<double>::iterator last); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// end catch_stats.hpp +#include <algorithm> +#include <iterator> +#include <tuple> +#include <vector> +#include <cmath> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Clock> + std::vector<double> resolution(int k) { + std::vector<TimePoint<Clock>> times; + times.reserve(k + 1); + std::generate_n(std::back_inserter(times), k + 1, now<Clock>{}); + + std::vector<double> deltas; + deltas.reserve(k); + std::transform(std::next(times.begin()), times.end(), times.begin(), + std::back_inserter(deltas), + [](TimePoint<Clock> a, TimePoint<Clock> b) { return static_cast<double>((a - b).count()); }); + + return deltas; } - ArgBuilder& describe( std::string const& description ) { - m_arg->description = description; - return *this; + const auto warmup_iterations = 10000; + const auto warmup_time = std::chrono::milliseconds(100); + const auto minimum_ticks = 1000; + const auto warmup_seed = 10000; + const auto clock_resolution_estimation_time = std::chrono::milliseconds(500); + const auto clock_cost_estimation_time_limit = std::chrono::seconds(1); + const auto clock_cost_estimation_tick_limit = 100000; + const auto clock_cost_estimation_time = std::chrono::milliseconds(10); + const auto clock_cost_estimation_iterations = 10000; + + template <typename Clock> + int warmup() { + return run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_seed, &resolution<Clock>) + .iterations; } - ArgBuilder& detail( std::string const& detail ) { - m_arg->detail = detail; - return *this; + template <typename Clock> + EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_resolution(int iterations) { + auto r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_resolution_estimation_time), iterations, &resolution<Clock>) + .result; + return { + FloatDuration<Clock>(mean(r.begin(), r.end())), + classify_outliers(r.begin(), r.end()), + }; + } + template <typename Clock> + EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_cost(FloatDuration<Clock> resolution) { + auto time_limit = (std::min)( + resolution * clock_cost_estimation_tick_limit, + FloatDuration<Clock>(clock_cost_estimation_time_limit)); + auto time_clock = [](int k) { + return Detail::measure<Clock>([k] { + for (int i = 0; i < k; ++i) { + volatile auto ignored = Clock::now(); + (void)ignored; + } + }).elapsed; + }; + time_clock(1); + int iters = clock_cost_estimation_iterations; + auto&& r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_cost_estimation_time), iters, time_clock); + std::vector<double> times; + int nsamples = static_cast<int>(std::ceil(time_limit / r.elapsed)); + times.reserve(nsamples); + std::generate_n(std::back_inserter(times), nsamples, [time_clock, &r] { + return static_cast<double>((time_clock(r.iterations) / r.iterations).count()); + }); + return { + FloatDuration<Clock>(mean(times.begin(), times.end())), + classify_outliers(times.begin(), times.end()), + }; } - protected: - Arg* m_arg; - }; + template <typename Clock> + Environment<FloatDuration<Clock>> measure_environment() { + static Environment<FloatDuration<Clock>>* env = nullptr; + if (env) { + return *env; + } - class OptBuilder : public ArgBuilder { - public: - OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} - OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + auto iters = Detail::warmup<Clock>(); + auto resolution = Detail::estimate_clock_resolution<Clock>(iters); + auto cost = Detail::estimate_clock_cost<Clock>(resolution.mean); - OptBuilder& operator[]( std::string const& optName ) { - addOptName( *ArgBuilder::m_arg, optName ); - return *this; + env = new Environment<FloatDuration<Clock>>{ resolution, cost }; + return *env; } - }; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch - public: +// end catch_estimate_clock.hpp +// start catch_analyse.hpp - CommandLine() - : m_boundProcessName( new Detail::NullBinder<ConfigT>() ), - m_highestSpecifiedArgPosition( 0 ), - m_throwOnUnrecognisedTokens( false ) - {} - CommandLine( CommandLine const& other ) - : m_boundProcessName( other.m_boundProcessName ), - m_options ( other.m_options ), - m_positionalArgs( other.m_positionalArgs ), - m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), - m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) - { - if( other.m_floatingArg.get() ) - m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); - } + // Run and analyse one benchmark - CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { - m_throwOnUnrecognisedTokens = shouldThrow; - return *this; - } - OptBuilder operator[]( std::string const& optName ) { - m_options.push_back( Arg() ); - addOptName( m_options.back(), optName ); - OptBuilder builder( &m_options.back() ); - return builder; - } +// start catch_sample_analysis.hpp - ArgBuilder operator[]( int position ) { - m_positionalArgs.insert( std::make_pair( position, Arg() ) ); - if( position > m_highestSpecifiedArgPosition ) - m_highestSpecifiedArgPosition = position; - setPositionalArg( m_positionalArgs[position], position ); - ArgBuilder builder( &m_positionalArgs[position] ); - return builder; - } +// Benchmark results - // Invoke this with the _ instance - ArgBuilder operator[]( UnpositionalTag ) { - if( m_floatingArg.get() ) - throw std::logic_error( "Only one unpositional argument can be added" ); - m_floatingArg.reset( new Arg() ); - ArgBuilder builder( m_floatingArg.get() ); - return builder; - } - template<typename C, typename M> - void bindProcessName( M C::* field ) { - m_boundProcessName = new Detail::BoundDataMember<C,M>( field ); - } - template<typename C, typename M> - void bindProcessName( void (C::*_unaryMethod)( M ) ) { - m_boundProcessName = new Detail::BoundUnaryMethod<C,M>( _unaryMethod ); - } - - void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { - typename std::vector<Arg>::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; - std::size_t maxWidth = 0; - for( it = itBegin; it != itEnd; ++it ) - maxWidth = (std::max)( maxWidth, it->commands().size() ); +#include <algorithm> +#include <vector> +#include <string> +#include <iterator> - for( it = itBegin; it != itEnd; ++it ) { - Detail::Text usage( it->commands(), Detail::TextAttributes() - .setWidth( maxWidth+indent ) - .setIndent( indent ) ); - Detail::Text desc( it->description, Detail::TextAttributes() - .setWidth( width - maxWidth - 3 ) ); +namespace Catch { + namespace Benchmark { + template <typename Duration> + struct SampleAnalysis { + std::vector<Duration> samples; + Estimate<Duration> mean; + Estimate<Duration> standard_deviation; + OutlierClassification outliers; + double outlier_variance; + + template <typename Duration2> + operator SampleAnalysis<Duration2>() const { + std::vector<Duration2> samples2; + samples2.reserve(samples.size()); + std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](Duration d) { return Duration2(d); }); + return { + std::move(samples2), + mean, + standard_deviation, + outliers, + outlier_variance, + }; + } + }; + } // namespace Benchmark +} // namespace Catch - for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { - std::string usageCol = i < usage.size() ? usage[i] : ""; - os << usageCol; +// end catch_sample_analysis.hpp +#include <algorithm> +#include <iterator> +#include <vector> - if( i < desc.size() && !desc[i].empty() ) - os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) - << desc[i]; - os << "\n"; +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Duration, typename Iterator> + SampleAnalysis<Duration> analyse(const IConfig &cfg, Environment<Duration>, Iterator first, Iterator last) { + if (!cfg.benchmarkNoAnalysis()) { + std::vector<double> samples; + samples.reserve(last - first); + std::transform(first, last, std::back_inserter(samples), [](Duration d) { return d.count(); }); + + auto analysis = Catch::Benchmark::Detail::analyse_samples(cfg.benchmarkConfidenceInterval(), cfg.benchmarkResamples(), samples.begin(), samples.end()); + auto outliers = Catch::Benchmark::Detail::classify_outliers(samples.begin(), samples.end()); + + auto wrap_estimate = [](Estimate<double> e) { + return Estimate<Duration> { + Duration(e.point), + Duration(e.lower_bound), + Duration(e.upper_bound), + e.confidence_interval, + }; + }; + std::vector<Duration> samples2; + samples2.reserve(samples.size()); + std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](double d) { return Duration(d); }); + return { + std::move(samples2), + wrap_estimate(analysis.mean), + wrap_estimate(analysis.standard_deviation), + outliers, + analysis.outlier_variance, + }; + } else { + std::vector<Duration> samples; + samples.reserve(last - first); + + Duration mean = Duration(0); + int i = 0; + for (auto it = first; it < last; ++it, ++i) { + samples.push_back(Duration(*it)); + mean += Duration(*it); + } + mean /= i; + + return { + std::move(samples), + Estimate<Duration>{mean, mean, mean, 0.0}, + Estimate<Duration>{Duration(0), Duration(0), Duration(0), 0.0}, + OutlierClassification{}, + 0.0 + }; } } - } - std::string optUsage() const { - std::ostringstream oss; - optUsage( oss ); - return oss.str(); - } - - void argSynopsis( std::ostream& os ) const { - for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { - if( i > 1 ) - os << " "; - typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( i ); - if( it != m_positionalArgs.end() ) - os << "<" << it->second.placeholder << ">"; - else if( m_floatingArg.get() ) - os << "<" << m_floatingArg->placeholder << ">"; - else - throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +// end catch_analyse.hpp +#include <algorithm> +#include <functional> +#include <string> +#include <vector> +#include <cmath> + +namespace Catch { + namespace Benchmark { + struct Benchmark { + Benchmark(std::string &&name) + : name(std::move(name)) {} + + template <class FUN> + Benchmark(std::string &&name, FUN &&func) + : fun(std::move(func)), name(std::move(name)) {} + + template <typename Clock> + ExecutionPlan<FloatDuration<Clock>> prepare(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const { + auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; + auto run_time = std::max(min_time, std::chrono::duration_cast<decltype(min_time)>(cfg.benchmarkWarmupTime())); + auto&& test = Detail::run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(run_time), 1, fun); + int new_iters = static_cast<int>(std::ceil(min_time * test.iterations / test.elapsed)); + return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), fun, std::chrono::duration_cast<FloatDuration<Clock>>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations }; } - // !TBD No indication of mandatory args - if( m_floatingArg.get() ) { - if( m_highestSpecifiedArgPosition > 1 ) - os << " "; - os << "[<" << m_floatingArg->placeholder << "> ...]"; + + template <typename Clock = default_clock> + void run() { + IConfigPtr cfg = getCurrentContext().getConfig(); + + auto env = Detail::measure_environment<Clock>(); + + getResultCapture().benchmarkPreparing(name); + CATCH_TRY{ + auto plan = user_code([&] { + return prepare<Clock>(*cfg, env); + }); + + BenchmarkInfo info { + name, + plan.estimated_duration.count(), + plan.iterations_per_sample, + cfg->benchmarkSamples(), + cfg->benchmarkResamples(), + env.clock_resolution.mean.count(), + env.clock_cost.mean.count() + }; + + getResultCapture().benchmarkStarting(info); + + auto samples = user_code([&] { + return plan.template run<Clock>(*cfg, env); + }); + + auto analysis = Detail::analyse(*cfg, env, samples.begin(), samples.end()); + BenchmarkStats<FloatDuration<Clock>> stats{ info, analysis.samples, analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; + getResultCapture().benchmarkEnded(stats); + + } CATCH_CATCH_ALL{ + if (translateActiveException() != Detail::benchmarkErrorMsg) // benchmark errors have been reported, otherwise rethrow. + std::rethrow_exception(std::current_exception()); + } } - } - std::string argSynopsis() const { - std::ostringstream oss; - argSynopsis( oss ); - return oss.str(); - } - void usage( std::ostream& os, std::string const& procName ) const { - validate(); - os << "usage:\n " << procName << " "; - argSynopsis( os ); - if( !m_options.empty() ) { - os << " [options]\n\nwhere options are: \n"; - optUsage( os, 2 ); + // sets lambda to be used in fun *and* executes benchmark! + template <typename Fun, + typename std::enable_if<!Detail::is_related<Fun, Benchmark>::value, int>::type = 0> + Benchmark & operator=(Fun func) { + fun = Detail::BenchmarkFunction(func); + run(); + return *this; } - os << "\n"; - } - std::string usage( std::string const& procName ) const { - std::ostringstream oss; - usage( oss, procName ); - return oss.str(); - } - - ConfigT parse( std::vector<std::string> const& args ) const { - ConfigT config; - parseInto( args, config ); - return config; - } - - std::vector<Parser::Token> parseInto( std::vector<std::string> const& args, ConfigT& config ) const { - std::string processName = args.empty() ? std::string() : args[0]; - std::size_t lastSlash = processName.find_last_of( "/\\" ); - if( lastSlash != std::string::npos ) - processName = processName.substr( lastSlash+1 ); - m_boundProcessName.set( config, processName ); - std::vector<Parser::Token> tokens; - Parser parser; - parser.parseIntoTokens( args, tokens ); - return populate( tokens, config ); - } - - std::vector<Parser::Token> populate( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { - validate(); - std::vector<Parser::Token> unusedTokens = populateOptions( tokens, config ); - unusedTokens = populateFixedArgs( unusedTokens, config ); - unusedTokens = populateFloatingArgs( unusedTokens, config ); - return unusedTokens; - } - - std::vector<Parser::Token> populateOptions( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { - std::vector<Parser::Token> unusedTokens; - std::vector<std::string> errors; - for( std::size_t i = 0; i < tokens.size(); ++i ) { - Parser::Token const& token = tokens[i]; - typename std::vector<Arg>::const_iterator it = m_options.begin(), itEnd = m_options.end(); - for(; it != itEnd; ++it ) { - Arg const& arg = *it; - - try { - if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || - ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { - if( arg.takesArg() ) { - if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) - errors.push_back( "Expected argument to option: " + token.data ); - else - arg.boundField.set( config, tokens[++i].data ); - } - else { - arg.boundField.set( config, "true" ); - } - break; - } - } - catch( std::exception& ex ) { - errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); - } + + explicit operator bool() { + return true; + } + + private: + Detail::BenchmarkFunction fun; + std::string name; + }; + } +} // namespace Catch + +#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1 +#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2 + +#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&](int benchmarkIndex) + +#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&] + +// end catch_benchmark.hpp +// start catch_constructor.hpp + +// Constructor and destructor helpers + + +#include <type_traits> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename T, bool Destruct> + struct ObjectStorage + { + using TStorage = typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type; + + ObjectStorage() : data() {} + + ObjectStorage(const ObjectStorage& other) + { + new(&data) T(other.stored_object()); } - if( it == itEnd ) { - if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) - unusedTokens.push_back( token ); - else if( errors.empty() && m_throwOnUnrecognisedTokens ) - errors.push_back( "unrecognised option: " + token.data ); + + ObjectStorage(ObjectStorage&& other) + { + new(&data) T(std::move(other.stored_object())); } - } - if( !errors.empty() ) { - std::ostringstream oss; - for( std::vector<std::string>::const_iterator it = errors.begin(), itEnd = errors.end(); - it != itEnd; - ++it ) { - if( it != errors.begin() ) - oss << "\n"; - oss << *it; + + ~ObjectStorage() { destruct_on_exit<T>(); } + + template <typename... Args> + void construct(Args&&... args) + { + new (&data) T(std::forward<Args>(args)...); } - throw std::runtime_error( oss.str() ); - } - return unusedTokens; - } - std::vector<Parser::Token> populateFixedArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { - std::vector<Parser::Token> unusedTokens; - int position = 1; - for( std::size_t i = 0; i < tokens.size(); ++i ) { - Parser::Token const& token = tokens[i]; - typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( position ); - if( it != m_positionalArgs.end() ) - it->second.boundField.set( config, token.data ); - else - unusedTokens.push_back( token ); - if( token.type == Parser::Token::Positional ) - position++; - } - return unusedTokens; - } - std::vector<Parser::Token> populateFloatingArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { - if( !m_floatingArg.get() ) - return tokens; - std::vector<Parser::Token> unusedTokens; - for( std::size_t i = 0; i < tokens.size(); ++i ) { - Parser::Token const& token = tokens[i]; - if( token.type == Parser::Token::Positional ) - m_floatingArg->boundField.set( config, token.data ); - else - unusedTokens.push_back( token ); - } - return unusedTokens; - } - void validate() const - { - if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) - throw std::logic_error( "No options or arguments specified" ); + template <bool AllowManualDestruction = !Destruct> + typename std::enable_if<AllowManualDestruction>::type destruct() + { + stored_object().~T(); + } - for( typename std::vector<Arg>::const_iterator it = m_options.begin(), - itEnd = m_options.end(); - it != itEnd; ++it ) - it->validate(); + private: + // If this is a constructor benchmark, destruct the underlying object + template <typename U> + void destruct_on_exit(typename std::enable_if<Destruct, U>::type* = 0) { destruct<true>(); } + // Otherwise, don't + template <typename U> + void destruct_on_exit(typename std::enable_if<!Destruct, U>::type* = 0) { } + + T& stored_object() { + return *static_cast<T*>(static_cast<void*>(&data)); + } + + T const& stored_object() const { + return *static_cast<T*>(static_cast<void*>(&data)); + } + + TStorage data; + }; } - private: - Detail::BoundArgFunction<ConfigT> m_boundProcessName; - std::vector<Arg> m_options; - std::map<int, Arg> m_positionalArgs; - ArgAutoPtr m_floatingArg; - int m_highestSpecifiedArgPosition; - bool m_throwOnUnrecognisedTokens; - }; + template <typename T> + using storage_for = Detail::ObjectStorage<T, true>; -} // end namespace Clara + template <typename T> + using destructable_object = Detail::ObjectStorage<T, false>; + } +} -STITCH_CLARA_CLOSE_NAMESPACE -#undef STITCH_CLARA_OPEN_NAMESPACE -#undef STITCH_CLARA_CLOSE_NAMESPACE +// end catch_constructor.hpp +// end catch_benchmarking_all.hpp +#endif -#endif // TWOBLUECUBES_CLARA_H_INCLUDED -#undef STITCH_CLARA_OPEN_NAMESPACE +#endif // ! CATCH_CONFIG_IMPL_ONLY -// Restore Clara's value for console width, if present -#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#ifdef CATCH_IMPL +// start catch_impl.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" #endif -#include <fstream> -#include <ctime> +// Keep these here for external reporters +// start catch_test_case_tracker.h + +#include <string> +#include <vector> +#include <memory> namespace Catch { +namespace TestCaseTracking { - inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } - inline void abortAfterX( ConfigData& config, int x ) { - if( x < 1 ) - throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); - config.abortAfter = x; - } - inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } - inline void addSectionToRun( ConfigData& config, std::string const& sectionName ) { config.sectionsToRun.push_back( sectionName ); } - inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } + struct NameAndLocation { + std::string name; + SourceLineInfo location; - inline void addWarning( ConfigData& config, std::string const& _warning ) { - if( _warning == "NoAssertions" ) - config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions ); - else - throw std::runtime_error( "Unrecognised warning: '" + _warning + '\'' ); - } - inline void setOrder( ConfigData& config, std::string const& order ) { - if( startsWith( "declared", order ) ) - config.runOrder = RunTests::InDeclarationOrder; - else if( startsWith( "lexical", order ) ) - config.runOrder = RunTests::InLexicographicalOrder; - else if( startsWith( "random", order ) ) - config.runOrder = RunTests::InRandomOrder; - else - throw std::runtime_error( "Unrecognised ordering: '" + order + '\'' ); - } - inline void setRngSeed( ConfigData& config, std::string const& seed ) { - if( seed == "time" ) { - config.rngSeed = static_cast<unsigned int>( std::time(0) ); + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); + friend bool operator==(NameAndLocation const& lhs, NameAndLocation const& rhs) { + return lhs.name == rhs.name + && lhs.location == rhs.location; } - else { - std::stringstream ss; - ss << seed; - ss >> config.rngSeed; - if( ss.fail() ) - throw std::runtime_error( "Argument to --rng-seed should be the word 'time' or a number" ); - } - } - inline void setVerbosity( ConfigData& config, int level ) { - // !TBD: accept strings? - config.verbosity = static_cast<Verbosity::Level>( level ); - } - inline void setShowDurations( ConfigData& config, bool _showDurations ) { - config.showDurations = _showDurations - ? ShowDurations::Always - : ShowDurations::Never; - } - inline void setUseColour( ConfigData& config, std::string const& value ) { - std::string mode = toLower( value ); - - if( mode == "yes" ) - config.useColour = UseColour::Yes; - else if( mode == "no" ) - config.useColour = UseColour::No; - else if( mode == "auto" ) - config.useColour = UseColour::Auto; - else - throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); - } - inline void setWaitForKeypress( ConfigData& config, std::string const& keypress ) { - std::string keypressLc = toLower( keypress ); - if( keypressLc == "start" ) - config.waitForKeypress = WaitForKeypress::BeforeStart; - else if( keypressLc == "exit" ) - config.waitForKeypress = WaitForKeypress::BeforeExit; - else if( keypressLc == "both" ) - config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; - else - throw std::runtime_error( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); }; - inline void forceColour( ConfigData& config ) { - config.useColour = UseColour::Yes; - } - inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { - std::ifstream f( _filename.c_str() ); - if( !f.is_open() ) - throw std::domain_error( "Unable to load input file: " + _filename ); + class ITracker; - std::string line; - while( std::getline( f, line ) ) { - line = trim(line); - if( !line.empty() && !startsWith( line, '#' ) ) { - if( !startsWith( line, '"' ) ) - line = '"' + line + '"'; - addTestOrTags( config, line + ',' ); - } + using ITrackerPtr = std::shared_ptr<ITracker>; + + class ITracker { + NameAndLocation m_nameAndLocation; + + public: + ITracker(NameAndLocation const& nameAndLoc) : + m_nameAndLocation(nameAndLoc) + {} + + // static queries + NameAndLocation const& nameAndLocation() const { + return m_nameAndLocation; } - } - inline Clara::CommandLine<ConfigData> makeCommandLineParser() { + virtual ~ITracker(); + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + virtual bool hasStarted() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( ITrackerPtr const& child ) = 0; + virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isGeneratorTracker() const = 0; + }; + + class TrackerContext { - using namespace Clara; - CommandLine<ConfigData> cli; + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; - cli.bindProcessName( &ConfigData::processName ); + ITrackerPtr m_rootTracker; + ITracker* m_currentTracker = nullptr; + RunState m_runState = NotStarted; - cli["-?"]["-h"]["--help"] - .describe( "display usage information" ) - .bind( &ConfigData::showHelp ); + public: - cli["-l"]["--list-tests"] - .describe( "list all/matching test cases" ) - .bind( &ConfigData::listTests ); + ITracker& startRun(); + void endRun(); - cli["-t"]["--list-tags"] - .describe( "list all/matching tags" ) - .bind( &ConfigData::listTags ); + void startCycle(); + void completeCycle(); - cli["-s"]["--success"] - .describe( "include successful tests in output" ) - .bind( &ConfigData::showSuccessfulTests ); + bool completedCycle() const; + ITracker& currentTracker(); + void setCurrentTracker( ITracker* tracker ); + }; - cli["-b"]["--break"] - .describe( "break into debugger on failure" ) - .bind( &ConfigData::shouldDebugBreak ); + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; - cli["-e"]["--nothrow"] - .describe( "skip exception tests" ) - .bind( &ConfigData::noThrow ); + using Children = std::vector<ITrackerPtr>; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState = NotStarted; - cli["-i"]["--invisibles"] - .describe( "show invisibles (tabs, newlines)" ) - .bind( &ConfigData::showInvisibles ); + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - cli["-o"]["--out"] - .describe( "output filename" ) - .bind( &ConfigData::outputFilename, "filename" ); + bool isComplete() const override; + bool isSuccessfullyCompleted() const override; + bool isOpen() const override; + bool hasChildren() const override; + bool hasStarted() const override { + return m_runState != NotStarted; + } - cli["-r"]["--reporter"] -// .placeholder( "name[:filename]" ) - .describe( "reporter to use (defaults to console)" ) - .bind( &addReporterName, "name" ); + void addChild( ITrackerPtr const& child ) override; - cli["-n"]["--name"] - .describe( "suite name" ) - .bind( &ConfigData::name, "name" ); + ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override; + ITracker& parent() override; - cli["-a"]["--abort"] - .describe( "abort at first failure" ) - .bind( &abortAfterFirst ); + void openChild() override; - cli["-x"]["--abortx"] - .describe( "abort after x failures" ) - .bind( &abortAfterX, "no. failures" ); + bool isSectionTracker() const override; + bool isGeneratorTracker() const override; - cli["-w"]["--warn"] - .describe( "enable warnings" ) - .bind( &addWarning, "warning name" ); + void open(); -// - needs updating if reinstated -// cli.into( &setVerbosity ) -// .describe( "level of verbosity (0=no output)" ) -// .shortOpt( "v") -// .longOpt( "verbosity" ) -// .placeholder( "level" ); + void close() override; + void fail() override; + void markAsNeedingAnotherRun() override; - cli[_] - .describe( "which test or tests to use" ) - .bind( &addTestOrTags, "test name, pattern or tags" ); + private: + void moveToParent(); + void moveToThis(); + }; - cli["-d"]["--durations"] - .describe( "show test durations" ) - .bind( &setShowDurations, "yes|no" ); + class SectionTracker : public TrackerBase { + std::vector<std::string> m_filters; + std::string m_trimmed_name; + public: + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - cli["-f"]["--input-file"] - .describe( "load test names to run from a file" ) - .bind( &loadTestNamesFromFile, "filename" ); + bool isSectionTracker() const override; - cli["-#"]["--filenames-as-tags"] - .describe( "adds a tag for the filename" ) - .bind( &ConfigData::filenamesAsTags ); + bool isComplete() const override; - cli["-c"]["--section"] - .describe( "specify section to run" ) - .bind( &addSectionToRun, "section name" ); + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); - // Less common commands which don't have a short form - cli["--list-test-names-only"] - .describe( "list all/matching test cases names only" ) - .bind( &ConfigData::listTestNamesOnly ); + void tryOpen(); - cli["--list-extra-info"] - .describe( "list all/matching test cases with more info" ) - .bind( &ConfigData::listExtraInfo ); + void addInitialFilters( std::vector<std::string> const& filters ); + void addNextFilters( std::vector<std::string> const& filters ); + //! Returns filters active in this tracker + std::vector<std::string> const& getFilters() const; + //! Returns whitespace-trimmed name of the tracked section + std::string const& trimmedName() const; + }; - cli["--list-reporters"] - .describe( "list all reporters" ) - .bind( &ConfigData::listReporters ); +} // namespace TestCaseTracking - cli["--order"] - .describe( "test case order (defaults to decl)" ) - .bind( &setOrder, "decl|lex|rand" ); +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; - cli["--rng-seed"] - .describe( "set a specific seed for random numbers" ) - .bind( &setRngSeed, "'time'|number" ); - - cli["--force-colour"] - .describe( "force colourised output (deprecated)" ) - .bind( &forceColour ); - - cli["--use-colour"] - .describe( "should output be colourised" ) - .bind( &setUseColour, "yes|no" ); - - cli["--use-colour"] - .describe( "should output be colourised" ) - .bind( &setUseColour, "yes|no" ); - - cli["--libidentify"] - .describe( "report name and version according to libidentify standard" ) - .bind( &ConfigData::libIdentify ); - - cli["--wait-for-keypress"] - .describe( "waits for a keypress before exiting" ) - .bind( &setWaitForKeypress, "start|exit|both" ); +} // namespace Catch - return cli; - } +// end catch_test_case_tracker.h -} // end namespace Catch +// start catch_leak_detector.h -// #included from: internal/catch_list.hpp -#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED +namespace Catch { -// #included from: catch_text.h -#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + struct LeakDetector { + LeakDetector(); + ~LeakDetector(); + }; -#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH +} +// end catch_leak_detector.h +// Cpp files will be included in the single-header file here +// start catch_stats.cpp -#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch -// #included from: ../external/tbc_text_format.h -// Only use header guard if we are not using an outer namespace -#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE -# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED -# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED -# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED -# endif -# else -# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED -# endif -#endif -#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED -#include <string> -#include <vector> -#include <sstream> +// Statistical analysis tools -// Use optional outer namespace -#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE -namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { -#endif +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) -namespace Tbc { +#include <cassert> +#include <random> -#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH - const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; -#else - const unsigned int consoleWidth = 80; +#if defined(CATCH_CONFIG_USE_ASYNC) +#include <future> #endif - struct TextAttributes { - TextAttributes() - : initialIndent( std::string::npos ), - indent( 0 ), - width( consoleWidth-1 ) - {} +namespace { + double erf_inv(double x) { + // Code accompanying the article "Approximating the erfinv function" in GPU Computing Gems, Volume 2 + double w, p; + + w = -log((1.0 - x) * (1.0 + x)); + + if (w < 6.250000) { + w = w - 3.125000; + p = -3.6444120640178196996e-21; + p = -1.685059138182016589e-19 + p * w; + p = 1.2858480715256400167e-18 + p * w; + p = 1.115787767802518096e-17 + p * w; + p = -1.333171662854620906e-16 + p * w; + p = 2.0972767875968561637e-17 + p * w; + p = 6.6376381343583238325e-15 + p * w; + p = -4.0545662729752068639e-14 + p * w; + p = -8.1519341976054721522e-14 + p * w; + p = 2.6335093153082322977e-12 + p * w; + p = -1.2975133253453532498e-11 + p * w; + p = -5.4154120542946279317e-11 + p * w; + p = 1.051212273321532285e-09 + p * w; + p = -4.1126339803469836976e-09 + p * w; + p = -2.9070369957882005086e-08 + p * w; + p = 4.2347877827932403518e-07 + p * w; + p = -1.3654692000834678645e-06 + p * w; + p = -1.3882523362786468719e-05 + p * w; + p = 0.0001867342080340571352 + p * w; + p = -0.00074070253416626697512 + p * w; + p = -0.0060336708714301490533 + p * w; + p = 0.24015818242558961693 + p * w; + p = 1.6536545626831027356 + p * w; + } else if (w < 16.000000) { + w = sqrt(w) - 3.250000; + p = 2.2137376921775787049e-09; + p = 9.0756561938885390979e-08 + p * w; + p = -2.7517406297064545428e-07 + p * w; + p = 1.8239629214389227755e-08 + p * w; + p = 1.5027403968909827627e-06 + p * w; + p = -4.013867526981545969e-06 + p * w; + p = 2.9234449089955446044e-06 + p * w; + p = 1.2475304481671778723e-05 + p * w; + p = -4.7318229009055733981e-05 + p * w; + p = 6.8284851459573175448e-05 + p * w; + p = 2.4031110387097893999e-05 + p * w; + p = -0.0003550375203628474796 + p * w; + p = 0.00095328937973738049703 + p * w; + p = -0.0016882755560235047313 + p * w; + p = 0.0024914420961078508066 + p * w; + p = -0.0037512085075692412107 + p * w; + p = 0.005370914553590063617 + p * w; + p = 1.0052589676941592334 + p * w; + p = 3.0838856104922207635 + p * w; + } else { + w = sqrt(w) - 5.000000; + p = -2.7109920616438573243e-11; + p = -2.5556418169965252055e-10 + p * w; + p = 1.5076572693500548083e-09 + p * w; + p = -3.7894654401267369937e-09 + p * w; + p = 7.6157012080783393804e-09 + p * w; + p = -1.4960026627149240478e-08 + p * w; + p = 2.9147953450901080826e-08 + p * w; + p = -6.7711997758452339498e-08 + p * w; + p = 2.2900482228026654717e-07 + p * w; + p = -9.9298272942317002539e-07 + p * w; + p = 4.5260625972231537039e-06 + p * w; + p = -1.9681778105531670567e-05 + p * w; + p = 7.5995277030017761139e-05 + p * w; + p = -0.00021503011930044477347 + p * w; + p = -0.00013871931833623122026 + p * w; + p = 1.0103004648645343977 + p * w; + p = 4.8499064014085844221 + p * w; + } + return p * x; + } - TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } - TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } - TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + double standard_deviation(std::vector<double>::iterator first, std::vector<double>::iterator last) { + auto m = Catch::Benchmark::Detail::mean(first, last); + double variance = std::accumulate(first, last, 0., [m](double a, double b) { + double diff = b - m; + return a + diff * diff; + }) / (last - first); + return std::sqrt(variance); + } - std::size_t initialIndent; // indent of first line, or npos - std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos - std::size_t width; // maximum width of text, including indent. Longer text will wrap - }; +} - class Text { - public: - Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) - : attr( _attr ) - { - const std::string wrappableBeforeChars = "[({<\t"; - const std::string wrappableAfterChars = "])}>-,./|\\"; - const std::string wrappableInsteadOfChars = " \n\r"; - std::string indent = _attr.initialIndent != std::string::npos - ? std::string( _attr.initialIndent, ' ' ) - : std::string( _attr.indent, ' ' ); - - typedef std::string::const_iterator iterator; - iterator it = _str.begin(); - const iterator strEnd = _str.end(); - - while( it != strEnd ) { - - if( lines.size() >= 1000 ) { - lines.push_back( "... message truncated due to excessive size" ); - return; - } +namespace Catch { + namespace Benchmark { + namespace Detail { + + double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, std::vector<double>::iterator last) { + auto count = last - first; + double idx = (count - 1) * k / static_cast<double>(q); + int j = static_cast<int>(idx); + double g = idx - j; + std::nth_element(first, first + j, last); + auto xj = first[j]; + if (g == 0) return xj; + + auto xj1 = *std::min_element(first + (j + 1), last); + return xj + g * (xj1 - xj); + } - std::string suffix; - std::size_t width = (std::min)( static_cast<size_t>( strEnd-it ), _attr.width-static_cast<size_t>( indent.size() ) ); - iterator itEnd = it+width; - iterator itNext = _str.end(); - - iterator itNewLine = std::find( it, itEnd, '\n' ); - if( itNewLine != itEnd ) - itEnd = itNewLine; - - if( itEnd != strEnd ) { - bool foundWrapPoint = false; - iterator findIt = itEnd; - do { - if( wrappableAfterChars.find( *findIt ) != std::string::npos && findIt != itEnd ) { - itEnd = findIt+1; - itNext = findIt+1; - foundWrapPoint = true; - } - else if( findIt > it && wrappableBeforeChars.find( *findIt ) != std::string::npos ) { - itEnd = findIt; - itNext = findIt; - foundWrapPoint = true; - } - else if( wrappableInsteadOfChars.find( *findIt ) != std::string::npos ) { - itNext = findIt+1; - itEnd = findIt; - foundWrapPoint = true; - } - if( findIt == it ) - break; - else - --findIt; - } - while( !foundWrapPoint ); + double erfc_inv(double x) { + return erf_inv(1.0 - x); + } - if( !foundWrapPoint ) { - // No good wrap char, so we'll break mid word and add a hyphen - --itEnd; - itNext = itEnd; - suffix = "-"; - } - else { - while( itEnd > it && wrappableInsteadOfChars.find( *(itEnd-1) ) != std::string::npos ) - --itEnd; - } + double normal_quantile(double p) { + static const double ROOT_TWO = std::sqrt(2.0); + + double result = 0.0; + assert(p >= 0 && p <= 1); + if (p < 0 || p > 1) { + return result; } - lines.push_back( indent + std::string( it, itEnd ) + suffix ); - if( indent.size() != _attr.indent ) - indent = std::string( _attr.indent, ' ' ); - it = itNext; + result = -erfc_inv(2.0 * p); + // result *= normal distribution standard deviation (1.0) * sqrt(2) + result *= /*sd * */ ROOT_TWO; + // result += normal disttribution mean (0) + return result; } - } - typedef std::vector<std::string>::const_iterator const_iterator; + double outlier_variance(Estimate<double> mean, Estimate<double> stddev, int n) { + double sb = stddev.point; + double mn = mean.point / n; + double mg_min = mn / 2.; + double sg = (std::min)(mg_min / 4., sb / std::sqrt(n)); + double sg2 = sg * sg; + double sb2 = sb * sb; + + auto c_max = [n, mn, sb2, sg2](double x) -> double { + double k = mn - x; + double d = k * k; + double nd = n * d; + double k0 = -n * nd; + double k1 = sb2 - n * sg2 + nd; + double det = k1 * k1 - 4 * sg2 * k0; + return (int)(-2. * k0 / (k1 + std::sqrt(det))); + }; + + auto var_out = [n, sb2, sg2](double c) { + double nc = n - c; + return (nc / n) * (sb2 - nc * sg2); + }; + + return (std::min)(var_out(1), var_out((std::min)(c_max(0.), c_max(mg_min)))) / sb2; + } - const_iterator begin() const { return lines.begin(); } - const_iterator end() const { return lines.end(); } - std::string const& last() const { return lines.back(); } - std::size_t size() const { return lines.size(); } - std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } - std::string toString() const { - std::ostringstream oss; - oss << *this; - return oss.str(); - } + bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector<double>::iterator first, std::vector<double>::iterator last) { + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + static std::random_device entropy; + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { - for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); - it != itEnd; ++it ) { - if( it != _text.begin() ) - _stream << "\n"; - _stream << *it; - } - return _stream; - } + auto n = static_cast<int>(last - first); // seriously, one can't use integral types without hell in C++ - private: - std::string str; - TextAttributes attr; - std::vector<std::string> lines; - }; + auto mean = &Detail::mean<std::vector<double>::iterator>; + auto stddev = &standard_deviation; -} // end namespace Tbc +#if defined(CATCH_CONFIG_USE_ASYNC) + auto Estimate = [=](double(*f)(std::vector<double>::iterator, std::vector<double>::iterator)) { + auto seed = entropy(); + return std::async(std::launch::async, [=] { + std::mt19937 rng(seed); + auto resampled = resample(rng, n_resamples, first, last, f); + return bootstrap(confidence_level, first, last, resampled, f); + }); + }; -#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE -} // end outer namespace -#endif + auto mean_future = Estimate(mean); + auto stddev_future = Estimate(stddev); + + auto mean_estimate = mean_future.get(); + auto stddev_estimate = stddev_future.get(); +#else + auto Estimate = [=](double(*f)(std::vector<double>::iterator, std::vector<double>::iterator)) { + auto seed = entropy(); + std::mt19937 rng(seed); + auto resampled = resample(rng, n_resamples, first, last, f); + return bootstrap(confidence_level, first, last, resampled, f); + }; -#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED -#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + auto mean_estimate = Estimate(mean); + auto stddev_estimate = Estimate(stddev); +#endif // CATCH_USE_ASYNC -namespace Catch { - using Tbc::Text; - using Tbc::TextAttributes; + double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n); + + return { mean_estimate, stddev_estimate, outlier_variance }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING +// end catch_stats.cpp +// start catch_approx.cpp + +#include <cmath> +#include <limits> + +namespace { + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); } -// #included from: catch_console_colour.hpp -#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED +} namespace Catch { +namespace Detail { - struct Colour { - enum Code { - None = 0, + Approx::Approx ( double value ) + : m_epsilon( std::numeric_limits<float>::epsilon()*100 ), + m_margin( 0.0 ), + m_scale( 0.0 ), + m_value( value ) + {} - White, - Red, - Green, - Blue, - Cyan, - Yellow, - Grey, + Approx Approx::custom() { + return Approx( 0 ); + } - Bright = 0x10, + Approx Approx::operator-() const { + auto temp(*this); + temp.m_value = -temp.m_value; + return temp; + } - BrightRed = Bright | Red, - BrightGreen = Bright | Green, - LightGrey = Bright | Grey, - BrightWhite = Bright | White, + std::string Approx::toString() const { + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; + return rss.str(); + } - // By intention - FileName = LightGrey, - Warning = Yellow, - ResultError = BrightRed, - ResultSuccess = BrightGreen, - ResultExpectedFailure = Warning, + bool Approx::equalityComparisonImpl(const double other) const { + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) + || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value)? 0 : m_value))); + } - Error = BrightRed, - Success = Green, + void Approx::setMargin(double newMargin) { + CATCH_ENFORCE(newMargin >= 0, + "Invalid Approx::margin: " << newMargin << '.' + << " Approx::Margin has to be non-negative."); + m_margin = newMargin; + } - OriginalExpression = Cyan, - ReconstructedExpression = Yellow, + void Approx::setEpsilon(double newEpsilon) { + CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0, + "Invalid Approx::epsilon: " << newEpsilon << '.' + << " Approx::epsilon has to be in [0, 1]"); + m_epsilon = newEpsilon; + } - SecondaryText = LightGrey, - Headers = White - }; +} // end namespace Detail - // Use constructed object for RAII guard - Colour( Code _colourCode ); - Colour( Colour const& other ); - ~Colour(); +namespace literals { + Detail::Approx operator "" _a(long double val) { + return Detail::Approx(val); + } + Detail::Approx operator "" _a(unsigned long long val) { + return Detail::Approx(val); + } +} // end namespace literals - // Use static method for one-shot changes - static void use( Code _colourCode ); +std::string StringMaker<Catch::Detail::Approx>::convert(Catch::Detail::Approx const& value) { + return value.toString(); +} - private: - bool m_moved; - }; +} // end namespace Catch +// end catch_approx.cpp +// start catch_assertionhandler.cpp - inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } +// start catch_debugger.h -} // end namespace Catch +namespace Catch { + bool isDebuggerActive(); +} -// #included from: catch_interfaces_reporter.h -#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED +#ifdef CATCH_PLATFORM_MAC -#include <string> -#include <ostream> -#include <map> + #if defined(__i386__) || defined(__x86_64__) + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + #elif defined(__aarch64__) + #define CATCH_TRAP() __asm__(".inst 0xd4200000") + #endif -namespace Catch -{ - struct ReporterConfig { - explicit ReporterConfig( Ptr<IConfig const> const& _fullConfig ) - : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} +#elif defined(CATCH_PLATFORM_IPHONE) + + // use inline assembler + #if defined(__i386__) || defined(__x86_64__) + #define CATCH_TRAP() __asm__("int $3") + #elif defined(__aarch64__) + #define CATCH_TRAP() __asm__(".inst 0xd4200000") + #elif defined(__arm__) && !defined(__thumb__) + #define CATCH_TRAP() __asm__(".inst 0xe7f001f0") + #elif defined(__arm__) && defined(__thumb__) + #define CATCH_TRAP() __asm__(".inst 0xde01") + #endif - ReporterConfig( Ptr<IConfig const> const& _fullConfig, std::ostream& _stream ) - : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} +#elif defined(CATCH_PLATFORM_LINUX) + // 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. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ + #else // Fall back to the generic way. + #include <signal.h> - std::ostream& stream() const { return *m_stream; } - Ptr<IConfig const> fullConfig() const { return m_fullConfig; } + #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 - private: - std::ostream* m_stream; - Ptr<IConfig const> m_fullConfig; - }; +#ifndef CATCH_BREAK_INTO_DEBUGGER + #ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }() + #else + #define CATCH_BREAK_INTO_DEBUGGER() []{}() + #endif +#endif - struct ReporterPreferences { - ReporterPreferences() - : shouldRedirectStdOut( false ) - {} +// end catch_debugger.h +// start catch_run_context.h - bool shouldRedirectStdOut; - }; +// start catch_fatal_condition.h - template<typename T> - struct LazyStat : Option<T> { - LazyStat() : used( false ) {} - LazyStat& operator=( T const& _value ) { - Option<T>::operator=( _value ); - used = false; - return *this; +#include <cassert> + +namespace Catch { + + // Wrapper for platform-specific fatal error (signals/SEH) handlers + // + // Tries to be cooperative with other handlers, and not step over + // other handlers. This means that unknown structured exceptions + // are passed on, previous signal handlers are called, and so on. + // + // Can only be instantiated once, and assumes that once a signal + // is caught, the binary will end up terminating. Thus, there + class FatalConditionHandler { + bool m_started = false; + + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + void engage_platform(); + void disengage_platform(); + public: + // Should also have platform-specific implementations as needed + FatalConditionHandler(); + ~FatalConditionHandler(); + + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); } - void reset() { - Option<T>::reset(); - used = false; + + void disengage() { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); } - bool used; }; - struct TestRunInfo { - TestRunInfo( std::string const& _name ) : name( _name ) {} - std::string name; + //! Simple RAII guard for (dis)engaging the FatalConditionHandler + class FatalConditionHandlerGuard { + FatalConditionHandler* m_handler; + public: + FatalConditionHandlerGuard(FatalConditionHandler* handler): + m_handler(handler) { + m_handler->engage(); + } + ~FatalConditionHandlerGuard() { + m_handler->disengage(); + } }; - struct GroupInfo { - GroupInfo( std::string const& _name, - std::size_t _groupIndex, - std::size_t _groupsCount ) - : name( _name ), - groupIndex( _groupIndex ), - groupsCounts( _groupsCount ) - {} - std::string name; - std::size_t groupIndex; - std::size_t groupsCounts; - }; +} // end namespace Catch - struct AssertionStats { - AssertionStats( AssertionResult const& _assertionResult, - std::vector<MessageInfo> const& _infoMessages, - Totals const& _totals ) - : assertionResult( _assertionResult ), - infoMessages( _infoMessages ), - totals( _totals ) - { - if( assertionResult.hasMessage() ) { - // Copy message into messages list. - // !TBD This should have been done earlier, somewhere - MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); - builder << assertionResult.getMessage(); - builder.m_info.message = builder.m_stream.str(); +// end catch_fatal_condition.h +#include <string> - infoMessages.push_back( builder.m_info ); - } - } - virtual ~AssertionStats(); +namespace Catch { -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - AssertionStats( AssertionStats const& ) = default; - AssertionStats( AssertionStats && ) = default; - AssertionStats& operator = ( AssertionStats const& ) = default; - AssertionStats& operator = ( AssertionStats && ) = default; -# endif + struct IMutableContext; - AssertionResult assertionResult; - std::vector<MessageInfo> infoMessages; - Totals totals; - }; + /////////////////////////////////////////////////////////////////////////// - struct SectionStats { - SectionStats( SectionInfo const& _sectionInfo, - Counts const& _assertions, - double _durationInSeconds, - bool _missingAssertions ) - : sectionInfo( _sectionInfo ), - assertions( _assertions ), - durationInSeconds( _durationInSeconds ), - missingAssertions( _missingAssertions ) - {} - virtual ~SectionStats(); -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - SectionStats( SectionStats const& ) = default; - SectionStats( SectionStats && ) = default; - SectionStats& operator = ( SectionStats const& ) = default; - SectionStats& operator = ( SectionStats && ) = default; -# endif + class RunContext : public IResultCapture, public IRunner { - SectionInfo sectionInfo; - Counts assertions; - double durationInSeconds; - bool missingAssertions; - }; + public: + RunContext( RunContext const& ) = delete; + RunContext& operator =( RunContext const& ) = delete; - struct TestCaseStats { - TestCaseStats( TestCaseInfo const& _testInfo, - Totals const& _totals, - std::string const& _stdOut, - std::string const& _stdErr, - bool _aborting ) - : testInfo( _testInfo ), - totals( _totals ), - stdOut( _stdOut ), - stdErr( _stdErr ), - aborting( _aborting ) - {} - virtual ~TestCaseStats(); + explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter ); -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - TestCaseStats( TestCaseStats const& ) = default; - TestCaseStats( TestCaseStats && ) = default; - TestCaseStats& operator = ( TestCaseStats const& ) = default; - TestCaseStats& operator = ( TestCaseStats && ) = default; -# endif + ~RunContext() override; - TestCaseInfo testInfo; - Totals totals; - std::string stdOut; - std::string stdErr; - bool aborting; - }; + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ); + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ); - struct TestGroupStats { - TestGroupStats( GroupInfo const& _groupInfo, - Totals const& _totals, - bool _aborting ) - : groupInfo( _groupInfo ), - totals( _totals ), - aborting( _aborting ) - {} - TestGroupStats( GroupInfo const& _groupInfo ) - : groupInfo( _groupInfo ), - aborting( false ) - {} - virtual ~TestGroupStats(); + Totals runTest(TestCase const& testCase); -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - TestGroupStats( TestGroupStats const& ) = default; - TestGroupStats( TestGroupStats && ) = default; - TestGroupStats& operator = ( TestGroupStats const& ) = default; - TestGroupStats& operator = ( TestGroupStats && ) = default; -# endif + IConfigPtr config() const; + IStreamingReporter& reporter() const; - GroupInfo groupInfo; - Totals totals; - bool aborting; - }; + public: // IResultCapture - struct TestRunStats { - TestRunStats( TestRunInfo const& _runInfo, - Totals const& _totals, - bool _aborting ) - : runInfo( _runInfo ), - totals( _totals ), - aborting( _aborting ) - {} - virtual ~TestRunStats(); + // Assertion handlers + void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) override; + void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction ) override; + void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) override; + void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction ) override; + void handleIncomplete + ( AssertionInfo const& info ) override; + void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) override; -# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS - TestRunStats( TestRunStats const& _other ) - : runInfo( _other.runInfo ), - totals( _other.totals ), - aborting( _other.aborting ) - {} -# else - TestRunStats( TestRunStats const& ) = default; - TestRunStats( TestRunStats && ) = default; - TestRunStats& operator = ( TestRunStats const& ) = default; - TestRunStats& operator = ( TestRunStats && ) = default; -# endif + bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; - TestRunInfo runInfo; - Totals totals; - bool aborting; - }; + void sectionEnded( SectionEndInfo const& endInfo ) override; + void sectionEndedEarly( SectionEndInfo const& endInfo ) override; - class MultipleReporters; + auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; - struct IStreamingReporter : IShared { - virtual ~IStreamingReporter(); +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void benchmarkPreparing( std::string const& name ) override; + void benchmarkStarting( BenchmarkInfo const& info ) override; + void benchmarkEnded( BenchmarkStats<> const& stats ) override; + void benchmarkFailed( std::string const& error ) override; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - // Implementing class must also provide the following static method: - // static std::string getDescription(); + void pushScopedMessage( MessageInfo const& message ) override; + void popScopedMessage( MessageInfo const& message ) override; - virtual ReporterPreferences getPreferences() const = 0; + void emplaceUnscopedMessage( MessageBuilder const& builder ) override; - virtual void noMatchingTestCases( std::string const& spec ) = 0; + std::string getCurrentTestName() const override; - virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; - virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + const AssertionResult* getLastResult() const override; - virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; - virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + void exceptionEarlyReported() override; - virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + void handleFatalErrorCondition( StringRef message ) override; - // The return value indicates if the messages buffer should be cleared: - virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + bool lastAssertionPassed() override; - virtual void sectionEnded( SectionStats const& sectionStats ) = 0; - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; - virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + void assertionPassed() override; - virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + public: + // !TBD We need to do this another way! + bool aborting() const final; - virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; } - }; + private: - struct IReporterFactory : IShared { - virtual ~IReporterFactory(); - virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; - virtual std::string getDescription() const = 0; - }; + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ); + void invokeActiveTestCase(); - struct IReporterRegistry { - typedef std::map<std::string, Ptr<IReporterFactory> > FactoryMap; - typedef std::vector<Ptr<IReporterFactory> > Listeners; + void resetAssertionInfo(); + bool testForMissingAssertions( Counts& assertions ); - virtual ~IReporterRegistry(); - virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig const> const& config ) const = 0; - virtual FactoryMap const& getFactories() const = 0; - virtual Listeners const& getListeners() const = 0; - }; + void assertionEnded( AssertionResult const& result ); + void reportExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ); - Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingReporter, Ptr<IStreamingReporter> const& additionalReporter ); + void populateReaction( AssertionReaction& reaction ); -} + private: -#include <limits> -#include <algorithm> + void handleUnfinishedSections(); + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase = nullptr; + ITracker* m_testCaseTracker = nullptr; + Option<AssertionResult> m_lastResult; + + IConfigPtr m_config; + Totals m_totals; + IStreamingReporterPtr m_reporter; + std::vector<MessageInfo> m_messages; + std::vector<ScopedMessage> m_messageScopes; /* Keeps owners of so-called unscoped messages. */ + AssertionInfo m_lastAssertionInfo; + std::vector<SectionEndInfo> m_unfinishedSections; + std::vector<ITracker*> m_activeSections; + TrackerContext m_trackerContext; + FatalConditionHandler m_fatalConditionhandler; + bool m_lastAssertionPassed = false; + bool m_shouldReportUnexpected = true; + bool m_includeSuccessfulResults; + }; + + void seedRng(IConfig const& config); + unsigned int rngSeed(); +} // end namespace Catch + +// end catch_run_context.h namespace Catch { - inline std::size_t listTests( Config const& config ) { + namespace { + auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { + expr.streamReconstructedExpression( os ); + return os; + } + } - TestSpec testSpec = config.testSpec(); - if( config.testSpec().hasFilters() ) - Catch::cout() << "Matching test cases:\n"; + LazyExpression::LazyExpression( bool isNegated ) + : m_isNegated( isNegated ) + {} + + LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {} + + LazyExpression::operator bool() const { + return m_transientExpression != nullptr; + } + + auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& { + if( lazyExpr.m_isNegated ) + os << "!"; + + if( lazyExpr ) { + if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() ) + os << "(" << *lazyExpr.m_transientExpression << ")"; + else + os << *lazyExpr.m_transientExpression; + } else { - Catch::cout() << "All available test cases:\n"; - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + os << "{** error - unchecked empty expression requested **}"; } + return os; + } - std::size_t matchedTests = 0; - TextAttributes nameAttr, descAttr, tagsAttr; - nameAttr.setInitialIndent( 2 ).setIndent( 4 ); - descAttr.setIndent( 4 ); - tagsAttr.setIndent( 6 ); + AssertionHandler::AssertionHandler + ( StringRef const& macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, + m_resultCapture( getResultCapture() ) + {} - std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); - for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); - it != itEnd; - ++it ) { - matchedTests++; - TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); - Colour::Code colour = testCaseInfo.isHidden() - ? Colour::SecondaryText - : Colour::None; - Colour colourGuard( colour ); + void AssertionHandler::handleExpr( ITransientExpression const& expr ) { + m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); + } + void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) { + m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction ); + } - Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; - if( config.listExtraInfo() ) { - Catch::cout() << " " << testCaseInfo.lineInfo << std::endl; - std::string description = testCaseInfo.description; - if( description.empty() ) - description = "(NO DESCRIPTION)"; - Catch::cout() << Text( description, descAttr ) << std::endl; - } - if( !testCaseInfo.tags.empty() ) - Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + auto AssertionHandler::allowThrows() const -> bool { + return getCurrentContext().getConfig()->allowThrows(); + } + + void AssertionHandler::complete() { + setCompleted(); + if( m_reaction.shouldDebugBreak ) { + + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) + CATCH_BREAK_INTO_DEBUGGER(); } + if (m_reaction.shouldThrow) { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + throw Catch::TestFailureException(); +#else + CATCH_ERROR( "Test failure requires aborting test!" ); +#endif + } + } + void AssertionHandler::setCompleted() { + m_completed = true; + } - if( !config.testSpec().hasFilters() ) - Catch::cout() << pluralise( matchedTests, "test case" ) << '\n' << std::endl; - else - Catch::cout() << pluralise( matchedTests, "matching test case" ) << '\n' << std::endl; - return matchedTests; + void AssertionHandler::handleUnexpectedInflightException() { + m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); } - inline std::size_t listTestsNamesOnly( Config const& config ) { - TestSpec testSpec = config.testSpec(); - if( !config.testSpec().hasFilters() ) - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); - std::size_t matchedTests = 0; - std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); - for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); - it != itEnd; - ++it ) { - matchedTests++; - TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); - if( startsWith( testCaseInfo.name, '#' ) ) - Catch::cout() << '"' << testCaseInfo.name << '"'; - else - Catch::cout() << testCaseInfo.name; - if ( config.listExtraInfo() ) - Catch::cout() << "\t@" << testCaseInfo.lineInfo; - Catch::cout() << std::endl; - } - return matchedTests; + void AssertionHandler::handleExceptionThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + void AssertionHandler::handleExceptionNotThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); } - struct TagInfo { - TagInfo() : count ( 0 ) {} - void add( std::string const& spelling ) { - ++count; - spellings.insert( spelling ); - } - std::string all() const { - std::string out; - for( std::set<std::string>::const_iterator it = spellings.begin(), itEnd = spellings.end(); - it != itEnd; - ++it ) - out += "[" + *it + "]"; - return out; - } - std::set<std::string> spellings; - std::size_t count; - }; + void AssertionHandler::handleUnexpectedExceptionNotThrown() { + m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); + } - inline std::size_t listTags( Config const& config ) { - TestSpec testSpec = config.testSpec(); - if( config.testSpec().hasFilters() ) - Catch::cout() << "Tags for matching test cases:\n"; - else { - Catch::cout() << "All available tags:\n"; - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); - } + void AssertionHandler::handleThrowingCallSkipped() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } - std::map<std::string, TagInfo> tagCounts; + // This is the overload that takes a string and infers the Equals matcher from it + // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString ) { + handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); + } - std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); - for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); - it != itEnd; - ++it ) { - for( std::set<std::string>::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), - tagItEnd = it->getTestCaseInfo().tags.end(); - tagIt != tagItEnd; - ++tagIt ) { - std::string tagName = *tagIt; - std::string lcaseTagName = toLower( tagName ); - std::map<std::string, TagInfo>::iterator countIt = tagCounts.find( lcaseTagName ); - if( countIt == tagCounts.end() ) - countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; - countIt->second.add( tagName ); +} // namespace Catch +// end catch_assertionhandler.cpp +// start catch_assertionresult.cpp + +namespace Catch { + AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression): + lazyExpression(_lazyExpression), + resultType(_resultType) {} + + std::string AssertionResultData::reconstructExpression() const { + + if( reconstructedExpression.empty() ) { + if( lazyExpression ) { + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); } } + return reconstructedExpression; + } - for( std::map<std::string, TagInfo>::const_iterator countIt = tagCounts.begin(), - countItEnd = tagCounts.end(); - countIt != countItEnd; - ++countIt ) { - std::ostringstream oss; - oss << " " << std::setw(2) << countIt->second.count << " "; - Text wrapper( countIt->second.all(), TextAttributes() - .setInitialIndent( 0 ) - .setIndent( oss.str().size() ) - .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); - Catch::cout() << oss.str() << wrapper << '\n'; + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + // Possibly overallocating by 3 characters should be basically free + std::string expr; expr.reserve(m_info.capturedExpression.size() + 3); + if (isFalseTest(m_info.resultDisposition)) { + expr += "!("; } - Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; - return tagCounts.size(); + expr += m_info.capturedExpression; + if (isFalseTest(m_info.resultDisposition)) { + expr += ')'; + } + return expr; } - inline std::size_t listReporters( Config const& /*config*/ ) { - Catch::cout() << "Available reporters:\n"; - IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); - IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; - std::size_t maxNameLen = 0; - for(it = itBegin; it != itEnd; ++it ) - maxNameLen = (std::max)( maxNameLen, it->first.size() ); - - for(it = itBegin; it != itEnd; ++it ) { - Text wrapper( it->second->getDescription(), TextAttributes() - .setInitialIndent( 0 ) - .setIndent( 7+maxNameLen ) - .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); - Catch::cout() << " " - << it->first - << ':' - << std::string( maxNameLen - it->first.size() + 2, ' ' ) - << wrapper << '\n'; + std::string AssertionResult::getExpressionInMacro() const { + std::string expr; + if( m_info.macroName.empty() ) + expr = static_cast<std::string>(m_info.capturedExpression); + else { + expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; } - Catch::cout() << std::endl; - return factories.size(); + return expr; } - inline Option<std::size_t> list( Config const& config ) { - Option<std::size_t> listedCount; - if( config.listTests() || ( config.listExtraInfo() && !config.listTestNamesOnly() ) ) - listedCount = listedCount.valueOr(0) + listTests( config ); - if( config.listTestNamesOnly() ) - listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); - if( config.listTags() ) - listedCount = listedCount.valueOr(0) + listTags( config ); - if( config.listReporters() ) - listedCount = listedCount.valueOr(0) + listReporters( config ); - return listedCount; + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + StringRef AssertionResult::getTestMacroName() const { + return m_info.macroName; } } // end namespace Catch +// end catch_assertionresult.cpp +// start catch_capture_matchers.cpp -// #included from: internal/catch_run_context.hpp -#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED +namespace Catch { -// #included from: catch_test_case_tracker.hpp -#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + using StringMatcher = Matchers::Impl::MatcherBase<std::string>; -#include <algorithm> -#include <string> -#include <assert.h> + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr<std::string, StringMatcher const&> expr( exceptionMessage, matcher, matcherString ); + handler.handleExpr( expr ); + } + +} // namespace Catch +// end catch_capture_matchers.cpp +// start catch_commandline.cpp + +// start catch_commandline.h + +// start catch_clara.h + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#endif +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1 + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wshadow" +#endif + +// start clara.hpp +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See https://github.com/philsquared/Clara for more details + +// Clara v1.1.5 + + +#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 +#endif + +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +#ifdef __has_include +#if __has_include(<optional>) && __cplusplus >= 201703L +#include <optional> +#define CLARA_CONFIG_OPTIONAL_TYPE std::optional +#endif +#endif +#endif + +// ----------- #included from clara_textflow.hpp ----------- + +// TextFlowCpp +// +// A single-header library for wrapping and laying out basic text, by Phil Nash +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// This project is hosted at https://github.com/philsquared/textflowcpp + + +#include <cassert> +#include <ostream> +#include <sstream> #include <vector> -#include <stdexcept> -CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 +#endif namespace Catch { -namespace TestCaseTracking { +namespace clara { +namespace TextFlow { - struct NameAndLocation { - std::string name; - SourceLineInfo location; +inline auto isWhitespace(char c) -> bool { + static std::string chars = " \t\n\r"; + return chars.find(c) != std::string::npos; +} +inline auto isBreakableBefore(char c) -> bool { + static std::string chars = "[({<|"; + return chars.find(c) != std::string::npos; +} +inline auto isBreakableAfter(char c) -> bool { + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find(c) != std::string::npos; +} - NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) - : name( _name ), - location( _location ) - {} - }; +class Columns; - struct ITracker : SharedImpl<> { - virtual ~ITracker(); +class Column { + std::vector<std::string> m_strings; + size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; - // static queries - virtual NameAndLocation const& nameAndLocation() const = 0; +public: + class iterator { + friend Column; + + Column const& m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; + + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; + + iterator(Column const& column, size_t stringIndex) + : m_column(column), + m_stringIndex(stringIndex) {} + + auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } + + auto isBoundary(size_t at) const -> bool { + assert(at > 0); + assert(at <= line().size()); + + return at == line().size() || + (isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) || + isBreakableBefore(line()[at]) || + isBreakableAfter(line()[at - 1]); + } + + void calcLength() { + assert(m_stringIndex < m_column.m_strings.size()); + + m_suffix = false; + auto width = m_column.m_width - indent(); + m_end = m_pos; + if (line()[m_pos] == '\n') { + ++m_end; + } + while (m_end < line().size() && line()[m_end] != '\n') + ++m_end; + + if (m_end < m_pos + width) { + m_len = m_end - m_pos; + } else { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + --len; + while (len > 0 && isWhitespace(line()[m_pos + len - 1])) + --len; + + if (len > 0) { + m_len = len; + } else { + m_suffix = true; + m_len = width - 1; + } + } + } + + auto indent() const -> size_t { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + auto addIndentAndSuffix(std::string const &plain) const -> std::string { + return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain); + } + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type * ; + using reference = value_type & ; + using iterator_category = std::forward_iterator_tag; + + explicit iterator(Column const& column) : m_column(column) { + assert(m_column.m_width > m_column.m_indent); + assert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent); + calcLength(); + if (m_len == 0) + m_stringIndex++; // Empty string + } + + auto operator *() const -> std::string { + assert(m_stringIndex < m_column.m_strings.size()); + assert(m_pos <= m_end); + return addIndentAndSuffix(line().substr(m_pos, m_len)); + } + + auto operator ++() -> iterator& { + m_pos += m_len; + if (m_pos < line().size() && line()[m_pos] == '\n') + m_pos += 1; + else + while (m_pos < line().size() && isWhitespace(line()[m_pos])) + ++m_pos; + + if (m_pos == line().size()) { + m_pos = 0; + ++m_stringIndex; + } + if (m_stringIndex < m_column.m_strings.size()) + calcLength(); + return *this; + } + auto operator ++(int) -> iterator { + iterator prev(*this); + operator++(); + return prev; + } + + auto operator ==(iterator const& other) const -> bool { + return + m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; + } + auto operator !=(iterator const& other) const -> bool { + return !operator==(other); + } + }; + using const_iterator = iterator; + + explicit Column(std::string const& text) { m_strings.push_back(text); } + + auto width(size_t newWidth) -> Column& { + assert(newWidth > 0); + m_width = newWidth; + return *this; + } + auto indent(size_t newIndent) -> Column& { + m_indent = newIndent; + return *this; + } + auto initialIndent(size_t newIndent) -> Column& { + m_initialIndent = newIndent; + return *this; + } + + auto width() const -> size_t { return m_width; } + auto begin() const -> iterator { return iterator(*this); } + auto end() const -> iterator { return { *this, m_strings.size() }; } + + inline friend std::ostream& operator << (std::ostream& os, Column const& col) { + bool first = true; + for (auto line : col) { + if (first) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto operator + (Column const& other)->Columns; + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } +}; - // dynamic queries - virtual bool isComplete() const = 0; // Successfully completed or failed - virtual bool isSuccessfullyCompleted() const = 0; - virtual bool isOpen() const = 0; // Started but not complete - virtual bool hasChildren() const = 0; +class Spacer : public Column { - virtual ITracker& parent() = 0; +public: + explicit Spacer(size_t spaceWidth) : Column("") { + width(spaceWidth); + } +}; - // actions - virtual void close() = 0; // Successfully complete - virtual void fail() = 0; - virtual void markAsNeedingAnotherRun() = 0; +class Columns { + std::vector<Column> m_columns; - virtual void addChild( Ptr<ITracker> const& child ) = 0; - virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) = 0; - virtual void openChild() = 0; +public: - // Debug/ checking - virtual bool isSectionTracker() const = 0; - virtual bool isIndexTracker() const = 0; + class iterator { + friend Columns; + struct EndTag {}; + + std::vector<Column> const& m_columns; + std::vector<Column::iterator> m_iterators; + size_t m_activeIterators; + + iterator(Columns const& columns, EndTag) + : m_columns(columns.m_columns), + m_activeIterators(0) { + m_iterators.reserve(m_columns.size()); + + for (auto const& col : m_columns) + m_iterators.push_back(col.end()); + } + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type * ; + using reference = value_type & ; + using iterator_category = std::forward_iterator_tag; + + explicit iterator(Columns const& columns) + : m_columns(columns.m_columns), + m_activeIterators(m_columns.size()) { + m_iterators.reserve(m_columns.size()); + + for (auto const& col : m_columns) + m_iterators.push_back(col.begin()); + } + + auto operator ==(iterator const& other) const -> bool { + return m_iterators == other.m_iterators; + } + auto operator !=(iterator const& other) const -> bool { + return m_iterators != other.m_iterators; + } + auto operator *() const -> std::string { + std::string row, padding; + + for (size_t i = 0; i < m_columns.size(); ++i) { + auto width = m_columns[i].width(); + if (m_iterators[i] != m_columns[i].end()) { + std::string col = *m_iterators[i]; + row += padding + col; + if (col.size() < width) + padding = std::string(width - col.size(), ' '); + else + padding = ""; + } else { + padding += std::string(width, ' '); + } + } + return row; + } + auto operator ++() -> iterator& { + for (size_t i = 0; i < m_columns.size(); ++i) { + if (m_iterators[i] != m_columns[i].end()) + ++m_iterators[i]; + } + return *this; + } + auto operator ++(int) -> iterator { + iterator prev(*this); + operator++(); + return prev; + } + }; + using const_iterator = iterator; + + auto begin() const -> iterator { return iterator(*this); } + auto end() const -> iterator { return { *this, iterator::EndTag() }; } + + auto operator += (Column const& col) -> Columns& { + m_columns.push_back(col); + return *this; + } + auto operator + (Column const& col) -> Columns { + Columns combined = *this; + combined += col; + return combined; + } + + inline friend std::ostream& operator << (std::ostream& os, Columns const& cols) { + + bool first = true; + for (auto line : cols) { + if (first) + first = false; + else + os << "\n"; + os << line; + } + return os; + } + + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); + } +}; + +inline auto Column::operator + (Column const& other) -> Columns { + Columns cols; + cols += *this; + cols += other; + return cols; +} +} + +} +} + +// ----------- end of #include from clara_textflow.hpp ----------- +// ........... back in clara.hpp + +#include <cctype> +#include <string> +#include <memory> +#include <set> +#include <algorithm> + +#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) +#define CATCH_PLATFORM_WINDOWS +#endif + +namespace Catch { namespace clara { +namespace detail { + + // Traits for extracting arg and return type of lambdas (for single argument lambdas) + template<typename L> + struct UnaryLambdaTraits : UnaryLambdaTraits<decltype( &L::operator() )> {}; + + template<typename ClassT, typename ReturnT, typename... Args> + struct UnaryLambdaTraits<ReturnT( ClassT::* )( Args... ) const> { + static const bool isValid = false; }; - class TrackerContext { + template<typename ClassT, typename ReturnT, typename ArgT> + struct UnaryLambdaTraits<ReturnT( ClassT::* )( ArgT ) const> { + static const bool isValid = true; + using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::type>::type; + using ReturnType = ReturnT; + }; - enum RunState { - NotStarted, - Executing, - CompletedCycle - }; + class TokenStream; - Ptr<ITracker> m_rootTracker; - ITracker* m_currentTracker; - RunState m_runState; + // Transport for raw args (copied from main args, or supplied via init list for testing) + class Args { + friend TokenStream; + std::string m_exeName; + std::vector<std::string> m_args; public: + Args( int argc, char const* const* argv ) + : m_exeName(argv[0]), + m_args(argv + 1, argv + argc) {} - static TrackerContext& instance() { - static TrackerContext s_instance; - return s_instance; + Args( std::initializer_list<std::string> args ) + : m_exeName( *args.begin() ), + m_args( args.begin()+1, args.end() ) + {} + + auto exeName() const -> std::string { + return m_exeName; } + }; - TrackerContext() - : m_currentTracker( CATCH_NULL ), - m_runState( NotStarted ) - {} + // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string + // may encode an option + its argument if the : or = form is used + enum class TokenType { + Option, Argument + }; + struct Token { + TokenType type; + std::string token; + }; - ITracker& startRun(); + inline auto isOptPrefix( char c ) -> bool { + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } - void endRun() { - m_rootTracker.reset(); - m_currentTracker = CATCH_NULL; - m_runState = NotStarted; + // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled + class TokenStream { + using Iterator = std::vector<std::string>::const_iterator; + Iterator it; + Iterator itEnd; + std::vector<Token> m_tokenBuffer; + + void loadBuffer() { + m_tokenBuffer.resize( 0 ); + + // Skip any empty strings + while( it != itEnd && it->empty() ) + ++it; + + if( it != itEnd ) { + auto const &next = *it; + if( isOptPrefix( next[0] ) ) { + auto delimiterPos = next.find_first_of( " :=" ); + if( delimiterPos != std::string::npos ) { + m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); + m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); + } else { + if( next[1] != '-' && next.size() > 2 ) { + std::string opt = "- "; + for( size_t i = 1; i < next.size(); ++i ) { + opt[1] = next[i]; + m_tokenBuffer.push_back( { TokenType::Option, opt } ); + } + } else { + m_tokenBuffer.push_back( { TokenType::Option, next } ); + } + } + } else { + m_tokenBuffer.push_back( { TokenType::Argument, next } ); + } + } } - void startCycle() { - m_currentTracker = m_rootTracker.get(); - m_runState = Executing; + public: + explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} + + TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { + loadBuffer(); } - void completeCycle() { - m_runState = CompletedCycle; + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; } - bool completedCycle() const { - return m_runState == CompletedCycle; + auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + + auto operator*() const -> Token { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); } - ITracker& currentTracker() { - return *m_currentTracker; + + auto operator->() const -> Token const * { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); } - void setCurrentTracker( ITracker* tracker ) { - m_currentTracker = tracker; + + auto operator++() -> TokenStream & { + if( m_tokenBuffer.size() >= 2 ) { + m_tokenBuffer.erase( m_tokenBuffer.begin() ); + } else { + if( it != itEnd ) + ++it; + loadBuffer(); + } + return *this; } }; - class TrackerBase : public ITracker { - protected: - enum CycleState { - NotStarted, - Executing, - ExecutingChildren, - NeedsAnotherRun, - CompletedSuccessfully, - Failed + class ResultBase { + public: + enum Type { + Ok, LogicError, RuntimeError }; - class TrackerHasName { - NameAndLocation m_nameAndLocation; - public: - TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} - bool operator ()( Ptr<ITracker> const& tracker ) { - return - tracker->nameAndLocation().name == m_nameAndLocation.name && - tracker->nameAndLocation().location == m_nameAndLocation.location; - } + + protected: + ResultBase( Type type ) : m_type( type ) {} + virtual ~ResultBase() = default; + + virtual void enforceOk() const = 0; + + Type m_type; + }; + + template<typename T> + class ResultValueBase : public ResultBase { + public: + auto value() const -> T const & { + enforceOk(); + return m_value; + } + + protected: + ResultValueBase( Type type ) : ResultBase( type ) {} + + ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + } + + ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { + new( &m_value ) T( value ); + } + + auto operator=( ResultValueBase const &other ) -> ResultValueBase & { + if( m_type == ResultBase::Ok ) + m_value.~T(); + ResultBase::operator=(other); + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + return *this; + } + + ~ResultValueBase() override { + if( m_type == Ok ) + m_value.~T(); + } + + union { + T m_value; }; - typedef std::vector<Ptr<ITracker> > Children; - NameAndLocation m_nameAndLocation; - TrackerContext& m_ctx; - ITracker* m_parent; - Children m_children; - CycleState m_runState; + }; + + template<> + class ResultValueBase<void> : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template<typename T = void> + class BasicResult : public ResultValueBase<T> { public: - TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : m_nameAndLocation( nameAndLocation ), - m_ctx( ctx ), - m_parent( parent ), - m_runState( NotStarted ) - {} - virtual ~TrackerBase(); + template<typename U> + explicit BasicResult( BasicResult<U> const &other ) + : ResultValueBase<T>( other.type() ), + m_errorMessage( other.errorMessage() ) + { + assert( type() != ResultBase::Ok ); + } - virtual NameAndLocation const& nameAndLocation() const CATCH_OVERRIDE { - return m_nameAndLocation; + template<typename U> + static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } + static auto ok() -> BasicResult { return { ResultBase::Ok }; } + static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } + static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } + + explicit operator bool() const { return m_type == ResultBase::Ok; } + auto type() const -> ResultBase::Type { return m_type; } + auto errorMessage() const -> std::string { return m_errorMessage; } + + protected: + void enforceOk() const override { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert( m_type != ResultBase::LogicError ); + assert( m_type != ResultBase::RuntimeError ); + if( m_type != ResultBase::Ok ) + std::abort(); + } + + std::string m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultBase::Type type, std::string const &message ) + : ResultValueBase<T>(type), + m_errorMessage(message) + { + assert( m_type != ResultBase::Ok ); } - virtual bool isComplete() const CATCH_OVERRIDE { - return m_runState == CompletedSuccessfully || m_runState == Failed; + + using ResultValueBase<T>::ResultValueBase; + using ResultBase::m_type; + }; + + enum class ParseResultType { + Matched, NoMatch, ShortCircuitAll, ShortCircuitSame + }; + + class ParseState { + public: + + ParseState( ParseResultType type, TokenStream const &remainingTokens ) + : m_type(type), + m_remainingTokens( remainingTokens ) + {} + + auto type() const -> ParseResultType { return m_type; } + auto remainingTokens() const -> TokenStream { return m_remainingTokens; } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult<void>; + using ParserResult = BasicResult<ParseResultType>; + using InternalParseResult = BasicResult<ParseState>; + + struct HelpColumns { + std::string left; + std::string right; + }; + + template<typename T> + inline auto convertInto( std::string const &source, T& target ) -> ParserResult { + std::stringstream ss; + ss << source; + ss >> target; + if( ss.fail() ) + return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { + target = source; + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { + std::string srcLC = source; + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( unsigned char c ) { return static_cast<char>( std::tolower(c) ); } ); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") + target = false; + else + return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + } +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template<typename T> + inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T>& target ) -> ParserResult { + T temp; + auto result = convertInto( source, temp ); + if( result ) + target = std::move(temp); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct NonCopyable { + NonCopyable() = default; + NonCopyable( NonCopyable const & ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable &operator=( NonCopyable const & ) = delete; + NonCopyable &operator=( NonCopyable && ) = delete; + }; + + struct BoundRef : NonCopyable { + virtual ~BoundRef() = default; + virtual auto isContainer() const -> bool { return false; } + virtual auto isFlag() const -> bool { return false; } + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const &arg ) -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + virtual auto isFlag() const -> bool { return true; } + }; + + template<typename T> + struct BoundValueRef : BoundValueRefBase { + T &m_ref; + + explicit BoundValueRef( T &ref ) : m_ref( ref ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return convertInto( arg, m_ref ); } - virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { - return m_runState == CompletedSuccessfully; + }; + + template<typename T> + struct BoundValueRef<std::vector<T>> : BoundValueRefBase { + std::vector<T> &m_ref; + + explicit BoundValueRef( std::vector<T> &ref ) : m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const &arg ) -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if( result ) + m_ref.push_back( temp ); + return result; } - virtual bool isOpen() const CATCH_OVERRIDE { - return m_runState != NotStarted && !isComplete(); + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool &m_ref; + + explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} + + auto setFlag( bool flag ) -> ParserResult override { + m_ref = flag; + return ParserResult::ok( ParseResultType::Matched ); } - virtual bool hasChildren() const CATCH_OVERRIDE { - return !m_children.empty(); + }; + + template<typename ReturnType> + struct LambdaInvoker { + static_assert( std::is_same<ReturnType, ParserResult>::value, "Lambda must return void or clara::ParserResult" ); + + template<typename L, typename ArgType> + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + return lambda( arg ); } + }; - virtual void addChild( Ptr<ITracker> const& child ) CATCH_OVERRIDE { - m_children.push_back( child ); + template<> + struct LambdaInvoker<void> { + template<typename L, typename ArgType> + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); } + }; + + template<typename ArgType, typename L> + inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result + ? result + : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp ); + } + + template<typename L> + struct BoundLambda : BoundValueRefBase { + L m_lambda; - virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) CATCH_OVERRIDE { - Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); - return( it != m_children.end() ) - ? it->get() - : CATCH_NULL; + static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( m_lambda, arg ); } - virtual ITracker& parent() CATCH_OVERRIDE { - assert( m_parent ); // Should always be non-null except for root - return *m_parent; + }; + + template<typename L> + struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" ); + static_assert( std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value, "flags must be boolean" ); + + explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( m_lambda, flag ); } + }; - virtual void openChild() CATCH_OVERRIDE { - if( m_runState != ExecutingChildren ) { - m_runState = ExecutingChildren; - if( m_parent ) - m_parent->openChild(); - } + enum class Optionality { Optional, Required }; + + struct Parser; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; + virtual auto cardinality() const -> size_t { return 1; } + + auto parse( Args const &args ) const -> InternalParseResult { + return parse( args.exeName(), TokenStream( args ) ); } + }; - virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; } - virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; } + template<typename DerivedT> + class ComposableParserImpl : public ParserBase { + public: + template<typename T> + auto operator|( T const &other ) const -> Parser; - void open() { - m_runState = Executing; - moveToThis(); - if( m_parent ) - m_parent->openChild(); + template<typename T> + auto operator+( T const &other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template<typename DerivedT> + class ParserRefImpl : public ComposableParserImpl<DerivedT> { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr<BoundRef> m_ref; + std::string m_hint; + std::string m_description; + + explicit ParserRefImpl( std::shared_ptr<BoundRef> const &ref ) : m_ref( ref ) {} + + public: + template<typename T> + ParserRefImpl( T &ref, std::string const &hint ) + : m_ref( std::make_shared<BoundValueRef<T>>( ref ) ), + m_hint( hint ) + {} + + template<typename LambdaT> + ParserRefImpl( LambdaT const &ref, std::string const &hint ) + : m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ), + m_hint(hint) + {} + + auto operator()( std::string const &description ) -> DerivedT & { + m_description = description; + return static_cast<DerivedT &>( *this ); } - virtual void close() CATCH_OVERRIDE { + auto optional() -> DerivedT & { + m_optionality = Optionality::Optional; + return static_cast<DerivedT &>( *this ); + }; - // Close any still open children (e.g. generators) - while( &m_ctx.currentTracker() != this ) - m_ctx.currentTracker().close(); + auto required() -> DerivedT & { + m_optionality = Optionality::Required; + return static_cast<DerivedT &>( *this ); + }; - switch( m_runState ) { - case NotStarted: - case CompletedSuccessfully: - case Failed: - throw std::logic_error( "Illogical state" ); + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } - case NeedsAnotherRun: - break;; + auto cardinality() const -> size_t override { + if( m_ref->isContainer() ) + return 0; + else + return 1; + } - case Executing: - m_runState = CompletedSuccessfully; - break; - case ExecutingChildren: - if( m_children.empty() || m_children.back()->isComplete() ) - m_runState = CompletedSuccessfully; - break; + auto hint() const -> std::string { return m_hint; } + }; - default: - throw std::logic_error( "Unexpected state" ); - } - moveToParent(); - m_ctx.completeCycle(); + class ExeName : public ComposableParserImpl<ExeName> { + std::shared_ptr<std::string> m_name; + std::shared_ptr<BoundValueRefBase> m_ref; + + template<typename LambdaT> + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundValueRefBase> { + return std::make_shared<BoundLambda<LambdaT>>( lambda) ; } - virtual void fail() CATCH_OVERRIDE { - m_runState = Failed; - if( m_parent ) - m_parent->markAsNeedingAnotherRun(); - moveToParent(); - m_ctx.completeCycle(); + + public: + ExeName() : m_name( std::make_shared<std::string>( "<executable>" ) ) {} + + explicit ExeName( std::string &ref ) : ExeName() { + m_ref = std::make_shared<BoundValueRef<std::string>>( ref ); } - virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { - m_runState = NeedsAnotherRun; + + template<typename LambdaT> + explicit ExeName( LambdaT const& lambda ) : ExeName() { + m_ref = std::make_shared<BoundLambda<LambdaT>>( lambda ); } - private: - void moveToParent() { - assert( m_parent ); - m_ctx.setCurrentTracker( m_parent ); + + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); } - void moveToThis() { - m_ctx.setCurrentTracker( this ); + + auto name() const -> std::string { return *m_name; } + auto set( std::string const& newName ) -> ParserResult { + + auto lastSlash = newName.find_last_of( "\\/" ); + auto filename = ( lastSlash == std::string::npos ) + ? newName + : newName.substr( lastSlash+1 ); + + *m_name = filename; + if( m_ref ) + return m_ref->setValue( filename ); + else + return ParserResult::ok( ParseResultType::Matched ); } }; - class SectionTracker : public TrackerBase { - std::vector<std::string> m_filters; + class Arg : public ParserRefImpl<Arg> { public: - SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : TrackerBase( nameAndLocation, ctx, parent ) - { - if( parent ) { - while( !parent->isSectionTracker() ) - parent = &parent->parent(); + using ParserRefImpl::ParserRefImpl; - SectionTracker& parentSection = static_cast<SectionTracker&>( *parent ); - addNextFilters( parentSection.m_filters ); - } + auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + auto const &token = *remainingTokens; + if( token.type != TokenType::Argument ) + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + + assert( !m_ref->isFlag() ); + auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() ); + + auto result = valueRef->setValue( remainingTokens->token ); + if( !result ) + return InternalParseResult( result ); + else + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); } - virtual ~SectionTracker(); + }; + + inline auto normaliseOpt( std::string const &optName ) -> std::string { +#ifdef CATCH_PLATFORM_WINDOWS + if( optName[0] == '/' ) + return "-" + optName.substr( 1 ); + else +#endif + return optName; + } - virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; } + class Opt : public ParserRefImpl<Opt> { + protected: + std::vector<std::string> m_optNames; + + public: + template<typename LambdaT> + explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared<BoundFlagLambda<LambdaT>>( ref ) ) {} + + explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared<BoundFlagRef>( ref ) ) {} - static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { - SectionTracker* section = CATCH_NULL; + template<typename LambdaT> + Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} - ITracker& currentTracker = ctx.currentTracker(); - if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { - assert( childTracker ); - assert( childTracker->isSectionTracker() ); - section = static_cast<SectionTracker*>( childTracker ); + template<typename T> + Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} + + auto operator[]( std::string const &optName ) -> Opt & { + m_optNames.push_back( optName ); + return *this; + } + + auto getHelpColumns() const -> std::vector<HelpColumns> { + std::ostringstream oss; + bool first = true; + for( auto const &opt : m_optNames ) { + if (first) + first = false; + else + oss << ", "; + oss << opt; } - else { - section = new SectionTracker( nameAndLocation, ctx, ¤tTracker ); - currentTracker.addChild( section ); + if( !m_hint.empty() ) + oss << " <" << m_hint << ">"; + return { { oss.str(), m_description } }; + } + + auto isMatch( std::string const &optToken ) const -> bool { + auto normalisedToken = normaliseOpt( optToken ); + for( auto const &name : m_optNames ) { + if( normaliseOpt( name ) == normalisedToken ) + return true; } - if( !ctx.completedCycle() ) - section->tryOpen(); - return *section; + return false; } - void tryOpen() { - if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) - open(); + using ParserBase::parse; + + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + if( remainingTokens && remainingTokens->type == TokenType::Option ) { + auto const &token = *remainingTokens; + if( isMatch(token.token ) ) { + if( m_ref->isFlag() ) { + auto flagRef = static_cast<detail::BoundFlagRefBase*>( m_ref.get() ); + auto result = flagRef->setFlag( true ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } else { + auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() ); + ++remainingTokens; + if( !remainingTokens ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto const &argToken = *remainingTokens; + if( argToken.type != TokenType::Argument ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto result = valueRef->setValue( argToken.token ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + } + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); } - void addInitialFilters( std::vector<std::string> const& filters ) { - if( !filters.empty() ) { - m_filters.push_back(""); // Root - should never be consulted - m_filters.push_back(""); // Test Case - not a section filter - m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + auto validate() const -> Result override { + if( m_optNames.empty() ) + return Result::logicError( "No options supplied to Opt" ); + for( auto const &name : m_optNames ) { + if( name.empty() ) + return Result::logicError( "Option name cannot be empty" ); +#ifdef CATCH_PLATFORM_WINDOWS + if( name[0] != '-' && name[0] != '/' ) + return Result::logicError( "Option name must begin with '-' or '/'" ); +#else + if( name[0] != '-' ) + return Result::logicError( "Option name must begin with '-'" ); +#endif } + return ParserRefImpl::validate(); } - void addNextFilters( std::vector<std::string> const& filters ) { - if( filters.size() > 1 ) - m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + }; + + struct Help : Opt { + Help( bool &showHelpFlag ) + : Opt([&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok( ParseResultType::ShortCircuitAll ); + }) + { + static_cast<Opt &>( *this ) + ("display usage information") + ["-?"]["-h"]["--help"] + .optional(); } }; - class IndexTracker : public TrackerBase { - int m_size; - int m_index; - public: - IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) - : TrackerBase( nameAndLocation, ctx, parent ), - m_size( size ), - m_index( -1 ) - {} - virtual ~IndexTracker(); + struct Parser : ParserBase { - virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; } + mutable ExeName m_exeName; + std::vector<Opt> m_options; + std::vector<Arg> m_args; - static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { - IndexTracker* tracker = CATCH_NULL; + auto operator|=( ExeName const &exeName ) -> Parser & { + m_exeName = exeName; + return *this; + } - ITracker& currentTracker = ctx.currentTracker(); - if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { - assert( childTracker ); - assert( childTracker->isIndexTracker() ); - tracker = static_cast<IndexTracker*>( childTracker ); - } - else { - tracker = new IndexTracker( nameAndLocation, ctx, ¤tTracker, size ); - currentTracker.addChild( tracker ); + auto operator|=( Arg const &arg ) -> Parser & { + m_args.push_back(arg); + return *this; + } + + auto operator|=( Opt const &opt ) -> Parser & { + m_options.push_back(opt); + return *this; + } + + auto operator|=( Parser const &other ) -> Parser & { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } + + template<typename T> + auto operator|( T const &other ) const -> Parser { + return Parser( *this ) |= other; + } + + // Forward deprecated interface with '+' instead of '|' + template<typename T> + auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } + template<typename T> + auto operator+( T const &other ) const -> Parser { return operator|( other ); } + + auto getHelpColumns() const -> std::vector<HelpColumns> { + std::vector<HelpColumns> cols; + for (auto const &o : m_options) { + auto childCols = o.getHelpColumns(); + cols.insert( cols.end(), childCols.begin(), childCols.end() ); } + return cols; + } - if( !ctx.completedCycle() && !tracker->isComplete() ) { - if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) - tracker->moveNext(); - tracker->open(); + void writeToStream( std::ostream &os ) const { + if (!m_exeName.name().empty()) { + os << "usage:\n" << " " << m_exeName.name() << " "; + bool required = true, first = true; + for( auto const &arg : m_args ) { + if (first) + first = false; + else + os << " "; + if( arg.isOptional() && required ) { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if( arg.cardinality() == 0 ) + os << " ... "; + } + if( !required ) + os << "]"; + if( !m_options.empty() ) + os << " options"; + os << "\n\nwhere options are:" << std::endl; } - return *tracker; + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for( auto const &cols : rows ) + optWidth = (std::max)(optWidth, cols.left.size() + 2); + + optWidth = (std::min)(optWidth, consoleWidth/2); + + for( auto const &cols : rows ) { + auto row = + TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + + TextFlow::Spacer(4) + + TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); + os << row << std::endl; + } } - int index() const { return m_index; } + friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { + parser.writeToStream( os ); + return os; + } - void moveNext() { - m_index++; - m_children.clear(); + auto validate() const -> Result override { + for( auto const &opt : m_options ) { + auto result = opt.validate(); + if( !result ) + return result; + } + for( auto const &arg : m_args ) { + auto result = arg.validate(); + if( !result ) + return result; + } + return Result::ok(); } - virtual void close() CATCH_OVERRIDE { - TrackerBase::close(); - if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) - m_runState = Executing; + using ParserBase::parse; + + auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { + + struct ParserInfo { + ParserBase const* parser = nullptr; + size_t count = 0; + }; + const size_t totalParsers = m_options.size() + m_args.size(); + assert( totalParsers < 512 ); + // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do + ParserInfo parseInfos[512]; + + { + size_t i = 0; + for (auto const &opt : m_options) parseInfos[i++].parser = &opt; + for (auto const &arg : m_args) parseInfos[i++].parser = &arg; + } + + m_exeName.set( exeName ); + + auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + while( result.value().remainingTokens() ) { + bool tokenParsed = false; + + for( size_t i = 0; i < totalParsers; ++i ) { + auto& parseInfo = parseInfos[i]; + if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { + result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + if (!result) + return result; + if (result.value().type() != ParseResultType::NoMatch) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if( result.value().type() == ParseResultType::ShortCircuitAll ) + return result; + if( !tokenParsed ) + return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); + } + // !TBD Check missing required options + return result; } }; - inline ITracker& TrackerContext::startRun() { - m_rootTracker = new SectionTracker( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, CATCH_NULL ); - m_currentTracker = CATCH_NULL; - m_runState = Executing; - return *m_rootTracker; + template<typename DerivedT> + template<typename T> + auto ComposableParserImpl<DerivedT>::operator|( T const &other ) const -> Parser { + return Parser() | static_cast<DerivedT const &>( *this ) | other; } +} // namespace detail -} // namespace TestCaseTracking +// A Combined parser +using detail::Parser; -using TestCaseTracking::ITracker; -using TestCaseTracking::TrackerContext; -using TestCaseTracking::SectionTracker; -using TestCaseTracking::IndexTracker; +// A parser for options +using detail::Opt; -} // namespace Catch +// A parser for arguments +using detail::Arg; + +// Wrapper for argc, argv from main() +using detail::Args; + +// Specifies the name of the executable +using detail::ExeName; + +// Convenience wrapper for option parser that specifies the help option +using detail::Help; + +// enum of result types from a parse +using detail::ParseResultType; + +// Result type for parser operation +using detail::ParserResult; + +}} // namespace Catch::clara + +// end clara.hpp +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +// end catch_clara.h +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ); + +} // end namespace Catch + +// end catch_commandline.h +#include <fstream> +#include <ctime> + +namespace Catch { + + clara::Parser makeCommandLineParser( ConfigData& config ) { + + using namespace clara; + + auto const setWarning = [&]( std::string const& warning ) { + auto warningSet = [&]() { + if( warning == "NoAssertions" ) + return WarnAbout::NoAssertions; + + if ( warning == "NoTests" ) + return WarnAbout::NoTests; + + return WarnAbout::Nothing; + }(); + + if (warningSet == WarnAbout::Nothing) + return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); + config.warnings = static_cast<WarnAbout::What>( config.warnings | warningSet ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const loadTestNamesFromFile = [&]( std::string const& filename ) { + std::ifstream f( filename.c_str() ); + if( !f.is_open() ) + return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + config.testsOrTags.push_back( line ); + config.testsOrTags.emplace_back( "," ); + } + } + //Remove comma in the end + if(!config.testsOrTags.empty()) + config.testsOrTags.erase( config.testsOrTags.end()-1 ); + + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setTestOrder = [&]( std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setRngSeed = [&]( std::string const& seed ) { + if( seed != "time" ) + return clara::detail::convertInto( seed, config.rngSeed ); + config.rngSeed = static_cast<unsigned int>( std::time(nullptr) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setColourUsage = [&]( std::string const& useColour ) { + auto mode = toLower( useColour ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setWaitForKeypress = [&]( std::string const& keypress ) { + auto keypressLc = toLower( keypress ); + if (keypressLc == "never") + config.waitForKeypress = WaitForKeypress::Never; + else if( keypressLc == "start" ) + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if( keypressLc == "exit" ) + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if( keypressLc == "both" ) + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError( "keypress argument must be one of: never, start, exit or both. '" + keypress + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setVerbosity = [&]( std::string const& verbosity ) { + auto lcVerbosity = toLower( verbosity ); + if( lcVerbosity == "quiet" ) + config.verbosity = Verbosity::Quiet; + else if( lcVerbosity == "normal" ) + config.verbosity = Verbosity::Normal; + else if( lcVerbosity == "high" ) + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setReporter = [&]( std::string const& reporter ) { + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); -CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + auto lcReporter = toLower( reporter ); + auto result = factories.find( lcReporter ); + + if( factories.end() != result ) + config.reporterName = lcReporter; + else + return ParserResult::runtimeError( "Unrecognized reporter, '" + reporter + "'. Check available with --list-reporters" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + + auto cli + = ExeName( config.processName ) + | Help( config.showHelp ) + | Opt( config.listTests ) + ["-l"]["--list-tests"] + ( "list all/matching test cases" ) + | Opt( config.listTags ) + ["-t"]["--list-tags"] + ( "list all/matching tags" ) + | Opt( config.showSuccessfulTests ) + ["-s"]["--success"] + ( "include successful tests in output" ) + | Opt( config.shouldDebugBreak ) + ["-b"]["--break"] + ( "break into debugger on failure" ) + | Opt( config.noThrow ) + ["-e"]["--nothrow"] + ( "skip exception tests" ) + | Opt( config.showInvisibles ) + ["-i"]["--invisibles"] + ( "show invisibles (tabs, newlines)" ) + | Opt( config.outputFilename, "filename" ) + ["-o"]["--out"] + ( "output filename" ) + | Opt( setReporter, "name" ) + ["-r"]["--reporter"] + ( "reporter to use (defaults to console)" ) + | Opt( config.name, "name" ) + ["-n"]["--name"] + ( "suite name" ) + | Opt( [&]( bool ){ config.abortAfter = 1; } ) + ["-a"]["--abort"] + ( "abort at first failure" ) + | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) + ["-x"]["--abortx"] + ( "abort after x failures" ) + | Opt( setWarning, "warning name" ) + ["-w"]["--warn"] + ( "enable warnings" ) + | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) + ["-d"]["--durations"] + ( "show test durations" ) + | Opt( config.minDuration, "seconds" ) + ["-D"]["--min-duration"] + ( "show test durations for tests taking at least the given number of seconds" ) + | Opt( loadTestNamesFromFile, "filename" ) + ["-f"]["--input-file"] + ( "load test names to run from a file" ) + | Opt( config.filenamesAsTags ) + ["-#"]["--filenames-as-tags"] + ( "adds a tag for the filename" ) + | Opt( config.sectionsToRun, "section name" ) + ["-c"]["--section"] + ( "specify section to run" ) + | Opt( setVerbosity, "quiet|normal|high" ) + ["-v"]["--verbosity"] + ( "set output verbosity" ) + | Opt( config.listTestNamesOnly ) + ["--list-test-names-only"] + ( "list all/matching test cases names only" ) + | Opt( config.listReporters ) + ["--list-reporters"] + ( "list all reporters" ) + | Opt( setTestOrder, "decl|lex|rand" ) + ["--order"] + ( "test case order (defaults to decl)" ) + | Opt( setRngSeed, "'time'|number" ) + ["--rng-seed"] + ( "set a specific seed for random numbers" ) + | Opt( setColourUsage, "yes|no" ) + ["--use-colour"] + ( "should output be colourised" ) + | Opt( config.libIdentify ) + ["--libidentify"] + ( "report name and version according to libidentify standard" ) + | Opt( setWaitForKeypress, "never|start|exit|both" ) + ["--wait-for-keypress"] + ( "waits for a keypress before exiting" ) + | Opt( config.benchmarkSamples, "samples" ) + ["--benchmark-samples"] + ( "number of samples to collect (default: 100)" ) + | Opt( config.benchmarkResamples, "resamples" ) + ["--benchmark-resamples"] + ( "number of resamples for the bootstrap (default: 100000)" ) + | Opt( config.benchmarkConfidenceInterval, "confidence interval" ) + ["--benchmark-confidence-interval"] + ( "confidence interval for the bootstrap (between 0 and 1, default: 0.95)" ) + | Opt( config.benchmarkNoAnalysis ) + ["--benchmark-no-analysis"] + ( "perform only measurements; do not perform any analysis" ) + | Opt( config.benchmarkWarmupTime, "benchmarkWarmupTime" ) + ["--benchmark-warmup-time"] + ( "amount of time in milliseconds spent on warming up each test (default: 100)" ) + | Arg( config.testsOrTags, "test name|pattern|tags" ) + ( "which test or tests to use" ); + + return cli; + } + +} // end namespace Catch +// end catch_commandline.cpp +// start catch_common.cpp -// #included from: catch_fatal_condition.hpp -#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED +#include <cstring> +#include <ostream> namespace Catch { - // Report the error condition - inline void reportFatal( std::string const& message ) { - IContext& context = Catch::getCurrentContext(); - IResultCapture* resultCapture = context.getResultCapture(); - resultCapture->handleFatalErrorCondition( message ); + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { + // We can assume that the same file will usually have the same pointer. + // Thus, if the pointers are the same, there is no point in calling the strcmp + return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); } -} // namespace Catch + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + + std::string StreamEndStop::operator+() const { + return std::string(); + } + + NonCopyable::NonCopyable() = default; + NonCopyable::~NonCopyable() = default; + +} +// end catch_common.cpp +// start catch_config.cpp + +namespace Catch { + + Config::Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) + { + // We need to trim filter specs to avoid trouble with superfluous + // whitespace (esp. important for bdd macros, as those are manually + // aligned with whitespace). + + for (auto& elem : m_data.testsOrTags) { + elem = trim(elem); + } + for (auto& elem : m_data.sectionsToRun) { + elem = trim(elem); + } + + TestSpecParser parser(ITagAliasRegistry::get()); + if (!m_data.testsOrTags.empty()) { + m_hasTestFilters = true; + for (auto const& testOrTags : m_data.testsOrTags) { + parser.parse(testOrTags); + } + } + m_testSpec = parser.testSpec(); + } + + std::string const& Config::getFilename() const { + return m_data.outputFilename ; + } + + bool Config::listTests() const { return m_data.listTests; } + bool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool Config::listTags() const { return m_data.listTags; } + bool Config::listReporters() const { return m_data.listReporters; } + + std::string Config::getProcessName() const { return m_data.processName; } + std::string const& Config::getReporterName() const { return m_data.reporterName; } + + std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; } + std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + + TestSpec const& Config::testSpec() const { return m_testSpec; } + bool Config::hasTestFilters() const { return m_hasTestFilters; } + + bool Config::showHelp() const { return m_data.showHelp; } + + // IConfig interface + bool Config::allowThrows() const { return !m_data.noThrow; } + std::ostream& Config::stream() const { return m_stream->stream(); } + std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } + bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } + ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } + double Config::minDuration() const { return m_data.minDuration; } + RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } + unsigned int Config::rngSeed() const { return m_data.rngSeed; } + UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } + bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } + int Config::abortAfter() const { return m_data.abortAfter; } + bool Config::showInvisibles() const { return m_data.showInvisibles; } + Verbosity Config::verbosity() const { return m_data.verbosity; } + + bool Config::benchmarkNoAnalysis() const { return m_data.benchmarkNoAnalysis; } + int Config::benchmarkSamples() const { return m_data.benchmarkSamples; } + double Config::benchmarkConfidenceInterval() const { return m_data.benchmarkConfidenceInterval; } + unsigned int Config::benchmarkResamples() const { return m_data.benchmarkResamples; } + std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); } + + IStream const* Config::openStream() { + return Catch::makeStream(m_data.outputFilename); + } + +} // end namespace Catch +// end catch_config.cpp +// start catch_console_colour.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +// start catch_errno_guard.h + +namespace Catch { + + class ErrnoGuard { + public: + ErrnoGuard(); + ~ErrnoGuard(); + private: + int m_oldErrno; + }; + +} -#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// -// #included from: catch_windows_h_proxy.h +// end catch_errno_guard.h +// start catch_windows_h_proxy.h -#define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED -#ifdef CATCH_DEFINES_NOMINMAX +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX # define NOMINMAX #endif -#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN #endif @@ -6349,1055 +10059,2270 @@ namespace Catch { #include <windows.h> #endif -#ifdef CATCH_DEFINES_NOMINMAX +#ifdef CATCH_DEFINED_NOMINMAX # undef NOMINMAX #endif -#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN # undef WIN32_LEAN_AND_MEAN #endif +#endif // defined(CATCH_PLATFORM_WINDOWS) -# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) +// end catch_windows_h_proxy.h +#include <sstream> namespace Catch { - struct FatalConditionHandler { - void reset() {} - }; -} + namespace { + + struct IColourImpl { + virtual ~IColourImpl() = default; + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) override {} -# else // CATCH_CONFIG_WINDOWS_SEH is defined + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// namespace Catch { +namespace { - struct SignalDefs { DWORD id; const char* name; }; - extern SignalDefs signalDefs[]; - // There is no 1-1 mapping between signals and windows exceptions. - // Windows can easily distinguish between SO and SigSegV, - // but SigInt, SigTerm, etc are handled differently. - SignalDefs signalDefs[] = { - { 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" }, - }; + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } - struct FatalConditionHandler { + void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { - for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { - if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { - reportFatal(signalDefs[i].name); - } + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + + default: + CATCH_ERROR( "Unknown colour requested" ); } - // If its not an exception we care about, pass it along. - // This stops us from eating debugger breaks etc. - return EXCEPTION_CONTINUE_SEARCH; } - FatalConditionHandler() { - isSet = true; - // 32k seems enough for Catch to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - exceptionHandlerHandle = CATCH_NULL; - // Register as first handler in current chain - exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = UseColour::Yes; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// - static void reset() { - if (isSet) { - // Unregister handler and restore the old guarantee - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = CATCH_NULL; - isSet = false; +#include <unistd.h> + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + case Colour::BrightYellow: return setColour( "[1;33m" ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); } } - - ~FatalConditionHandler() { - reset(); + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; } + private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; + void setColour( const char* _escapeCode ) { + getCurrentContext().getConfig()->stream() + << '\033' << _escapeCode; + } }; - bool FatalConditionHandler::isSet = false; - ULONG FatalConditionHandler::guaranteeSize = 0; - PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL; + bool useColourOnPlatform() { + return +#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) + !isDebuggerActive() && +#endif +#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) + isatty(STDOUT_FILENO) +#else + false +#endif + ; + } + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = useColourOnPlatform() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch -} // namespace Catch +#else // not Windows or ANSI /////////////////////////////////////////////// -# endif // CATCH_CONFIG_WINDOWS_SEH +namespace Catch { -#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } -# if !defined(CATCH_CONFIG_POSIX_SIGNALS) +} // end namespace Catch + +#endif // Windows/ ANSI/ None namespace Catch { - struct FatalConditionHandler { - void reset() {} - }; -} -# else // CATCH_CONFIG_POSIX_SIGNALS is defined + Colour::Colour( Code _colourCode ) { use( _colourCode ); } + Colour::Colour( Colour&& other ) noexcept { + m_moved = other.m_moved; + other.m_moved = true; + } + Colour& Colour::operator=( Colour&& other ) noexcept { + m_moved = other.m_moved; + other.m_moved = true; + return *this; + } -#include <signal.h> + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + // Strictly speaking, this cannot possibly happen. + // However, under some conditions it does happen (see #1626), + // and this change is small enough that we can let practicality + // triumph over purity in this case. + if (impl != nullptr) { + impl->use( _colourCode ); + } + } + + std::ostream& operator << ( std::ostream& os, Colour const& ) { + return os; + } + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_console_colour.cpp +// start catch_context.cpp namespace Catch { - struct SignalDefs { - int id; - const char* name; - }; - extern SignalDefs signalDefs[]; - SignalDefs signalDefs[] = { - { SIGINT, "SIGINT - Terminal interrupt signal" }, - { SIGILL, "SIGILL - Illegal instruction signal" }, - { SIGFPE, "SIGFPE - Floating point error signal" }, - { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, - { SIGTERM, "SIGTERM - Termination request signal" }, - { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } - }; - - struct FatalConditionHandler { - - static bool isSet; - static struct sigaction oldSigActions [sizeof(signalDefs)/sizeof(SignalDefs)]; - static stack_t oldSigStack; - static char altStackMem[SIGSTKSZ]; - - static void handleSignal( int sig ) { - std::string name = "<unknown signal>"; - for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { - SignalDefs &def = signalDefs[i]; - if (sig == def.id) { - name = def.name; - break; - } - } - reset(); - reportFatal(name); - raise( sig ); - } + class Context : public IMutableContext, NonCopyable { - FatalConditionHandler() { - isSet = true; - stack_t sigStack; - sigStack.ss_sp = altStackMem; - sigStack.ss_size = SIGSTKSZ; - sigStack.ss_flags = 0; - sigaltstack(&sigStack, &oldSigStack); - struct sigaction sa = { 0 }; + public: // IContext + IResultCapture* getResultCapture() override { + return m_resultCapture; + } + IRunner* getRunner() override { + return m_runner; + } - sa.sa_handler = handleSignal; - sa.sa_flags = SA_ONSTACK; - for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { - sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); - } + IConfigPtr const& getConfig() const override { + return m_config; } - ~FatalConditionHandler() { - reset(); + ~Context() override; + + public: // IMutableContext + void setResultCapture( IResultCapture* resultCapture ) override { + m_resultCapture = resultCapture; } - static void reset() { - if( isSet ) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { - sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL); - } - // Return the old stack - sigaltstack(&oldSigStack, CATCH_NULL); - isSet = false; - } + void setRunner( IRunner* runner ) override { + m_runner = runner; } + void setConfig( IConfigPtr const& config ) override { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IConfigPtr m_config; + IRunner* m_runner = nullptr; + IResultCapture* m_resultCapture = nullptr; }; - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; + IMutableContext *IMutableContext::currentContext = nullptr; -} // namespace Catch + void IMutableContext::createContext() + { + currentContext = new Context(); + } -# endif // CATCH_CONFIG_POSIX_SIGNALS + void cleanUpContext() { + delete IMutableContext::currentContext; + IMutableContext::currentContext = nullptr; + } + IContext::~IContext() = default; + IMutableContext::~IMutableContext() = default; + Context::~Context() = default; -#endif // not Windows + SimplePcg32& rng() { + static SimplePcg32 s_rng; + return s_rng; + } + +} +// end catch_context.cpp +// start catch_debug_console.cpp + +// start catch_debug_console.h -#include <set> #include <string> namespace Catch { + void writeToDebugConsole( std::string const& text ); +} - class StreamRedirect { +// end catch_debug_console.h +#if defined(CATCH_CONFIG_ANDROID_LOGWRITE) +#include <android/log.h> - public: - StreamRedirect( std::ostream& stream, std::string& targetString ) - : m_stream( stream ), - m_prevBuf( stream.rdbuf() ), - m_targetString( targetString ) - { - stream.rdbuf( m_oss.rdbuf() ); + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + __android_log_write( ANDROID_LOG_DEBUG, "Catch", text.c_str() ); } + } - ~StreamRedirect() { - m_targetString += m_oss.str(); - m_stream.rdbuf( m_prevBuf ); +#elif defined(CATCH_PLATFORM_WINDOWS) + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); } + } - private: - std::ostream& m_stream; - std::streambuf* m_prevBuf; - std::ostringstream m_oss; - std::string& m_targetString; - }; +#else - // StdErr has two constituent streams in C++, std::cerr and std::clog - // This means that we need to redirect 2 streams into 1 to keep proper - // order of writes and cannot use StreamRedirect on its own - class StdErrRedirect { - public: - StdErrRedirect(std::string& targetString) - :m_cerrBuf( cerr().rdbuf() ), m_clogBuf(clog().rdbuf()), - m_targetString(targetString){ - cerr().rdbuf(m_oss.rdbuf()); - clog().rdbuf(m_oss.rdbuf()); - } - ~StdErrRedirect() { - m_targetString += m_oss.str(); - cerr().rdbuf(m_cerrBuf); - clog().rdbuf(m_clogBuf); + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; } - private: - std::streambuf* m_cerrBuf; - std::streambuf* m_clogBuf; - std::ostringstream m_oss; - std::string& m_targetString; - }; + } - /////////////////////////////////////////////////////////////////////////// +#endif // Platform +// end catch_debug_console.cpp +// start catch_debugger.cpp - class RunContext : public IResultCapture, public IRunner { +#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) - RunContext( RunContext const& ); - void operator =( RunContext const& ); +# include <cassert> +# include <sys/types.h> +# include <unistd.h> +# include <cstddef> +# include <ostream> - public: +#ifdef __apple_build_version__ + // These headers will only compile with AppleClang (XCode) + // For other compilers (Clang, GCC, ... ) we need to exclude them +# include <sys/sysctl.h> +#endif - explicit RunContext( Ptr<IConfig const> const& _config, Ptr<IStreamingReporter> const& reporter ) - : m_runInfo( _config->name() ), - m_context( getCurrentMutableContext() ), - m_activeTestCase( CATCH_NULL ), - m_config( _config ), - m_reporter( reporter ), - m_shouldReportUnexpected ( true ) - { - m_context.setRunner( this ); - m_context.setConfig( m_config ); - m_context.setResultCapture( this ); - m_reporter->testRunStarting( m_runInfo ); - } + namespace Catch { + #ifdef __apple_build_version__ + // The following function is taken directly from the following technical note: + // https://developer.apple.com/library/archive/qa/qa1361/_index.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + int mib[4]; + struct kinfo_proc info; + std::size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; - virtual ~RunContext() { - m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); } + #else + bool isDebuggerActive() { + // We need to find another way to determine this for non-appleclang compilers on macOS + return false; + } + #endif + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include <fstream> + #include <string> + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } - void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { - m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; } - void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { - m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; } + } +#else + namespace Catch { + bool isDebuggerActive() { return false; } + } +#endif // Platform +// end catch_debugger.cpp +// start catch_decomposer.cpp - Totals runTest( TestCase const& testCase ) { - Totals prevTotals = m_totals; +namespace Catch { - std::string redirectedCout; - std::string redirectedCerr; + ITransientExpression::~ITransientExpression() = default; - TestCaseInfo testInfo = testCase.getTestCaseInfo(); + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { + if( lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ) + os << lhs << " " << op << " " << rhs; + else + os << lhs << "\n" << op << "\n" << rhs; + } +} +// end catch_decomposer.cpp +// start catch_enforce.cpp - m_reporter->testCaseStarting( testInfo ); +#include <stdexcept> - m_activeTestCase = &testCase; +namespace Catch { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) + [[noreturn]] + void throw_exception(std::exception const& e) { + Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); + } +#endif - do { - ITracker& rootTracker = m_trackerContext.startRun(); - assert( rootTracker.isSectionTracker() ); - static_cast<SectionTracker&>( rootTracker ).addInitialFilters( m_config->getSectionsToRun() ); - do { - m_trackerContext.startCycle(); - m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( testInfo.name, testInfo.lineInfo ) ); - runCurrentTest( redirectedCout, redirectedCerr ); - } - while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() ); - } - // !TBD: deprecated - this will be replaced by indexed trackers - while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + [[noreturn]] + void throw_logic_error(std::string const& msg) { + throw_exception(std::logic_error(msg)); + } - Totals deltaTotals = m_totals.delta( prevTotals ); - if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { - deltaTotals.assertions.failed++; - deltaTotals.testCases.passed--; - deltaTotals.testCases.failed++; - } - m_totals.testCases += deltaTotals.testCases; - m_reporter->testCaseEnded( TestCaseStats( testInfo, - deltaTotals, - redirectedCout, - redirectedCerr, - aborting() ) ); + [[noreturn]] + void throw_domain_error(std::string const& msg) { + throw_exception(std::domain_error(msg)); + } - m_activeTestCase = CATCH_NULL; - m_testCaseTracker = CATCH_NULL; + [[noreturn]] + void throw_runtime_error(std::string const& msg) { + throw_exception(std::runtime_error(msg)); + } - return deltaTotals; - } +} // namespace Catch; +// end catch_enforce.cpp +// start catch_enum_values_registry.cpp +// start catch_enum_values_registry.h - Ptr<IConfig const> config() const { - return m_config; - } +#include <vector> +#include <memory> + +namespace Catch { + + namespace Detail { + + std::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ); + + class EnumValuesRegistry : public IMutableEnumValuesRegistry { + + std::vector<std::unique_ptr<EnumInfo>> m_enumInfos; + + EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values) override; + }; + + std::vector<StringRef> parseEnums( StringRef enums ); + + } // Detail - private: // IResultCapture +} // Catch - virtual void assertionEnded( AssertionResult const& result ) { - if( result.getResultType() == ResultWas::Ok ) { - m_totals.assertions.passed++; +// end catch_enum_values_registry.h + +#include <map> +#include <cassert> + +namespace Catch { + + IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() {} + + namespace Detail { + + namespace { + // Extracts the actual name part of an enum instance + // In other words, it returns the Blue part of Bikeshed::Colour::Blue + StringRef extractInstanceName(StringRef enumInstance) { + // Find last occurrence of ":" + size_t name_start = enumInstance.size(); + while (name_start > 0 && enumInstance[name_start - 1] != ':') { + --name_start; + } + return enumInstance.substr(name_start, enumInstance.size() - name_start); } - else if( !result.isOk() ) { - if( m_activeTestCase->getTestCaseInfo().okToFail() ) - m_totals.assertions.failedButOk++; - else - m_totals.assertions.failed++; + } + + std::vector<StringRef> parseEnums( StringRef enums ) { + auto enumValues = splitStringRef( enums, ',' ); + std::vector<StringRef> parsed; + parsed.reserve( enumValues.size() ); + for( auto const& enumValue : enumValues ) { + parsed.push_back(trim(extractInstanceName(enumValue))); } + return parsed; + } - // We have no use for the return value (whether messages should be cleared), because messages were made scoped - // and should be let to clear themselves out. - static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + EnumInfo::~EnumInfo() {} - // Reset working state - m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); - m_lastResult = result; + StringRef EnumInfo::lookup( int value ) const { + for( auto const& valueToName : m_values ) { + if( valueToName.first == value ) + return valueToName.second; + } + return "{** unexpected enum value **}"_sr; } - virtual bool lastAssertionPassed() - { - return m_totals.assertions.passed == (m_prevPassed + 1); - } + std::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) { + std::unique_ptr<EnumInfo> enumInfo( new EnumInfo ); + enumInfo->m_name = enumName; + enumInfo->m_values.reserve( values.size() ); - virtual void assertionPassed() - { - m_totals.assertions.passed++; - m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"; - m_lastAssertionInfo.macroName = ""; + const auto valueNames = Catch::Detail::parseEnums( allValueNames ); + assert( valueNames.size() == values.size() ); + std::size_t i = 0; + for( auto value : values ) + enumInfo->m_values.emplace_back(value, valueNames[i++]); + + return enumInfo; } - virtual void assertionRun() - { - m_prevPassed = m_totals.assertions.passed; + EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) { + m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values)); + return *m_enumInfos.back(); } - virtual bool sectionStarted ( - SectionInfo const& sectionInfo, - Counts& assertions - ) - { - ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( sectionInfo.name, sectionInfo.lineInfo ) ); - if( !sectionTracker.isOpen() ) - return false; - m_activeSections.push_back( §ionTracker ); + } // Detail +} // Catch - m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; +// end catch_enum_values_registry.cpp +// start catch_errno_guard.cpp - m_reporter->sectionStarting( sectionInfo ); +#include <cerrno> - assertions = m_totals.assertions; +namespace Catch { + ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} + ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } +} +// end catch_errno_guard.cpp +// start catch_exception_translator_registry.cpp - return true; +// start catch_exception_translator_registry.h + +#include <vector> +#include <string> +#include <memory> + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry(); + virtual void registerTranslator( const IExceptionTranslator* translator ); + std::string translateActiveException() const override; + std::string tryTranslators() const; + + private: + std::vector<std::unique_ptr<IExceptionTranslator const>> m_translators; + }; +} + +// end catch_exception_translator_registry.h +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { + } + + void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( std::unique_ptr<const IExceptionTranslator>( translator ) ); + } + +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + std::string ExceptionTranslatorRegistry::translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::Detail::stringify( [exception description] ); + } +#else + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if (std::current_exception() == nullptr) { + return "Non C++ exception. Possibly a CLR exception."; + } + return tryTranslators(); +#endif } - bool testForMissingAssertions( Counts& assertions ) { - if( assertions.total() != 0 ) - return false; - if( !m_config->warnAboutMissingAssertions() ) - return false; - if( m_trackerContext.currentTracker().hasChildren() ) - return false; - m_totals.assertions.failed++; - assertions.failed++; - return true; + catch( TestFailureException& ) { + std::rethrow_exception(std::current_exception()); + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + + std::string ExceptionTranslatorRegistry::tryTranslators() const { + if (m_translators.empty()) { + std::rethrow_exception(std::current_exception()); + } else { + return m_translators[0]->translate(m_translators.begin() + 1, m_translators.end()); + } + } + +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + std::string ExceptionTranslatorRegistry::translateActiveException() const { + CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); + } + + std::string ExceptionTranslatorRegistry::tryTranslators() const { + CATCH_INTERNAL_ERROR("Attempted to use exception translators under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); + } +#endif - virtual void sectionEnded( SectionEndInfo const& endInfo ) { - Counts assertions = m_totals.assertions - endInfo.prevAssertions; - bool missingAssertions = testForMissingAssertions( assertions ); +} +// end catch_exception_translator_registry.cpp +// start catch_fatal_condition.cpp + +#include <algorithm> + +#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + // If neither SEH nor signal handling is required, the handler impls + // do not have to do anything, and can be empty. + void FatalConditionHandler::engage_platform() {} + void FatalConditionHandler::disengage_platform() {} + FatalConditionHandler::FatalConditionHandler() = default; + FatalConditionHandler::~FatalConditionHandler() = default; + +} // end namespace Catch + +#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS ) +#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" +#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace { + //! Signals fatal error message to the run context + void reportFatal( char const * const message ) { + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); + } - if( !m_activeSections.empty() ) { - m_activeSections.back()->close(); - m_activeSections.pop_back(); + //! Minimal size Catch2 needs for its own fatal error handling. + //! Picked anecdotally, so it might not be sufficient on all + //! platforms, and for all configurations. + constexpr std::size_t minStackSizeForErrors = 32 * 1024; +} // end unnamed namespace + +#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + + struct SignalDefs { DWORD id; const char* name; }; + + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + static SignalDefs signalDefs[] = { + { static_cast<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION), "SIGILL - Illegal instruction signal" }, + { static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow" }, + { static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION), "SIGSEGV - Segmentation violation signal" }, + { static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, + }; + + static LONG CALLBACK handleVectoredException(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. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } - m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); - m_messages.clear(); + // Since we do not support multiple instantiations, we put these + // into global variables and rely on cleaning them up in outlined + // constructors/destructors + static PVOID exceptionHandlerHandle = nullptr; + + // For MSVC, we reserve part of the stack memory for handling + // memory overflow structured exception. + FatalConditionHandler::FatalConditionHandler() { + ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors); + if (!SetThreadStackGuarantee(&guaranteeSize)) { + // We do not want to fully error out, because needing + // the stack reserve should be rare enough anyway. + Catch::cerr() + << "Failed to reserve piece of stack." + << " Stack overflows will not be reported successfully."; } + } - virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { - if( m_unfinishedSections.empty() ) - m_activeSections.back()->fail(); - else - m_activeSections.back()->close(); - m_activeSections.pop_back(); + // We do not attempt to unset the stack guarantee, because + // Windows does not support lowering the stack size guarantee. + FatalConditionHandler::~FatalConditionHandler() = default; - m_unfinishedSections.push_back( endInfo ); + void FatalConditionHandler::engage_platform() { + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + if (!exceptionHandlerHandle) { + CATCH_RUNTIME_ERROR("Could not register vectored exception handler"); } + } - virtual void pushScopedMessage( MessageInfo const& message ) { - m_messages.push_back( message ); + void FatalConditionHandler::disengage_platform() { + if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) { + CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler"); } + exceptionHandlerHandle = nullptr; + } - virtual void popScopedMessage( MessageInfo const& message ) { - m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); - } +} // end namespace Catch + +#endif // CATCH_CONFIG_WINDOWS_SEH + +#if defined( CATCH_CONFIG_POSIX_SIGNALS ) + +#include <signal.h> + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + + static SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + +// Older GCCs trigger -Wmissing-field-initializers for T foo = {} +// which is zero initialization, but not explicit. We want to avoid +// that. +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif - virtual std::string getCurrentTestName() const { - return m_activeTestCase - ? m_activeTestCase->getTestCaseInfo().name - : std::string(); + static char* altStackMem = nullptr; + static std::size_t altStackSize = 0; + static stack_t oldSigStack{}; + static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; + + static void restorePreviousSignalHandlers() { + // We set signal handlers back to the previous ones. Hopefully + // nobody overwrote them in the meantime, and doesn't expect + // their signal handlers to live past ours given that they + // installed them after ours.. + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + } + + static void handleSignal( int sig ) { + char const * name = "<unknown signal>"; + for (auto const& def : signalDefs) { + if (sig == def.id) { + name = def.name; + break; + } } + // We need to restore previous signal handlers and let them do + // their thing, so that the users can have the debugger break + // when a signal is raised, and so on. + restorePreviousSignalHandlers(); + reportFatal( name ); + raise( sig ); + } - virtual const AssertionResult* getLastResult() const { - return &m_lastResult; + FatalConditionHandler::FatalConditionHandler() { + assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); + if (altStackSize == 0) { + altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors); } + altStackMem = new char[altStackSize](); + } + + FatalConditionHandler::~FatalConditionHandler() { + delete[] altStackMem; + // We signal that another instance can be constructed by zeroing + // out the pointer. + altStackMem = nullptr; + } + + void FatalConditionHandler::engage_platform() { + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = altStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { }; - virtual void exceptionEarlyReported() { - m_shouldReportUnexpected = false; + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); } + } - virtual void handleFatalErrorCondition( std::string const& message ) { - // Don't rebuild the result -- the stringification itself can cause more fatal errors - // Instead, fake a result data. - AssertionResultData tempResult; - tempResult.resultType = ResultWas::FatalErrorCondition; - tempResult.message = message; - AssertionResult result(m_lastAssertionInfo, tempResult); +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif - getResultCapture().assertionEnded(result); + void FatalConditionHandler::disengage_platform() { + restorePreviousSignalHandlers(); + } - handleUnfinishedSections(); +} // end namespace Catch - // Recreate section for test case (as we will lose the one that was in scope) - TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); +#endif // CATCH_CONFIG_POSIX_SIGNALS +// end catch_fatal_condition.cpp +// start catch_generators.cpp - Counts assertions; - assertions.failed = 1; - SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); - m_reporter->sectionEnded( testCaseSectionStats ); +#include <limits> +#include <set> + +namespace Catch { - TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); +IGeneratorTracker::~IGeneratorTracker() {} - Totals deltaTotals; - deltaTotals.testCases.failed = 1; - deltaTotals.assertions.failed = 1; - m_reporter->testCaseEnded( TestCaseStats( testInfo, - deltaTotals, - std::string(), - std::string(), - false ) ); - m_totals.testCases.failed++; - testGroupEnded( std::string(), m_totals, 1, 1 ); - m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); - } +const char* GeneratorException::what() const noexcept { + return m_msg; +} + +namespace Generators { + + GeneratorUntypedBase::~GeneratorUntypedBase() {} + + auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + return getResultCapture().acquireGeneratorTracker( generatorName, lineInfo ); + } + +} // namespace Generators +} // namespace Catch +// end catch_generators.cpp +// start catch_interfaces_capture.cpp + +namespace Catch { + IResultCapture::~IResultCapture() = default; +} +// end catch_interfaces_capture.cpp +// start catch_interfaces_config.cpp + +namespace Catch { + IConfig::~IConfig() = default; +} +// end catch_interfaces_config.cpp +// start catch_interfaces_exception.cpp + +namespace Catch { + IExceptionTranslator::~IExceptionTranslator() = default; + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} +// end catch_interfaces_exception.cpp +// start catch_interfaces_registry_hub.cpp + +namespace Catch { + IRegistryHub::~IRegistryHub() = default; + IMutableRegistryHub::~IMutableRegistryHub() = default; +} +// end catch_interfaces_registry_hub.cpp +// start catch_interfaces_reporter.cpp + +// start catch_reporter_listening.h + +namespace Catch { + + class ListeningReporter : public IStreamingReporter { + using Reporters = std::vector<IStreamingReporterPtr>; + Reporters m_listeners; + IStreamingReporterPtr m_reporter = nullptr; + ReporterPreferences m_preferences; public: - // !TBD We need to do this another way! - bool aborting() const { - return m_totals.assertions.failed == static_cast<std::size_t>( m_config->abortAfter() ); + ListeningReporter(); + + void addListener( IStreamingReporterPtr&& listener ); + void addReporter( IStreamingReporterPtr&& reporter ); + + public: // IStreamingReporter + + ReporterPreferences getPreferences() const override; + + void noMatchingTestCases( std::string const& spec ) override; + + void reportInvalidArguments(std::string const&arg) override; + + static std::set<Verbosity> getSupportedVerbosities(); + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void benchmarkPreparing(std::string const& name) override; + void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; + void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override; + void benchmarkFailed(std::string const&) override; +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + + void testRunStarting( TestRunInfo const& testRunInfo ) override; + void testGroupStarting( GroupInfo const& groupInfo ) override; + void testCaseStarting( TestCaseInfo const& testInfo ) override; + void sectionStarting( SectionInfo const& sectionInfo ) override; + void assertionStarting( AssertionInfo const& assertionInfo ) override; + + // The return value indicates if the messages buffer should be cleared: + bool assertionEnded( AssertionStats const& assertionStats ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + void testCaseEnded( TestCaseStats const& testCaseStats ) override; + void testGroupEnded( TestGroupStats const& testGroupStats ) override; + void testRunEnded( TestRunStats const& testRunStats ) override; + + void skipTest( TestCaseInfo const& testInfo ) override; + bool isMulti() const override; + + }; + +} // end namespace Catch + +// end catch_reporter_listening.h +namespace Catch { + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& ReporterConfig::stream() const { return *m_stream; } + IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; } + + TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {} + + GroupInfo::GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + AssertionStats::AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; + + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); } + } - private: + AssertionStats::~AssertionStats() = default; - void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { - TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); - m_reporter->sectionStarting( testCaseSection ); - Counts prevAssertions = m_totals.assertions; - double duration = 0; - m_shouldReportUnexpected = true; - try { - m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); + SectionStats::SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} - seedRng( *m_config ); + SectionStats::~SectionStats() = default; + + TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} - Timer timer; - timer.start(); - if( m_reporter->getPreferences().shouldRedirectStdOut ) { - StreamRedirect coutRedir( Catch::cout(), redirectedCout ); - StdErrRedirect errRedir( redirectedCerr ); - invokeActiveTestCase(); - } - else { - invokeActiveTestCase(); - } - duration = timer.getElapsedSeconds(); - } - catch( TestFailureException& ) { - // This just means the test was aborted due to failure - } - catch(...) { - // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions - // are reported without translation at the point of origin. - if (m_shouldReportUnexpected) { - makeUnexpectedResultBuilder().useActiveException(); - } + TestCaseStats::~TestCaseStats() = default; + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + + TestGroupStats::~TestGroupStats() = default; + + TestRunStats::TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + TestRunStats::~TestRunStats() = default; + + void IStreamingReporter::fatalErrorEncountered( StringRef ) {} + bool IStreamingReporter::isMulti() const { return false; } + + IReporterFactory::~IReporterFactory() = default; + IReporterRegistry::~IReporterRegistry() = default; + +} // end namespace Catch +// end catch_interfaces_reporter.cpp +// start catch_interfaces_runner.cpp + +namespace Catch { + IRunner::~IRunner() = default; +} +// end catch_interfaces_runner.cpp +// start catch_interfaces_testcase.cpp + +namespace Catch { + ITestInvoker::~ITestInvoker() = default; + ITestCaseRegistry::~ITestCaseRegistry() = default; +} +// end catch_interfaces_testcase.cpp +// start catch_leak_detector.cpp + +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include <crtdbg.h> + +namespace Catch { + + LeakDetector::LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +} + +#else + + Catch::LeakDetector::LeakDetector() {} + +#endif + +Catch::LeakDetector::~LeakDetector() { + Catch::cleanUp(); +} +// end catch_leak_detector.cpp +// start catch_list.cpp + +// start catch_list.h + +#include <set> + +namespace Catch { + + std::size_t listTests( Config const& config ); + + std::size_t listTestsNamesOnly( Config const& config ); + + struct TagInfo { + void add( std::string const& spelling ); + std::string all() const; + + std::set<std::string> spellings; + std::size_t count = 0; + }; + + std::size_t listTags( Config const& config ); + + std::size_t listReporters(); + + Option<std::size_t> list( std::shared_ptr<Config> const& config ); + +} // end namespace Catch + +// end catch_list.h +// start catch_text.h + +namespace Catch { + using namespace clara::TextFlow; +} + +// end catch_text.h +#include <limits> +#include <algorithm> +#include <iomanip> + +namespace Catch { + + std::size_t listTests( Config const& config ) { + TestSpec const& testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + } + + auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n"; + if( config.verbosity() >= Verbosity::High ) { + Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl; + std::string description = testCaseInfo.description; + if( description.empty() ) + description = "(NO DESCRIPTION)"; + Catch::cout() << Column( description ).indent(4) << std::endl; } - m_testCaseTracker->close(); - handleUnfinishedSections(); - m_messages.clear(); + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n"; + } + + if( !config.hasTestFilters() ) + Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl; + else + Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl; + return matchedTestCases.size(); + } - Counts assertions = m_totals.assertions - prevAssertions; - bool missingAssertions = testForMissingAssertions( assertions ); + std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec const& testSpec = config.testSpec(); + std::size_t matchedTests = 0; + std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + matchedTests++; + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if ( config.verbosity() >= Verbosity::High ) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; + } + return matchedTests; + } + + void TagInfo::add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + + std::string TagInfo::all() const { + size_t size = 0; + for (auto const& spelling : spellings) { + // Add 2 for the brackes + size += spelling.size() + 2; + } - SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); - m_reporter->sectionEnded( testCaseSectionStats ); + std::string out; out.reserve(size); + for (auto const& spelling : spellings) { + out += '['; + out += spelling; + out += ']'; } + return out; + } - void invokeActiveTestCase() { - FatalConditionHandler fatalConditionHandler; // Handle signals - m_activeTestCase->invoke(); - fatalConditionHandler.reset(); + std::size_t listTags( Config const& config ) { + TestSpec const& testSpec = config.testSpec(); + if( config.hasTestFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; } - private: + std::map<std::string, TagInfo> tagCounts; - ResultBuilder makeUnexpectedResultBuilder() const { - return ResultBuilder( m_lastAssertionInfo.macroName, - m_lastAssertionInfo.lineInfo, - m_lastAssertionInfo.capturedExpression, - m_lastAssertionInfo.resultDisposition ); + std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCase : matchedTestCases ) { + for( auto const& tagName : testCase.getTestCaseInfo().tags ) { + std::string lcaseTagName = toLower( tagName ); + auto countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } } - void handleUnfinishedSections() { - // If sections ended prematurely due to an exception we stored their - // infos here so we can tear them down outside the unwind process. - for( std::vector<SectionEndInfo>::const_reverse_iterator it = m_unfinishedSections.rbegin(), - itEnd = m_unfinishedSections.rend(); - it != itEnd; - ++it ) - sectionEnded( *it ); - m_unfinishedSections.clear(); + for( auto const& tagCount : tagCounts ) { + ReusableStringStream rss; + rss << " " << std::setw(2) << tagCount.second.count << " "; + auto str = rss.str(); + auto wrapper = Column( tagCount.second.all() ) + .initialIndent( 0 ) + .indent( str.size() ) + .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); + Catch::cout() << str << wrapper << '\n'; } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; + return tagCounts.size(); + } - TestRunInfo m_runInfo; - IMutableContext& m_context; - TestCase const* m_activeTestCase; - ITracker* m_testCaseTracker; - ITracker* m_currentSectionTracker; - AssertionResult m_lastResult; + std::size_t listReporters() { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + std::size_t maxNameLen = 0; + for( auto const& factoryKvp : factories ) + maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() ); - Ptr<IConfig const> m_config; - Totals m_totals; - Ptr<IStreamingReporter> m_reporter; - std::vector<MessageInfo> m_messages; - AssertionInfo m_lastAssertionInfo; - std::vector<SectionEndInfo> m_unfinishedSections; - std::vector<ITracker*> m_activeSections; - TrackerContext m_trackerContext; - size_t m_prevPassed; - bool m_shouldReportUnexpected; - }; + for( auto const& factoryKvp : factories ) { + Catch::cout() + << Column( factoryKvp.first + ":" ) + .indent(2) + .width( 5+maxNameLen ) + + Column( factoryKvp.second->getDescription() ) + .initialIndent(0) + .indent(2) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) + << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } - IResultCapture& getResultCapture() { - if( IResultCapture* capture = getCurrentContext().getResultCapture() ) - return *capture; - else - throw std::logic_error( "No result capture instance" ); + Option<std::size_t> list( std::shared_ptr<Config> const& config ) { + Option<std::size_t> listedCount; + getCurrentMutableContext().setConfig( config ); + if( config->listTests() ) + listedCount = listedCount.valueOr(0) + listTests( *config ); + if( config->listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( *config ); + if( config->listTags() ) + listedCount = listedCount.valueOr(0) + listTags( *config ); + if( config->listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters(); + return listedCount; } } // end namespace Catch +// end catch_list.cpp +// start catch_matchers.cpp + +namespace Catch { +namespace Matchers { + namespace Impl { + + std::string MatcherUntypedBase::toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } -// #included from: internal/catch_version.h -#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + MatcherUntypedBase::~MatcherUntypedBase() = default; + + } // namespace Impl +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch +// end catch_matchers.cpp +// start catch_matchers_exception.cpp namespace Catch { +namespace Matchers { +namespace Exception { - // Versioning information - struct Version { - Version( unsigned int _majorVersion, - unsigned int _minorVersion, - unsigned int _patchNumber, - char const * const _branchName, - unsigned int _buildNumber ); +bool ExceptionMessageMatcher::match(std::exception const& ex) const { + return ex.what() == m_message; +} - unsigned int const majorVersion; - unsigned int const minorVersion; - unsigned int const patchNumber; +std::string ExceptionMessageMatcher::describe() const { + return "exception message matches \"" + m_message + "\""; +} - // buildNumber is only used if branchName is not null - char const * const branchName; - unsigned int const buildNumber; +} +Exception::ExceptionMessageMatcher Message(std::string const& message) { + return Exception::ExceptionMessageMatcher(message); +} - friend std::ostream& operator << ( std::ostream& os, Version const& version ); +// namespace Exception +} // namespace Matchers +} // namespace Catch +// end catch_matchers_exception.cpp +// start catch_matchers_floating.cpp - private: - void operator=( Version const& ); - }; +// start catch_polyfills.hpp - inline Version libraryVersion(); +namespace Catch { + bool isnan(float f); + bool isnan(double d); } -#include <fstream> -#include <stdlib.h> +// end catch_polyfills.hpp +// start catch_to_string.hpp + +#include <string> + +namespace Catch { + template <typename T> + std::string to_string(T const& t) { +#if defined(CATCH_CONFIG_CPP11_TO_STRING) + return std::to_string(t); +#else + ReusableStringStream rss; + rss << t; + return rss.str(); +#endif + } +} // end namespace Catch + +// end catch_to_string.hpp +#include <algorithm> +#include <cmath> +#include <cstdlib> +#include <cstdint> +#include <cstring> +#include <sstream> +#include <type_traits> +#include <iomanip> #include <limits> namespace Catch { +namespace { - Ptr<IStreamingReporter> createReporter( std::string const& reporterName, Ptr<Config> const& config ) { - Ptr<IStreamingReporter> reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); - if( !reporter ) { - std::ostringstream oss; - oss << "No reporter registered with name: '" << reporterName << "'"; - throw std::domain_error( oss.str() ); + int32_t convert(float f) { + static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); + int32_t i; + std::memcpy(&i, &f, sizeof(f)); + return i; + } + + int64_t convert(double d) { + static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); + int64_t i; + std::memcpy(&i, &d, sizeof(d)); + return i; + } + + template <typename FP> + bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (Catch::isnan(lhs) || Catch::isnan(rhs)) { + return false; } - return reporter; + + auto lc = convert(lhs); + auto rc = convert(rhs); + + if ((lc < 0) != (rc < 0)) { + // Potentially we can have +0 and -0 + return lhs == rhs; + } + + // static cast as a workaround for IBM XLC + auto ulpDiff = std::abs(static_cast<FP>(lc - rc)); + return static_cast<uint64_t>(ulpDiff) <= maxUlpDiff; } -#if !defined(CATCH_CONFIG_DEFAULT_REPORTER) -#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) + + float nextafter(float x, float y) { + return ::nextafterf(x, y); + } + + double nextafter(double x, double y) { + return ::nextafter(x, y); + } + +#endif // ^^^ CATCH_CONFIG_GLOBAL_NEXTAFTER ^^^ + +template <typename FP> +FP step(FP start, FP direction, uint64_t steps) { + for (uint64_t i = 0; i < steps; ++i) { +#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) + start = Catch::nextafter(start, direction); +#else + start = std::nextafter(start, direction); #endif + } + return start; +} + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +template <typename FloatingPoint> +void write(std::ostream& out, FloatingPoint num) { + out << std::scientific + << std::setprecision(std::numeric_limits<FloatingPoint>::max_digits10 - 1) + << num; +} + +} // end anonymous namespace - Ptr<IStreamingReporter> makeReporter( Ptr<Config> const& config ) { - std::vector<std::string> reporters = config->getReporterNames(); - if( reporters.empty() ) - reporters.push_back( CATCH_CONFIG_DEFAULT_REPORTER ); +namespace Matchers { +namespace Floating { + + enum class FloatingPointKind : uint8_t { + Float, + Double + }; - Ptr<IStreamingReporter> reporter; - for( std::vector<std::string>::const_iterator it = reporters.begin(), itEnd = reporters.end(); - it != itEnd; - ++it ) - reporter = addReporter( reporter, createReporter( *it, config ) ); - return reporter; + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + :m_target{ target }, m_margin{ margin } { + CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' + << " Margin has to be non-negative."); } - Ptr<IStreamingReporter> addListeners( Ptr<IConfig const> const& config, Ptr<IStreamingReporter> reporters ) { - IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); - for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); - it != itEnd; - ++it ) - reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); - return reporters; + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool WithinAbsMatcher::match(double const& matchee) const { + return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); } - Totals runTests( Ptr<Config> const& config ) { + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } - Ptr<IConfig const> iconfig = config.get(); + WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType) + :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { + CATCH_ENFORCE(m_type == FloatingPointKind::Double + || m_ulps < (std::numeric_limits<uint32_t>::max)(), + "Provided ULP is impossibly large for a float comparison."); + } - Ptr<IStreamingReporter> reporter = makeReporter( config ); - reporter = addListeners( iconfig, reporter ); +#if defined(__clang__) +#pragma clang diagnostic push +// Clang <3.5 reports on the default branch in the switch below +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif - RunContext context( iconfig, reporter ); + bool WithinUlpsMatcher::match(double const& matchee) const { + switch (m_type) { + case FloatingPointKind::Float: + return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps); + case FloatingPointKind::Double: + return almostEqualUlps<double>(matchee, m_target, m_ulps); + default: + CATCH_INTERNAL_ERROR( "Unknown FloatingPointKind value" ); + } + } - Totals totals; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif - context.testGroupStarting( config->name(), 1, 1 ); + std::string WithinUlpsMatcher::describe() const { + std::stringstream ret; - TestSpec testSpec = config->testSpec(); - if( !testSpec.hasFilters() ) - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + ret << "is within " << m_ulps << " ULPs of "; - std::vector<TestCase> const& allTestCases = getAllTestCasesSorted( *iconfig ); - for( std::vector<TestCase>::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); - it != itEnd; - ++it ) { - if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) ) - totals += context.runTest( *it ); - else - reporter->skipTest( *it ); + if (m_type == FloatingPointKind::Float) { + write(ret, static_cast<float>(m_target)); + ret << 'f'; + } else { + write(ret, m_target); } - context.testGroupEnded( iconfig->name(), totals, 1, 1 ); - return totals; + ret << " (["; + if (m_type == FloatingPointKind::Double) { + write(ret, step(m_target, static_cast<double>(-INFINITY), m_ulps)); + ret << ", "; + write(ret, step(m_target, static_cast<double>( INFINITY), m_ulps)); + } else { + // We have to cast INFINITY to float because of MinGW, see #1782 + write(ret, step(static_cast<float>(m_target), static_cast<float>(-INFINITY), m_ulps)); + ret << ", "; + write(ret, step(static_cast<float>(m_target), static_cast<float>( INFINITY), m_ulps)); + } + ret << "])"; + + return ret.str(); + } + + WithinRelMatcher::WithinRelMatcher(double target, double epsilon): + m_target(target), + m_epsilon(epsilon){ + CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon < 0 does not make sense."); + CATCH_ENFORCE(m_epsilon < 1., "Relative comparison with epsilon >= 1 does not make sense."); } - void applyFilenamesAsTags( IConfig const& config ) { - std::vector<TestCase> const& tests = getAllTestCasesSorted( config ); - for(std::size_t i = 0; i < tests.size(); ++i ) { - TestCase& test = const_cast<TestCase&>( tests[i] ); - std::set<std::string> tags = test.tags; + bool WithinRelMatcher::match(double const& matchee) const { + const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target)); + return marginComparison(matchee, m_target, + std::isinf(relMargin)? 0 : relMargin); + } - std::string filename = test.lineInfo.file; - std::string::size_type lastSlash = filename.find_last_of( "\\/" ); - if( lastSlash != std::string::npos ) - filename = filename.substr( lastSlash+1 ); + std::string WithinRelMatcher::describe() const { + Catch::ReusableStringStream sstr; + sstr << "and " << m_target << " are within " << m_epsilon * 100. << "% of each other"; + return sstr.str(); + } - std::string::size_type lastDot = filename.find_last_of( '.' ); - if( lastDot != std::string::npos ) - filename = filename.substr( 0, lastDot ); +}// namespace Floating - tags.insert( '#' + filename ); - setTags( test, tags ); - } +Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); +} + +Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) { + return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); +} + +Floating::WithinAbsMatcher WithinAbs(double target, double margin) { + return Floating::WithinAbsMatcher(target, margin); +} + +Floating::WithinRelMatcher WithinRel(double target, double eps) { + return Floating::WithinRelMatcher(target, eps); +} + +Floating::WithinRelMatcher WithinRel(double target) { + return Floating::WithinRelMatcher(target, std::numeric_limits<double>::epsilon() * 100); +} + +Floating::WithinRelMatcher WithinRel(float target, float eps) { + return Floating::WithinRelMatcher(target, eps); +} + +Floating::WithinRelMatcher WithinRel(float target) { + return Floating::WithinRelMatcher(target, std::numeric_limits<float>::epsilon() * 100); +} + +} // namespace Matchers +} // namespace Catch +// end catch_matchers_floating.cpp +// start catch_matchers_generic.cpp + +std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) { + if (desc.empty()) { + return "matches undescribed predicate"; + } else { + return "matches predicate: \"" + desc + '"'; } +} +// end catch_matchers_generic.cpp +// start catch_matchers_string.cpp - class Session : NonCopyable { - static bool alreadyInstantiated; +#include <regex> - public: +namespace Catch { +namespace Matchers { - struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + namespace StdString { - Session() - : m_cli( makeCommandLineParser() ) { - if( alreadyInstantiated ) { - std::string msg = "Only one instance of Catch::Session can ever be used"; - Catch::cerr() << msg << std::endl; - throw std::logic_error( msg ); - } - alreadyInstantiated = true; + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; } - ~Session() { - Catch::cleanUp(); + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); } - void showHelp( std::string const& processName ) { - Catch::cout() << "\nCatch v" << libraryVersion() << "\n"; - - m_cli.usage( Catch::cout(), processName ); - Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { } - void libIdentify() { - Catch::cout() - << std::left << std::setw(16) << "description: " << "A Catch test executable\n" - << std::left << std::setw(16) << "category: " << "testframework\n" - << std::left << std::setw(16) << "framework: " << "Catch Test\n" - << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; - } - - int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { - try { - m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); - m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); - if( m_configData.showHelp ) - showHelp( m_configData.processName ); - if( m_configData.libIdentify ) - libIdentify(); - m_config.reset(); - } - catch( std::exception& ex ) { - { - Colour colourGuard( Colour::Red ); - Catch::cerr() - << "\nError(s) in input:\n" - << Text( ex.what(), TextAttributes().setIndent(2) ) - << "\n\n"; - } - m_cli.usage( Catch::cout(), m_configData.processName ); - return (std::numeric_limits<int>::max)(); - } - return 0; + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; } - void useConfigData( ConfigData const& _configData ) { - m_configData = _configData; - m_config.reset(); + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; } - int run( int argc, char const* const* const argv ) { + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} - int returnCode = applyCommandLine( argc, argv ); - if( returnCode == 0 ) - returnCode = run(); - return returnCode; + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); } - #if defined(WIN32) && defined(UNICODE) - int run( int argc, wchar_t const* const* const argv ) { + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} - char **utf8Argv = new char *[ argc ]; + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } - for ( int i = 0; i < argc; ++i ) { - int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} - utf8Argv[ i ] = new char[ bufSize ]; + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} - WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::Choice::No) { + flags |= std::regex::icase; } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } - int returnCode = applyCommandLine( argc, utf8Argv ); - if( returnCode == 0 ) - returnCode = run(); + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively"); + } + + } // namespace StdString + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } - for ( int i = 0; i < argc; ++i ) - delete [] utf8Argv[ i ]; + StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) { + return StdString::RegexMatcher(regex, caseSensitivity); + } - delete [] utf8Argv; +} // namespace Matchers +} // namespace Catch +// end catch_matchers_string.cpp +// start catch_message.cpp - return returnCode; +// start catch_uncaught_exceptions.h + +namespace Catch { + bool uncaught_exceptions(); +} // end namespace Catch + +// end catch_uncaught_exceptions.h +#include <cassert> +#include <stack> + +namespace Catch { + + MessageInfo::MessageInfo( StringRef const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + bool MessageInfo::operator==( MessageInfo const& other ) const { + return sequence == other.sequence; + } + + bool MessageInfo::operator<( MessageInfo const& other ) const { + return sequence < other.sequence; + } + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + Catch::MessageBuilder::MessageBuilder( StringRef const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + :m_info(macroName, lineInfo, type) {} + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ), m_moved() + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + + ScopedMessage::ScopedMessage( ScopedMessage&& old ) + : m_info( old.m_info ), m_moved() + { + old.m_moved = true; + } + + ScopedMessage::~ScopedMessage() { + if ( !uncaught_exceptions() && !m_moved ){ + getResultCapture().popScopedMessage(m_info); } - #endif + } - int run() { - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before starting" << std::endl; - static_cast<void>(std::getchar()); + Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) { + auto trimmed = [&] (size_t start, size_t end) { + while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) { + ++start; } - int exitCode = runInternal(); - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; - static_cast<void>(std::getchar()); + while (names[end] == ',' || isspace(static_cast<unsigned char>(names[end]))) { + --end; } - return exitCode; - } + return names.substr(start, end - start + 1); + }; + auto skipq = [&] (size_t start, char quote) { + for (auto i = start + 1; i < names.size() ; ++i) { + if (names[i] == quote) + return i; + if (names[i] == '\\') + ++i; + } + CATCH_INTERNAL_ERROR("CAPTURE parsing encountered unmatched quote"); + }; - Clara::CommandLine<ConfigData> const& cli() const { - return m_cli; - } - std::vector<Clara::Parser::Token> const& unusedTokens() const { - return m_unusedTokens; - } - ConfigData& configData() { - return m_configData; + size_t start = 0; + std::stack<char> openings; + for (size_t pos = 0; pos < names.size(); ++pos) { + char c = names[pos]; + switch (c) { + case '[': + case '{': + case '(': + // It is basically impossible to disambiguate between + // comparison and start of template args in this context +// case '<': + openings.push(c); + break; + case ']': + case '}': + case ')': +// case '>': + openings.pop(); + break; + case '"': + case '\'': + pos = skipq(pos, c); + break; + case ',': + if (start != pos && openings.empty()) { + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = static_cast<std::string>(trimmed(start, pos)); + m_messages.back().message += " := "; + start = pos; + } + } } - Config& config() { - if( !m_config ) - m_config = new Config( m_configData ); - return *m_config; + assert(openings.empty() && "Mismatched openings"); + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1)); + m_messages.back().message += " := "; + } + 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] ); } - private: + } - int runInternal() { - if( m_configData.showHelp || m_configData.libIdentify ) - return 0; + 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] ); + m_captured++; + } - try - { - config(); // Force config to be constructed +} // end namespace Catch +// end catch_message.cpp +// start catch_output_redirect.cpp - seedRng( *m_config ); +// start catch_output_redirect.h +#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H - if( m_configData.filenamesAsTags ) - applyFilenamesAsTags( *m_config ); +#include <cstdio> +#include <iosfwd> +#include <string> - // Handle list request - if( Option<std::size_t> listed = list( config() ) ) - return static_cast<int>( *listed ); +namespace Catch { - return static_cast<int>( runTests( m_config ).assertions.failed ); - } - catch( std::exception& ex ) { - Catch::cerr() << ex.what() << std::endl; - return (std::numeric_limits<int>::max)(); - } - } + class RedirectedStream { + std::ostream& m_originalStream; + std::ostream& m_redirectionStream; + std::streambuf* m_prevBuf; - Clara::CommandLine<ConfigData> m_cli; - std::vector<Clara::Parser::Token> m_unusedTokens; - ConfigData m_configData; - Ptr<Config> m_config; + public: + RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ); + ~RedirectedStream(); }; - bool Session::alreadyInstantiated = false; + class RedirectedStdOut { + ReusableStringStream m_rss; + RedirectedStream m_cout; + public: + RedirectedStdOut(); + auto str() const -> std::string; + }; -} // end namespace Catch + // StdErr has two constituent streams in C++, std::cerr and std::clog + // This means that we need to redirect 2 streams into 1 to keep proper + // order of writes + class RedirectedStdErr { + ReusableStringStream m_rss; + RedirectedStream m_cerr; + RedirectedStream m_clog; + public: + RedirectedStdErr(); + auto str() const -> std::string; + }; -// #included from: catch_registry_hub.hpp -#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + class RedirectedStreams { + public: + RedirectedStreams(RedirectedStreams const&) = delete; + RedirectedStreams& operator=(RedirectedStreams const&) = delete; + RedirectedStreams(RedirectedStreams&&) = delete; + RedirectedStreams& operator=(RedirectedStreams&&) = delete; -// #included from: catch_test_case_registry_impl.hpp -#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr); + ~RedirectedStreams(); + private: + std::string& m_redirectedCout; + std::string& m_redirectedCerr; + RedirectedStdOut m_redirectedStdOut; + RedirectedStdErr m_redirectedStdErr; + }; -#include <vector> -#include <set> -#include <sstream> -#include <algorithm> +#if defined(CATCH_CONFIG_NEW_CAPTURE) -namespace Catch { + // Windows's implementation of std::tmpfile is terrible (it tries + // to create a file inside system folder, thus requiring elevated + // privileges for the binary), so we have to use tmpnam(_s) and + // create the file ourselves there. + class TempFile { + public: + TempFile(TempFile const&) = delete; + TempFile& operator=(TempFile const&) = delete; + TempFile(TempFile&&) = delete; + TempFile& operator=(TempFile&&) = delete; + + TempFile(); + ~TempFile(); - struct RandomNumberGenerator { - typedef std::ptrdiff_t result_type; + std::FILE* getFile(); + std::string getContents(); - result_type operator()( result_type n ) const { return std::rand() % n; } + private: + std::FILE* m_file = nullptr; + #if defined(_MSC_VER) + char m_buffer[L_tmpnam] = { 0 }; + #endif + }; + + class OutputRedirect { + public: + OutputRedirect(OutputRedirect const&) = delete; + OutputRedirect& operator=(OutputRedirect const&) = delete; + OutputRedirect(OutputRedirect&&) = delete; + OutputRedirect& operator=(OutputRedirect&&) = delete; + + OutputRedirect(std::string& stdout_dest, std::string& stderr_dest); + ~OutputRedirect(); + + private: + int m_originalStdout = -1; + int m_originalStderr = -1; + TempFile m_stdoutFile; + TempFile m_stderrFile; + std::string& m_stdoutDest; + std::string& m_stderrDest; + }; -#ifdef CATCH_CONFIG_CPP11_SHUFFLE - static constexpr result_type min() { return 0; } - static constexpr result_type max() { return 1000000; } - result_type operator()() const { return std::rand() % max(); } #endif - template<typename V> - static void shuffle( V& vector ) { - RandomNumberGenerator rng; -#ifdef CATCH_CONFIG_CPP11_SHUFFLE - std::shuffle( vector.begin(), vector.end(), rng ); -#else - std::random_shuffle( vector.begin(), vector.end(), rng ); + +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H +// end catch_output_redirect.h +#include <cstdio> +#include <cstring> +#include <fstream> +#include <sstream> +#include <stdexcept> + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #include <io.h> //_dup and _dup2 + #define dup _dup + #define dup2 _dup2 + #define fileno _fileno + #else + #include <unistd.h> // dup and dup2 + #endif #endif - } - }; - inline std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) { +namespace Catch { - std::vector<TestCase> sorted = unsortedTestCases; + RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) + : m_originalStream( originalStream ), + m_redirectionStream( redirectionStream ), + m_prevBuf( m_originalStream.rdbuf() ) + { + m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); + } - switch( config.runOrder() ) { - case RunTests::InLexicographicalOrder: - std::sort( sorted.begin(), sorted.end() ); - break; - case RunTests::InRandomOrder: - { - seedRng( config ); - RandomNumberGenerator::shuffle( sorted ); - } - break; - case RunTests::InDeclarationOrder: - // already in declaration order - break; + RedirectedStream::~RedirectedStream() { + m_originalStream.rdbuf( m_prevBuf ); + } + + RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} + auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } + + RedirectedStdErr::RedirectedStdErr() + : m_cerr( Catch::cerr(), m_rss.get() ), + m_clog( Catch::clog(), m_rss.get() ) + {} + auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } + + RedirectedStreams::RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr) + : m_redirectedCout(redirectedCout), + m_redirectedCerr(redirectedCerr) + {} + + RedirectedStreams::~RedirectedStreams() { + m_redirectedCout += m_redirectedStdOut.str(); + m_redirectedCerr += m_redirectedStdErr.str(); + } + +#if defined(CATCH_CONFIG_NEW_CAPTURE) + +#if defined(_MSC_VER) + TempFile::TempFile() { + if (tmpnam_s(m_buffer)) { + CATCH_RUNTIME_ERROR("Could not get a temp filename"); + } + if (fopen_s(&m_file, m_buffer, "w+")) { + char buffer[100]; + if (strerror_s(buffer, errno)) { + CATCH_RUNTIME_ERROR("Could not translate errno to a string"); + } + CATCH_RUNTIME_ERROR("Could not open the temp file: '" << m_buffer << "' because: " << buffer); } - return sorted; } - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { - return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); +#else + TempFile::TempFile() { + m_file = std::tmpfile(); + if (!m_file) { + CATCH_RUNTIME_ERROR("Could not create a temp file."); + } } - void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) { - std::set<TestCase> seenFunctions; - for( std::vector<TestCase>::const_iterator it = functions.begin(), itEnd = functions.end(); - it != itEnd; - ++it ) { - std::pair<std::set<TestCase>::const_iterator, bool> prev = seenFunctions.insert( *it ); - if( !prev.second ) { - std::ostringstream ss; +#endif - ss << Colour( Colour::Red ) - << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" - << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << '\n' - << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; + TempFile::~TempFile() { + // TBD: What to do about errors here? + std::fclose(m_file); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +#if defined(_MSC_VER) + std::remove(m_buffer); +#endif + } - throw std::runtime_error(ss.str()); - } + FILE* TempFile::getFile() { + return m_file; + } + + std::string TempFile::getContents() { + std::stringstream sstr; + char buffer[100] = {}; + std::rewind(m_file); + while (std::fgets(buffer, sizeof(buffer), m_file)) { + sstr << buffer; } + return sstr.str(); } - std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) { - std::vector<TestCase> filtered; - filtered.reserve( testCases.size() ); - for( std::vector<TestCase>::const_iterator it = testCases.begin(), itEnd = testCases.end(); - it != itEnd; - ++it ) - if( matchTest( *it, testSpec, config ) ) - filtered.push_back( *it ); - return filtered; + OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) : + m_originalStdout(dup(1)), + m_originalStderr(dup(2)), + m_stdoutDest(stdout_dest), + m_stderrDest(stderr_dest) { + dup2(fileno(m_stdoutFile.getFile()), 1); + dup2(fileno(m_stderrFile.getFile()), 2); } - std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) { - return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + + OutputRedirect::~OutputRedirect() { + Catch::cout() << std::flush; + fflush(stdout); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush(stderr); + + dup2(m_originalStdout, 1); + dup2(m_originalStderr, 2); + + m_stdoutDest += m_stdoutFile.getContents(); + m_stderrDest += m_stderrFile.getContents(); } - class TestRegistry : public ITestCaseRegistry { - public: - TestRegistry() - : m_currentSortOrder( RunTests::InDeclarationOrder ), - m_unnamedCount( 0 ) - {} - virtual ~TestRegistry(); +#endif // CATCH_CONFIG_NEW_CAPTURE - virtual void registerTest( TestCase const& testCase ) { - std::string name = testCase.getTestCaseInfo().name; - if( name.empty() ) { - std::ostringstream oss; - oss << "Anonymous test case " << ++m_unnamedCount; - return registerTest( testCase.withName( oss.str() ) ); - } - m_functions.push_back( testCase ); - } +} // namespace Catch - virtual std::vector<TestCase> const& getAllTests() const { - return m_functions; - } - virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const { - if( m_sortedFunctions.empty() ) - enforceNoDuplicateTestCases( m_functions ); +#if defined(CATCH_CONFIG_NEW_CAPTURE) + #if defined(_MSC_VER) + #undef dup + #undef dup2 + #undef fileno + #endif +#endif +// end catch_output_redirect.cpp +// start catch_polyfills.cpp - if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { - m_sortedFunctions = sortTests( config, m_functions ); - m_currentSortOrder = config.runOrder(); - } - return m_sortedFunctions; - } +#include <cmath> - private: - std::vector<TestCase> m_functions; - mutable RunTests::InWhatOrder m_currentSortOrder; - mutable std::vector<TestCase> m_sortedFunctions; - size_t m_unnamedCount; - std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised - }; +namespace Catch { - /////////////////////////////////////////////////////////////////////////// +#if !defined(CATCH_CONFIG_POLYFILL_ISNAN) + bool isnan(float f) { + return std::isnan(f); + } + bool isnan(double d) { + return std::isnan(d); + } +#else + // For now we only use this for embarcadero + bool isnan(float f) { + return std::_isnan(f); + } + bool isnan(double d) { + return std::_isnan(d); + } +#endif - class FreeFunctionTestCase : public SharedImpl<ITestCase> { - public: +} // end namespace Catch +// end catch_polyfills.cpp +// start catch_random_number_generator.cpp - FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} +namespace Catch { - virtual void invoke() const { - m_fun(); +namespace { + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4146) // we negate uint32 during the rotate +#endif + // Safe rotr implementation thanks to John Regehr + uint32_t rotate_right(uint32_t val, uint32_t count) { + const uint32_t mask = 31; + count &= mask; + return (val >> count) | (val << (-count & mask)); } - private: - virtual ~FreeFunctionTestCase(); +#if defined(_MSC_VER) +#pragma warning(pop) +#endif - TestFunction m_fun; - }; +} - inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { - std::string className = classOrQualifiedMethodName; - if( startsWith( className, '&' ) ) - { - std::size_t lastColons = className.rfind( "::" ); - std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); - if( penultimateColons == std::string::npos ) - penultimateColons = 1; - className = className.substr( penultimateColons, lastColons-penultimateColons ); + SimplePcg32::SimplePcg32(result_type seed_) { + seed(seed_); + } + + void SimplePcg32::seed(result_type seed_) { + m_state = 0; + (*this)(); + m_state += seed_; + (*this)(); + } + + void SimplePcg32::discard(uint64_t skip) { + // We could implement this to run in O(log n) steps, but this + // should suffice for our use case. + for (uint64_t s = 0; s < skip; ++s) { + static_cast<void>((*this)()); } - return className; } - void registerTestCase - ( ITestCase* testCase, - char const* classOrQualifiedMethodName, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ) { + SimplePcg32::result_type SimplePcg32::operator()() { + // prepare the output value + const uint32_t xorshifted = static_cast<uint32_t>(((m_state >> 18u) ^ m_state) >> 27u); + const auto output = rotate_right(xorshifted, m_state >> 59u); - getMutableRegistryHub().registerTest - ( makeTestCase - ( testCase, - extractClassName( classOrQualifiedMethodName ), - nameAndDesc.name, - nameAndDesc.description, - lineInfo ) ); + // advance state + m_state = m_state * 6364136223846793005ULL + s_inc; + + return output; } - void registerTestCaseFunction - ( TestFunction function, - SourceLineInfo const& lineInfo, - NameAndDesc const& nameAndDesc ) { - registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); + + bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { + return lhs.m_state == rhs.m_state; } + bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { + return lhs.m_state != rhs.m_state; + } +} +// end catch_random_number_generator.cpp +// start catch_registry_hub.cpp + +// start catch_test_case_registry_impl.h + +#include <vector> +#include <set> +#include <algorithm> +#include <ios> + +namespace Catch { + + class TestCase; + struct IConfig; + + std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ); + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + + void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ); + + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); + + class TestRegistry : public ITestCaseRegistry { + public: + virtual ~TestRegistry() = default; + + virtual void registerTest( TestCase const& testCase ); + + std::vector<TestCase> const& getAllTests() const override; + std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const override; + + private: + std::vector<TestCase> m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; + mutable std::vector<TestCase> m_sortedFunctions; + std::size_t m_unnamedCount = 0; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + /////////////////////////////////////////////////////////////////////////// - AutoReg::AutoReg - ( TestFunction function, - SourceLineInfo const& lineInfo, - NameAndDesc const& nameAndDesc ) { - registerTestCaseFunction( function, lineInfo, nameAndDesc ); - } + class TestInvokerAsFunction : public ITestInvoker { + void(*m_testAsFunction)(); + public: + TestInvokerAsFunction( void(*testAsFunction)() ) noexcept; + + void invoke() const override; + }; - AutoReg::~AutoReg() {} + std::string extractClassName( StringRef const& classOrQualifiedMethodName ); + + /////////////////////////////////////////////////////////////////////////// } // end namespace Catch -// #included from: catch_reporter_registry.hpp -#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED +// end catch_test_case_registry_impl.h +// start catch_reporter_registry.h #include <map> @@ -7407,28 +12332,15 @@ namespace Catch { public: - virtual ~ReporterRegistry() CATCH_OVERRIDE {} + ~ReporterRegistry() override; - virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig const> const& config ) const CATCH_OVERRIDE { - FactoryMap::const_iterator it = m_factories.find( name ); - if( it == m_factories.end() ) - return CATCH_NULL; - return it->second->create( ReporterConfig( config ) ); - } + IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override; - void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) { - m_factories.insert( std::make_pair( name, factory ) ); - } - void registerListener( Ptr<IReporterFactory> const& factory ) { - m_listeners.push_back( factory ); - } + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ); + void registerListener( IReporterFactoryPtr const& factory ); - virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { - return m_factories; - } - virtual Listeners const& getListeners() const CATCH_OVERRIDE { - return m_listeners; - } + FactoryMap const& getFactories() const override; + Listeners const& getListeners() const override; private: FactoryMap m_factories; @@ -7436,80 +12348,34 @@ namespace Catch { }; } -// #included from: catch_exception_translator_registry.hpp -#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED +// end catch_reporter_registry.h +// start catch_tag_alias_registry.h -#ifdef __OBJC__ -#import "Foundation/Foundation.h" -#endif - -namespace Catch { - - class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { - public: - ~ExceptionTranslatorRegistry() { - deleteAll( m_translators ); - } +// start catch_tag_alias.h - virtual void registerTranslator( const IExceptionTranslator* translator ) { - m_translators.push_back( translator ); - } +#include <string> - virtual std::string translateActiveException() const { - try { -#ifdef __OBJC__ - // In Objective-C try objective-c exceptions first - @try { - return tryTranslators(); - } - @catch (NSException *exception) { - return Catch::toString( [exception description] ); - } -#else - return tryTranslators(); -#endif - } - catch( TestFailureException& ) { - throw; - } - catch( std::exception& ex ) { - return ex.what(); - } - catch( std::string& msg ) { - return msg; - } - catch( const char* msg ) { - return msg; - } - catch(...) { - return "Unknown exception"; - } - } +namespace Catch { - std::string tryTranslators() const { - if( m_translators.empty() ) - throw; - else - return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); - } + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo); - private: - std::vector<const IExceptionTranslator*> m_translators; + std::string tag; + SourceLineInfo lineInfo; }; -} -// #included from: catch_tag_alias_registry.h -#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED +} // end namespace Catch +// end catch_tag_alias.h #include <map> namespace Catch { class TagAliasRegistry : public ITagAliasRegistry { public: - virtual ~TagAliasRegistry(); - virtual Option<TagAlias> find( std::string const& alias ) const; - virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + ~TagAliasRegistry() override; + TagAlias const* find( std::string const& alias ) const override; + std::string expandAliases( std::string const& unexpandedTestSpec ) const override; void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); private: @@ -7518,73 +12384,134 @@ namespace Catch { } // end namespace Catch +// end catch_tag_alias_registry.h +// start catch_startup_exception_registry.h + +#include <vector> +#include <exception> + namespace Catch { - namespace { + class StartupExceptionRegistry { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + public: + void add(std::exception_ptr const& exception) noexcept; + std::vector<std::exception_ptr> const& getExceptions() const noexcept; + private: + std::vector<std::exception_ptr> m_exceptions; +#endif + }; - class RegistryHub : public IRegistryHub, public IMutableRegistryHub { +} // end namespace Catch - RegistryHub( RegistryHub const& ); - void operator=( RegistryHub const& ); +// end catch_startup_exception_registry.h +// start catch_singletons.hpp - public: // IRegistryHub - RegistryHub() { +namespace Catch { + + struct ISingleton { + virtual ~ISingleton(); + }; + + void addSingleton( ISingleton* singleton ); + void cleanupSingletons(); + + template<typename SingletonImplT, typename InterfaceT = SingletonImplT, typename MutableInterfaceT = InterfaceT> + class Singleton : SingletonImplT, public ISingleton { + + static auto getInternal() -> Singleton* { + static Singleton* s_instance = nullptr; + if( !s_instance ) { + s_instance = new Singleton; + addSingleton( s_instance ); } - virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { + return s_instance; + } + + public: + static auto get() -> InterfaceT const& { + return *getInternal(); + } + static auto getMutable() -> MutableInterfaceT& { + return *getInternal(); + } + }; + +} // namespace Catch + +// end catch_singletons.hpp +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub, + private NonCopyable { + + public: // IRegistryHub + RegistryHub() = default; + IReporterRegistry const& getReporterRegistry() const override { return m_reporterRegistry; } - virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { + ITestCaseRegistry const& getTestCaseRegistry() const override { return m_testCaseRegistry; } - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { + IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override { return m_exceptionTranslatorRegistry; } - virtual ITagAliasRegistry const& getTagAliasRegistry() const CATCH_OVERRIDE { + ITagAliasRegistry const& getTagAliasRegistry() const override { return m_tagAliasRegistry; } + StartupExceptionRegistry const& getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } public: // IMutableRegistryHub - virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE { + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { m_reporterRegistry.registerReporter( name, factory ); } - virtual void registerListener( Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE { + void registerListener( IReporterFactoryPtr const& factory ) override { m_reporterRegistry.registerListener( factory ); } - virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { + void registerTest( TestCase const& testInfo ) override { m_testCaseRegistry.registerTest( testInfo ); } - virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { + void registerTranslator( const IExceptionTranslator* translator ) override { m_exceptionTranslatorRegistry.registerTranslator( translator ); } - virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) CATCH_OVERRIDE { + void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { m_tagAliasRegistry.add( alias, tag, lineInfo ); } + void registerStartupException() noexcept override { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + m_exceptionRegistry.add(std::current_exception()); +#else + CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); +#endif + } + IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override { + return m_enumValuesRegistry; + } private: TestRegistry m_testCaseRegistry; ReporterRegistry m_reporterRegistry; ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; + Detail::EnumValuesRegistry m_enumValuesRegistry; }; - - // Single, global, instance - inline RegistryHub*& getTheRegistryHub() { - static RegistryHub* theRegistryHub = CATCH_NULL; - if( !theRegistryHub ) - theRegistryHub = new RegistryHub(); - return theRegistryHub; - } } - IRegistryHub& getRegistryHub() { - return *getTheRegistryHub(); + using RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>; + + IRegistryHub const& getRegistryHub() { + return RegistryHubSingleton::get(); } IMutableRegistryHub& getMutableRegistryHub() { - return *getTheRegistryHub(); + return RegistryHubSingleton::getMutable(); } void cleanUp() { - delete getTheRegistryHub(); - getTheRegistryHub() = CATCH_NULL; + cleanupSingletons(); cleanUpContext(); } std::string translateActiveException() { @@ -7592,642 +12519,1541 @@ namespace Catch { } } // end namespace Catch +// end catch_registry_hub.cpp +// start catch_reporter_registry.cpp -// #included from: catch_notimplemented_exception.hpp -#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED +namespace Catch { -#include <sstream> + ReporterRegistry::~ReporterRegistry() = default; + + IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const { + auto it = m_factories.find( name ); + if( it == m_factories.end() ) + return nullptr; + return it->second->create( ReporterConfig( config ) ); + } + + void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) { + m_factories.emplace(name, factory); + } + void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) { + m_listeners.push_back( factory ); + } + + IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { + return m_factories; + } + IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const { + return m_listeners; + } + +} +// end catch_reporter_registry.cpp +// start catch_result_type.cpp namespace Catch { - NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) - : m_lineInfo( lineInfo ) { - std::ostringstream oss; - oss << lineInfo << ": function "; - oss << "not implemented"; - m_what = oss.str(); + bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + bool isJustInfo( int flags ) { + return flags == ResultWas::Info; } - const char* NotImplementedException::what() const CATCH_NOEXCEPT { - return m_what.c_str(); + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) ); } + bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + } // end namespace Catch +// end catch_result_type.cpp +// start catch_run_context.cpp -// #included from: catch_context_impl.hpp -#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED +#include <cassert> +#include <algorithm> +#include <sstream> -// #included from: catch_stream.hpp -#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED +namespace Catch { -#include <stdexcept> -#include <cstdio> -#include <iostream> + namespace Generators { + struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { + GeneratorBasePtr m_generator; -namespace Catch { + GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + {} + ~GeneratorTracker(); + + static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) { + std::shared_ptr<GeneratorTracker> tracker; + + ITracker& currentTracker = ctx.currentTracker(); + // Under specific circumstances, the generator we want + // to acquire is also the current tracker. If this is + // the case, we have to avoid looking through current + // tracker's children, and instead return the current + // tracker. + // A case where this check is important is e.g. + // for (int i = 0; i < 5; ++i) { + // int n = GENERATE(1, 2); + // } + // + // without it, the code above creates 5 nested generators. + if (currentTracker.nameAndLocation() == nameAndLocation) { + auto thisTracker = currentTracker.parent().findChild(nameAndLocation); + assert(thisTracker); + assert(thisTracker->isGeneratorTracker()); + tracker = std::static_pointer_cast<GeneratorTracker>(thisTracker); + } else if ( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isGeneratorTracker() ); + tracker = std::static_pointer_cast<GeneratorTracker>( childTracker ); + } else { + tracker = std::make_shared<GeneratorTracker>( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( tracker ); + } - template<typename WriterF, size_t bufferSize=256> - class StreamBufImpl : public StreamBufBase { - char data[bufferSize]; - WriterF m_writer; + if( !tracker->isComplete() ) { + tracker->open(); + } - public: - StreamBufImpl() { - setp( data, data + sizeof(data) ); - } + return *tracker; + } - ~StreamBufImpl() CATCH_NOEXCEPT { - sync(); - } + // TrackerBase interface + bool isGeneratorTracker() const override { return true; } + auto hasGenerator() const -> bool override { + return !!m_generator; + } + void close() override { + TrackerBase::close(); + // If a generator has a child (it is followed by a section) + // and none of its children have started, then we must wait + // until later to start consuming its values. + // This catches cases where `GENERATE` is placed between two + // `SECTION`s. + // **The check for m_children.empty cannot be removed**. + // doing so would break `GENERATE` _not_ followed by `SECTION`s. + const bool should_wait_for_child = [&]() { + // No children -> nobody to wait for + if ( m_children.empty() ) { + return false; + } + // If at least one child started executing, don't wait + if ( std::find_if( + m_children.begin(), + m_children.end(), + []( TestCaseTracking::ITrackerPtr tracker ) { + return tracker->hasStarted(); + } ) != m_children.end() ) { + return false; + } - private: - int overflow( int c ) { - sync(); + // No children have started. We need to check if they _can_ + // start, and thus we should wait for them, or they cannot + // start (due to filters), and we shouldn't wait for them + auto* parent = m_parent; + // This is safe: there is always at least one section + // tracker in a test case tracking tree + while ( !parent->isSectionTracker() ) { + parent = &( parent->parent() ); + } + assert( parent && + "Missing root (test case) level section" ); + + auto const& parentSection = + static_cast<SectionTracker&>( *parent ); + auto const& filters = parentSection.getFilters(); + // No filters -> no restrictions on running sections + if ( filters.empty() ) { + return true; + } - if( c != EOF ) { - if( pbase() == epptr() ) - m_writer( std::string( 1, static_cast<char>( c ) ) ); - else - sputc( static_cast<char>( c ) ); + for ( auto const& child : m_children ) { + if ( child->isSectionTracker() && + std::find( filters.begin(), + filters.end(), + static_cast<SectionTracker&>( *child ) + .trimmedName() ) != + filters.end() ) { + return true; + } + } + return false; + }(); + + // This check is a bit tricky, because m_generator->next() + // has a side-effect, where it consumes generator's current + // value, but we do not want to invoke the side-effect if + // this generator is still waiting for any child to start. + if ( should_wait_for_child || + ( m_runState == CompletedSuccessfully && + m_generator->next() ) ) { + m_children.clear(); + m_runState = Executing; + } } - return 0; - } - int sync() { - if( pbase() != pptr() ) { - m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); - setp( pbase(), epptr() ); + // IGeneratorTracker interface + auto getGenerator() const -> GeneratorBasePtr const& override { + return m_generator; } - return 0; + void setGenerator( GeneratorBasePtr&& generator ) override { + m_generator = std::move( generator ); + } + }; + GeneratorTracker::~GeneratorTracker() {} + } + + RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) + : m_runInfo(_config->name()), + m_context(getCurrentMutableContext()), + m_config(_config), + m_reporter(std::move(reporter)), + m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + { + m_context.setRunner(this); + m_context.setConfig(m_config); + m_context.setResultCapture(this); + m_reporter->testRunStarting(m_runInfo); + } + + RunContext::~RunContext() { + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); + } + + void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); + } + + void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); + } + + Totals RunContext::runTest(TestCase const& testCase) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + auto const& testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting(testInfo); + + m_activeTestCase = &testCase; + + ITracker& rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); + runCurrentTest(redirectedCout, redirectedCerr); + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; } - }; + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting())); - /////////////////////////////////////////////////////////////////////////// + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; - FileStream::FileStream( std::string const& filename ) { - m_ofs.open( filename.c_str() ); - if( m_ofs.fail() ) { - std::ostringstream oss; - oss << "Unable to open file: '" << filename << '\''; - throw std::domain_error( oss.str() ); + return deltaTotals; + } + + IConfigPtr RunContext::config() const { + return m_config; + } + + IStreamingReporter& RunContext::reporter() const { + return *m_reporter; + } + + void RunContext::assertionEnded(AssertionResult const & result) { + if (result.getResultType() == ResultWas::Ok) { + m_totals.assertions.passed++; + m_lastAssertionPassed = true; + } else if (!result.isOk()) { + m_lastAssertionPassed = false; + if( m_activeTestCase->getTestCaseInfo().okToFail() ) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + else { + m_lastAssertionPassed = true; } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + if (result.getResultType() != ResultWas::Warning) + m_messageScopes.clear(); + + // Reset working state + resetAssertionInfo(); + m_lastResult = result; + } + void RunContext::resetAssertionInfo() { + m_lastAssertionInfo.macroName = StringRef(); + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; + } + + bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { + ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting(sectionInfo); + + assertions = m_totals.assertions; + + return true; + } + auto RunContext::acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + using namespace Generators; + GeneratorTracker& tracker = GeneratorTracker::acquire(m_trackerContext, + TestCaseTracking::NameAndLocation( static_cast<std::string>(generatorName), lineInfo ) ); + m_lastAssertionInfo.lineInfo = lineInfo; + return tracker; } - std::ostream& FileStream::stream() const { - return m_ofs; + bool RunContext::testForMissingAssertions(Counts& assertions) { + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; } - struct OutputDebugWriter { + void RunContext::sectionEnded(SectionEndInfo const & endInfo) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); - void operator()( std::string const&str ) { - writeToDebugConsole( str ); + if (!m_activeSections.empty()) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); } - }; - DebugOutStream::DebugOutStream() - : m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ), - m_os( m_streamBuf.get() ) - {} + m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); + m_messages.clear(); + m_messageScopes.clear(); + } - std::ostream& DebugOutStream::stream() const { - return m_os; + void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { + if (m_unfinishedSections.empty()) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(endInfo); } - // Store the streambuf from cout up-front because - // cout may get redirected when running tests - CoutStream::CoutStream() - : m_os( Catch::cout().rdbuf() ) - {} +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void RunContext::benchmarkPreparing(std::string const& name) { + m_reporter->benchmarkPreparing(name); + } + void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { + m_reporter->benchmarkStarting( info ); + } + void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) { + m_reporter->benchmarkEnded( stats ); + } + void RunContext::benchmarkFailed(std::string const & error) { + m_reporter->benchmarkFailed(error); + } +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - std::ostream& CoutStream::stream() const { - return m_os; + void RunContext::pushScopedMessage(MessageInfo const & message) { + m_messages.push_back(message); } -#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions - std::ostream& cout() { - return std::cout; + void RunContext::popScopedMessage(MessageInfo const & message) { + m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); } - std::ostream& cerr() { - return std::cerr; + + void RunContext::emplaceUnscopedMessage( MessageBuilder const& builder ) { + m_messageScopes.emplace_back( builder ); } - std::ostream& clog() { - return std::clog; + + std::string RunContext::getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); } -#endif -} -namespace Catch { + const AssertionResult * RunContext::getLastResult() const { + return &(*m_lastResult); + } - class Context : public IMutableContext { + void RunContext::exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } - Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} - Context( Context const& ); - void operator=( Context const& ); + void RunContext::handleFatalErrorCondition( StringRef message ) { + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered(message); - public: - virtual ~Context() { - deleteAllValues( m_generatorsByTestName ); - } + // 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<std::string>(message); + AssertionResult result(m_lastAssertionInfo, tempResult); - public: // IContext - virtual IResultCapture* getResultCapture() { - return m_resultCapture; - } - virtual IRunner* getRunner() { - return m_runner; - } - virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { - return getGeneratorsForCurrentTest() - .getGeneratorInfo( fileInfo, totalSize ) - .getCurrentIndex(); - } - virtual bool advanceGeneratorsForCurrentTest() { - IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); - return generators && generators->moveNext(); - } + assertionEnded(result); - virtual Ptr<IConfig const> getConfig() const { - return m_config; - } + handleUnfinishedSections(); - public: // IMutableContext - virtual void setResultCapture( IResultCapture* resultCapture ) { - m_resultCapture = resultCapture; - } - virtual void setRunner( IRunner* runner ) { - m_runner = runner; - } - virtual void setConfig( Ptr<IConfig const> const& config ) { - m_config = config; - } + // Recreate section for test case (as we will lose the one that was in scope) + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); - friend IMutableContext& getCurrentMutableContext(); + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); + m_reporter->sectionEnded(testCaseSectionStats); + + auto const& testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + testGroupEnded(std::string(), m_totals, 1, 1); + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); + } - private: - IGeneratorsForTest* findGeneratorsForCurrentTest() { - std::string testName = getResultCapture()->getCurrentTestName(); + bool RunContext::lastAssertionPassed() { + return m_lastAssertionPassed; + } + + void RunContext::assertionPassed() { + m_lastAssertionPassed = true; + ++m_totals.assertions.passed; + resetAssertionInfo(); + m_messageScopes.clear(); + } - std::map<std::string, IGeneratorsForTest*>::const_iterator it = - m_generatorsByTestName.find( testName ); - return it != m_generatorsByTestName.end() - ? it->second - : CATCH_NULL; + bool RunContext::aborting() const { + return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter()); + } + + void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + m_reporter->sectionStarting(testCaseSection); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + + seedRng(*m_config); + + Timer timer; + CATCH_TRY { + if (m_reporter->getPreferences().shouldRedirectStdOut) { +#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) + RedirectedStreams redirectedStreams(redirectedCout, redirectedCerr); + + timer.start(); + invokeActiveTestCase(); +#else + OutputRedirect r(redirectedCout, redirectedCerr); + timer.start(); + invokeActiveTestCase(); +#endif + } else { + timer.start(); + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } CATCH_CATCH_ANON (TestFailureException&) { + // This just means the test was aborted due to failure + } 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 ) { + AssertionReaction dummyReaction; + handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + } } + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); - IGeneratorsForTest& getGeneratorsForCurrentTest() { - IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); - if( !generators ) { - std::string testName = getResultCapture()->getCurrentTestName(); - generators = createGeneratorsForTest(); - m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + m_messageScopes.clear(); + + SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); + } + + void RunContext::invokeActiveTestCase() { + FatalConditionHandlerGuard _(&m_fatalConditionhandler); + m_activeTestCase->invoke(); + } + + void RunContext::handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for (auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it) + sectionEnded(*it); + m_unfinishedSections.clear(); + } + + void RunContext::handleExpr( + AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + bool negated = isFalseTest( info.resultDisposition ); + bool result = expr.getResult() != negated; + + if( result ) { + if (!m_includeSuccessfulResults) { + assertionPassed(); + } + else { + reportExpr(info, ResultWas::Ok, &expr, negated); } - return *generators; } + else { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); + populateReaction( reaction ); + } + } + void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ) { - private: - Ptr<IConfig const> m_config; - IRunner* m_runner; - IResultCapture* m_resultCapture; - std::map<std::string, IGeneratorsForTest*> m_generatorsByTestName; - }; + m_lastAssertionInfo = info; + AssertionResultData data( resultType, LazyExpression( negated ) ); - namespace { - Context* currentContext = CATCH_NULL; + AssertionResult assertionResult{ info, data }; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded( assertionResult ); } - IMutableContext& getCurrentMutableContext() { - if( !currentContext ) - currentContext = new Context(); - return *currentContext; + + void RunContext::handleMessage( + AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef const& message, + AssertionReaction& reaction + ) { + m_reporter->assertionStarting( info ); + + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + data.message = static_cast<std::string>(message); + AssertionResult assertionResult{ m_lastAssertionInfo, data }; + assertionEnded( assertionResult ); + if( !assertionResult.isOk() ) + populateReaction( reaction ); } - IContext& getCurrentContext() { - return getCurrentMutableContext(); + void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const& info, + AssertionReaction& reaction + ) { + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); } - void cleanUpContext() { - delete currentContext; - currentContext = CATCH_NULL; + void RunContext::handleUnexpectedInflightException( + AssertionInfo const& info, + std::string const& message, + AssertionReaction& reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = message; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + populateReaction( reaction ); + } + + void RunContext::populateReaction( AssertionReaction& reaction ) { + reaction.shouldDebugBreak = m_config->shouldDebugBreak(); + reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + } + + void RunContext::handleIncomplete( + AssertionInfo const& info + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); } + void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + AssertionResult assertionResult{ info, data }; + assertionEnded( assertionResult ); + + if( !assertionResult.isOk() ) + populateReaction( reaction ); + } + + IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) + return *capture; + else + CATCH_INTERNAL_ERROR("No result capture instance"); + } + + void seedRng(IConfig const& config) { + if (config.rngSeed() != 0) { + std::srand(config.rngSeed()); + rng().seed(config.rngSeed()); + } + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + } +// end catch_run_context.cpp +// start catch_section.cpp -// #included from: catch_console_colour_impl.hpp -#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED +namespace Catch { -// #included from: catch_errno_guard.hpp -#define TWOBLUECUBES_CATCH_ERRNO_GUARD_HPP_INCLUDED + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } -#include <cerrno> + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() }; + if( uncaught_exceptions() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch +// end catch_section.cpp +// start catch_section_info.cpp namespace Catch { - class ErrnoGuard { + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name ) + : name( _name ), + lineInfo( _lineInfo ) + {} + +} // end namespace Catch +// end catch_section_info.cpp +// start catch_session.cpp + +// start catch_session.h + +#include <memory> + +namespace Catch { + + class Session : NonCopyable { public: - ErrnoGuard():m_oldErrno(errno){} - ~ErrnoGuard() { errno = m_oldErrno; } + + Session(); + ~Session() override; + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char const * const * argv ); + #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) + int applyCommandLine( int argc, wchar_t const * const * argv ); + #endif + + void useConfigData( ConfigData const& configData ); + + template<typename CharT> + int run(int argc, CharT const * const argv[]) { + if (m_startupExceptions) + return 1; + int returnCode = applyCommandLine(argc, argv); + if (returnCode == 0) + returnCode = run(); + return returnCode; + } + + int run(); + + clara::Parser const& cli() const; + void cli( clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); private: - int m_oldErrno; + int runInternal(); + + clara::Parser m_cli; + ConfigData m_configData; + std::shared_ptr<Config> m_config; + bool m_startupExceptions = false; }; +} // end namespace Catch + +// end catch_session.h +// start catch_version.h + +#include <iosfwd> + +namespace Catch { + + // Versioning information + struct Version { + Version( Version const& ) = delete; + Version& operator=( Version const& ) = delete; + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + }; + + Version const& libraryVersion(); } +// end catch_version.h +#include <cstdlib> +#include <iomanip> +#include <set> +#include <iterator> + namespace Catch { + namespace { + const int MaxExitCode = 255; - struct IColourImpl { - virtual ~IColourImpl() {} - virtual void use( Colour::Code _colourCode ) = 0; - }; + IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); - struct NoColourImpl : IColourImpl { - void use( Colour::Code ) {} + return reporter; + } - static IColourImpl* instance() { - static NoColourImpl s_instance; - return &s_instance; + IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) { + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { + return createReporter(config->getReporterName(), config); } - }; - } // anon namespace -} // namespace Catch + // On older platforms, returning std::unique_ptr<ListeningReporter> + // when the return type is std::unique_ptr<IStreamingReporter> + // doesn't compile without a std::move call. However, this causes + // a warning on newer platforms. Thus, we have to work around + // it a bit and downcast the pointer manually. + auto ret = std::unique_ptr<IStreamingReporter>(new ListeningReporter); + auto& multi = static_cast<ListeningReporter&>(*ret); + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const& listener : listeners) { + multi.addListener(listener->create(Catch::ReporterConfig(config))); + } + multi.addReporter(createReporter(config->getReporterName(), config)); + return ret; + } -#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) -# ifdef CATCH_PLATFORM_WINDOWS -# define CATCH_CONFIG_COLOUR_WINDOWS -# else -# define CATCH_CONFIG_COLOUR_ANSI -# endif -#endif + class TestGroup { + public: + explicit TestGroup(std::shared_ptr<Config> const& config) + : m_config{config} + , m_context{config, makeReporter(config)} + { + auto const& allTestCases = getAllTestCasesSorted(*m_config); + m_matches = m_config->testSpec().matchesByFilter(allTestCases, *m_config); + auto const& invalidArgs = m_config->testSpec().getInvalidArgs(); + + if (m_matches.empty() && invalidArgs.empty()) { + for (auto const& test : allTestCases) + if (!test.isHidden()) + m_tests.emplace(&test); + } else { + for (auto const& match : m_matches) + m_tests.insert(match.tests.begin(), match.tests.end()); + } + } -#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + Totals execute() { + auto const& invalidArgs = m_config->testSpec().getInvalidArgs(); + Totals totals; + m_context.testGroupStarting(m_config->name(), 1, 1); + for (auto const& testCase : m_tests) { + if (!m_context.aborting()) + totals += m_context.runTest(*testCase); + else + m_context.reporter().skipTest(*testCase); + } -namespace Catch { -namespace { + for (auto const& match : m_matches) { + if (match.tests.empty()) { + m_context.reporter().noMatchingTestCases(match.name); + totals.error = -1; + } + } - class Win32ColourImpl : public IColourImpl { - public: - Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) - { - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); - originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); - originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); - } + if (!invalidArgs.empty()) { + for (auto const& invalidArg: invalidArgs) + m_context.reporter().reportInvalidArguments(invalidArg); + } - virtual void use( Colour::Code _colourCode ) { - switch( _colourCode ) { - case Colour::None: return setTextAttribute( originalForegroundAttributes ); - case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); - case Colour::Red: return setTextAttribute( FOREGROUND_RED ); - case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); - case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); - case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); - case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); - case Colour::Grey: return setTextAttribute( 0 ); + m_context.testGroupEnded(m_config->name(), totals, 1, 1); + return totals; + } - case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); - case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); - case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); - case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + private: + using Tests = std::set<TestCase const*>; + + std::shared_ptr<Config> m_config; + RunContext m_context; + Tests m_tests; + TestSpec::Matches m_matches; + }; + + void applyFilenamesAsTags(Catch::IConfig const& config) { + auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config)); + for (auto& testCase : tests) { + auto tags = testCase.tags; - case Colour::Bright: throw std::logic_error( "not a colour" ); + std::string filename = testCase.lineInfo.file; + auto lastSlash = filename.find_last_of("\\/"); + if (lastSlash != std::string::npos) { + filename.erase(0, lastSlash); + filename[0] = '#'; + } + + auto lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) { + filename.erase(lastDot); + } + + tags.push_back(std::move(filename)); + setTags(testCase, tags); } } - private: - void setTextAttribute( WORD _textAttribute ) { - SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } // anon namespace + + Session::Session() { + static bool alreadyInstantiated = false; + if( alreadyInstantiated ) { + CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } + CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } + } + + // There cannot be exceptions at startup in no-exception mode. +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + config(); + getCurrentMutableContext().setConfig(m_config); + + m_startupExceptions = true; + Colour colourGuard( Colour::Red ); + Catch::cerr() << "Errors occurred during startup!" << '\n'; + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch ( std::exception const& ex ) { + Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; + } + } } - HANDLE stdoutHandle; - WORD originalForegroundAttributes; - WORD originalBackgroundAttributes; - }; +#endif - IColourImpl* platformColourInstance() { - static Win32ColourImpl s_instance; + alreadyInstantiated = true; + m_cli = makeCommandLineParser( m_configData ); + } + Session::~Session() { + Catch::cleanUp(); + } - Ptr<IConfig const> config = getCurrentContext().getConfig(); - UseColour::YesOrNo colourMode = config - ? config->useColour() - : UseColour::Auto; - if( colourMode == UseColour::Auto ) - colourMode = !isDebuggerActive() - ? UseColour::Yes - : UseColour::No; - return colourMode == UseColour::Yes - ? &s_instance - : NoColourImpl::instance(); + void Session::showHelp() const { + Catch::cout() + << "\nCatch v" << libraryVersion() << "\n" + << m_cli << std::endl + << "For more detailed usage please see the project docs\n" << std::endl; + } + void Session::libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch2 test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch Test\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; } -} // end anon namespace -} // end namespace Catch + int Session::applyCommandLine( int argc, char const * const * argv ) { + if( m_startupExceptions ) + return 1; + + auto result = m_cli.parse( clara::Args( argc, argv ) ); + if( !result ) { + config(); + getCurrentMutableContext().setConfig(m_config); + Catch::cerr() + << Colour( Colour::Red ) + << "\nError(s) in input:\n" + << Column( result.errorMessage() ).indent( 2 ) + << "\n\n"; + Catch::cerr() << "Run with -? for usage\n" << std::endl; + return MaxExitCode; + } + + if( m_configData.showHelp ) + showHelp(); + if( m_configData.libIdentify ) + libIdentify(); + m_config.reset(); + return 0; + } -#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// +#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) + int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { -#include <unistd.h> + char **utf8Argv = new char *[ argc ]; -namespace Catch { -namespace { + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr ); - // use POSIX/ ANSI console terminal codes - // Thanks to Adam Strzelecki for original contribution - // (http://github.com/nanoant) - // https://github.com/philsquared/Catch/pull/131 - class PosixColourImpl : public IColourImpl { - public: - virtual void use( Colour::Code _colourCode ) { - switch( _colourCode ) { - case Colour::None: - case Colour::White: return setColour( "[0m" ); - case Colour::Red: return setColour( "[0;31m" ); - case Colour::Green: return setColour( "[0;32m" ); - case Colour::Blue: return setColour( "[0;34m" ); - case Colour::Cyan: return setColour( "[0;36m" ); - case Colour::Yellow: return setColour( "[0;33m" ); - case Colour::Grey: return setColour( "[1;30m" ); + utf8Argv[ i ] = new char[ bufSize ]; - case Colour::LightGrey: return setColour( "[0;37m" ); - case Colour::BrightRed: return setColour( "[1;31m" ); - case Colour::BrightGreen: return setColour( "[1;32m" ); - case Colour::BrightWhite: return setColour( "[1;37m" ); + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr ); + } - case Colour::Bright: throw std::logic_error( "not a colour" ); - } + int returnCode = applyCommandLine( argc, utf8Argv ); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } +#endif + + void Session::useConfigData( ConfigData const& configData ) { + m_configData = configData; + m_config.reset(); + } + + int Session::run() { + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before starting" << std::endl; + static_cast<void>(std::getchar()); } - static IColourImpl* instance() { - static PosixColourImpl s_instance; - return &s_instance; + int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; + static_cast<void>(std::getchar()); } + return exitCode; + } - private: - void setColour( const char* _escapeCode ) { - Catch::cout() << '\033' << _escapeCode; + clara::Parser const& Session::cli() const { + return m_cli; + } + void Session::cli( clara::Parser const& newParser ) { + m_cli = newParser; + } + ConfigData& Session::configData() { + return m_configData; + } + Config& Session::config() { + if( !m_config ) + m_config = std::make_shared<Config>( m_configData ); + return *m_config; + } + + int Session::runInternal() { + if( m_startupExceptions ) + return 1; + + if (m_configData.showHelp || m_configData.libIdentify) { + return 0; } - }; - IColourImpl* platformColourInstance() { - ErrnoGuard guard; - Ptr<IConfig const> config = getCurrentContext().getConfig(); - UseColour::YesOrNo colourMode = config - ? config->useColour() - : UseColour::Auto; - if( colourMode == UseColour::Auto ) - colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) - ? UseColour::Yes - : UseColour::No; - return colourMode == UseColour::Yes - ? PosixColourImpl::instance() - : NoColourImpl::instance(); + CATCH_TRY { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option<std::size_t> listed = list( m_config ) ) + return static_cast<int>( *listed ); + + TestGroup tests { m_config }; + auto const totals = tests.execute(); + + if( m_config->warnAboutNoTests() && totals.error == -1 ) + return 2; + + // Note that on unices only the lower 8 bits are usually used, clamping + // the return value to 255 prevents false negative when some multiple + // of 256 tests has failed + return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast<int>(totals.assertions.failed))); + } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return MaxExitCode; + } +#endif } -} // end anon namespace } // end namespace Catch +// end catch_session.cpp +// start catch_singletons.cpp -#else // not Windows or ANSI /////////////////////////////////////////////// +#include <vector> namespace Catch { - static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + namespace { + static auto getSingletons() -> std::vector<ISingleton*>*& { + static std::vector<ISingleton*>* g_singletons = nullptr; + if( !g_singletons ) + g_singletons = new std::vector<ISingleton*>(); + return g_singletons; + } + } -} // end namespace Catch + ISingleton::~ISingleton() {} -#endif // Windows/ ANSI/ None + void addSingleton(ISingleton* singleton ) { + getSingletons()->push_back( singleton ); + } + void cleanupSingletons() { + auto& singletons = getSingletons(); + for( auto singleton : *singletons ) + delete singleton; + delete singletons; + singletons = nullptr; + } -namespace Catch { +} // namespace Catch +// end catch_singletons.cpp +// start catch_startup_exception_registry.cpp - Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } - Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast<Colour&>( _other ).m_moved = true; } - Colour::~Colour(){ if( !m_moved ) use( None ); } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +namespace Catch { +void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { + CATCH_TRY { + m_exceptions.push_back(exception); + } CATCH_CATCH_ALL { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } + } - void Colour::use( Code _colourCode ) { - static IColourImpl* impl = platformColourInstance(); - impl->use( _colourCode ); + std::vector<std::exception_ptr> const& StartupExceptionRegistry::getExceptions() const noexcept { + return m_exceptions; } } // end namespace Catch +#endif +// end catch_startup_exception_registry.cpp +// start catch_stream.cpp -// #included from: catch_generators_impl.hpp -#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED - +#include <cstdio> +#include <iostream> +#include <fstream> +#include <sstream> #include <vector> -#include <string> -#include <map> +#include <memory> namespace Catch { - struct GeneratorInfo : IGeneratorInfo { + Catch::IStream::~IStream() = default; - GeneratorInfo( std::size_t size ) - : m_size( size ), - m_currentIndex( 0 ) - {} + namespace Detail { namespace { + template<typename WriterF, std::size_t bufferSize=256> + class StreamBufImpl : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; - bool moveNext() { - if( ++m_currentIndex == m_size ) { - m_currentIndex = 0; - return false; + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); } - return true; - } - std::size_t getCurrentIndex() const { - return m_currentIndex; - } + ~StreamBufImpl() noexcept { + StreamBufImpl::sync(); + } - std::size_t m_size; - std::size_t m_currentIndex; - }; + private: + int overflow( int c ) override { + sync(); - /////////////////////////////////////////////////////////////////////////// + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast<char>( c ) ) ); + else + sputc( static_cast<char>( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; - class GeneratorsForTest : public IGeneratorsForTest { + /////////////////////////////////////////////////////////////////////////// - public: - ~GeneratorsForTest() { - deleteAll( m_generatorsInOrder ); - } + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + /////////////////////////////////////////////////////////////////////////// - IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { - std::map<std::string, IGeneratorInfo*>::const_iterator it = m_generatorsByName.find( fileInfo ); - if( it == m_generatorsByName.end() ) { - IGeneratorInfo* info = new GeneratorInfo( size ); - m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); - m_generatorsInOrder.push_back( info ); - return *info; + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( StringRef filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); + } + ~FileStream() override = default; + public: // IStream + std::ostream& stream() const override { + return m_ofs; } - return *it->second; + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os( Catch::cout().rdbuf() ) {} + ~CoutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream : public IStream { + std::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream() + : m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ), + m_os( m_streamBuf.get() ) + {} + + ~DebugOutStream() override = default; + + public: // IStream + std::ostream& stream() const override { return m_os; } + }; + + }} // namespace anon::detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream( StringRef const &filename ) -> IStream const* { + if( filename.empty() ) + return new Detail::CoutStream(); + else if( filename[0] == '%' ) { + if( filename == "%debug" ) + return new Detail::DebugOutStream(); + else + CATCH_ERROR( "Unrecognised stream: '" << filename << "'" ); } + else + return new Detail::FileStream( filename ); + } - bool moveNext() { - std::vector<IGeneratorInfo*>::const_iterator it = m_generatorsInOrder.begin(); - std::vector<IGeneratorInfo*>::const_iterator itEnd = m_generatorsInOrder.end(); - for(; it != itEnd; ++it ) { - if( (*it)->moveNext() ) - return true; + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector<std::unique_ptr<std::ostringstream>> m_streams; + std::vector<std::size_t> m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + + auto add() -> std::size_t { + if( m_unused.empty() ) { + m_streams.push_back( std::unique_ptr<std::ostringstream>( new std::ostringstream ) ); + return m_streams.size()-1; + } + else { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; } - return false; } - private: - std::map<std::string, IGeneratorInfo*> m_generatorsByName; - std::vector<IGeneratorInfo*> m_generatorsInOrder; + void release( std::size_t index ) { + m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state + m_unused.push_back(index); + } }; - IGeneratorsForTest* createGeneratorsForTest() - { - return new GeneratorsForTest(); - } + ReusableStringStream::ReusableStringStream() + : m_index( Singleton<StringStreams>::getMutable().add() ), + m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() ) + {} -} // end namespace Catch + ReusableStringStream::~ReusableStringStream() { + static_cast<std::ostringstream*>( m_oss )->str(""); + m_oss->clear(); + Singleton<StringStreams>::getMutable().release( m_index ); + } -// #included from: catch_assertionresult.hpp -#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + auto ReusableStringStream::str() const -> std::string { + return static_cast<std::ostringstream*>( m_oss )->str(); + } -namespace Catch { + /////////////////////////////////////////////////////////////////////////// - AssertionInfo::AssertionInfo():macroName(""), capturedExpression(""), resultDisposition(ResultDisposition::Normal), secondArg(""){} +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { return std::cout; } + std::ostream& cerr() { return std::cerr; } + std::ostream& clog() { return std::clog; } +#endif +} +// end catch_stream.cpp +// start catch_string_manip.cpp - AssertionInfo::AssertionInfo( char const * _macroName, - SourceLineInfo const& _lineInfo, - char const * _capturedExpression, - ResultDisposition::Flags _resultDisposition, - char const * _secondArg) - : macroName( _macroName ), - lineInfo( _lineInfo ), - capturedExpression( _capturedExpression ), - resultDisposition( _resultDisposition ), - secondArg( _secondArg ) - {} +#include <algorithm> +#include <ostream> +#include <cstring> +#include <cctype> +#include <vector> - AssertionResult::AssertionResult() {} +namespace Catch { - AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) - : m_info( info ), - m_resultData( data ) - {} + namespace { + char toLowerCh(char c) { + return static_cast<char>( std::tolower( static_cast<unsigned char>(c) ) ); + } + } - AssertionResult::~AssertionResult() {} + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); - // Result was a success - bool AssertionResult::succeeded() const { - return Catch::isOk( m_resultData.resultType ); + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); } - // Result was a success, or failure is suppressed - bool AssertionResult::isOk() const { - return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + StringRef trim(StringRef ref) { + const auto is_ws = [](char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + }; + size_t real_begin = 0; + while (real_begin < ref.size() && is_ws(ref[real_begin])) { ++real_begin; } + size_t real_end = ref.size(); + while (real_end > real_begin && is_ws(ref[real_end - 1])) { --real_end; } + + return ref.substr(real_begin, real_end - real_begin); } - ResultWas::OfType AssertionResult::getResultType() const { - return m_resultData.resultType; + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; } - bool AssertionResult::hasExpression() const { - return m_info.capturedExpression[0] != 0; + std::vector<StringRef> splitStringRef( StringRef str, char delimiter ) { + std::vector<StringRef> subStrings; + std::size_t start = 0; + for(std::size_t pos = 0; pos < str.size(); ++pos ) { + if( str[pos] == delimiter ) { + if( pos - start > 1 ) + subStrings.push_back( str.substr( start, pos-start ) ); + start = pos+1; + } + } + if( start < str.size() ) + subStrings.push_back( str.substr( start, str.size()-start ) ); + return subStrings; } - bool AssertionResult::hasMessage() const { - return !m_resultData.message.empty(); + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; } - std::string capturedExpressionWithSecondArgument( char const * capturedExpression, char const * secondArg ) { - return (secondArg[0] == 0 || secondArg[0] == '"' && secondArg[1] == '"') - ? capturedExpression - : std::string(capturedExpression) + ", " + secondArg; +} +// end catch_string_manip.cpp +// start catch_stringref.cpp + +#include <algorithm> +#include <ostream> +#include <cstring> +#include <cstdint> + +namespace Catch { + StringRef::StringRef( char const* rawChars ) noexcept + : StringRef( rawChars, static_cast<StringRef::size_type>(std::strlen(rawChars) ) ) + {} + + auto StringRef::c_str() const -> char const* { + CATCH_ENFORCE(isNullTerminated(), "Called StringRef::c_str() on a non-null-terminated instance"); + return m_start; + } + auto StringRef::data() const noexcept -> char const* { + return m_start; } - std::string AssertionResult::getExpression() const { - if( isFalseTest( m_info.resultDisposition ) ) - return '!' + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); - else - return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); + auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { + if (start < m_size) { + return StringRef(m_start + start, (std::min)(m_size - start, size)); + } else { + return StringRef(); + } } - std::string AssertionResult::getExpressionInMacro() const { - if( m_info.macroName[0] == 0 ) - return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); - else - return std::string(m_info.macroName) + "( " + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg) + " )"; + auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { + return m_size == other.m_size + && (std::memcmp( m_start, other.m_start, m_size ) == 0); } - bool AssertionResult::hasExpandedExpression() const { - return hasExpression() && getExpandedExpression() != getExpression(); + auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { + return os.write(str.data(), str.size()); } - std::string AssertionResult::getExpandedExpression() const { - return m_resultData.reconstructExpression(); + auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& { + lhs.append(rhs.data(), rhs.size()); + return lhs; } - std::string AssertionResult::getMessage() const { - return m_resultData.message; +} // namespace Catch +// end catch_stringref.cpp +// start catch_tag_alias.cpp + +namespace Catch { + TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {} +} +// end catch_tag_alias.cpp +// start catch_tag_alias_autoregistrar.cpp + +namespace Catch { + + RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { + CATCH_TRY { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } } - SourceLineInfo AssertionResult::getSourceInfo() const { - return m_info.lineInfo; + +} +// end catch_tag_alias_autoregistrar.cpp +// start catch_tag_alias_registry.cpp + +#include <sstream> + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { + auto it = m_registry.find( alias ); + if( it != m_registry.end() ) + return &(it->second); + else + return nullptr; } - std::string AssertionResult::getTestMacroName() const { - return m_info.macroName; + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( auto const& registryKvp : m_registry ) { + std::size_t pos = expandedTestSpec.find( registryKvp.first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + registryKvp.second.tag + + expandedTestSpec.substr( pos + registryKvp.first.size() ); + } + } + return expandedTestSpec; } - void AssertionResult::discardDecomposedExpression() const { - m_resultData.decomposedExpression = CATCH_NULL; + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); + + CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo ); } - void AssertionResult::expandDecomposedExpression() const { - m_resultData.reconstructExpression(); + ITagAliasRegistry::~ITagAliasRegistry() {} + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); } } // end namespace Catch - -// #included from: catch_test_case_info.hpp -#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED +// end catch_tag_alias_registry.cpp +// start catch_test_case_info.cpp #include <cctype> +#include <exception> +#include <algorithm> +#include <sstream> namespace Catch { - inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { - if( startsWith( tag, '.' ) || - tag == "hide" || - tag == "!hide" ) - return TestCaseInfo::IsHidden; - else if( tag == "!throws" ) - return TestCaseInfo::Throws; - else if( tag == "!shouldfail" ) - return TestCaseInfo::ShouldFail; - else if( tag == "!mayfail" ) - return TestCaseInfo::MayFail; - else if( tag == "!nonportable" ) - return TestCaseInfo::NonPortable; - else - return TestCaseInfo::None; - } - inline bool isReservedTag( std::string const& tag ) { - return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] ); - } - inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { - if( isReservedTag( tag ) ) { - std::ostringstream ss; - ss << Colour(Colour::Red) - << "Tag name [" << tag << "] not allowed.\n" - << "Tag names starting with non alpha-numeric characters are reserved\n" - << Colour(Colour::FileName) - << _lineInfo << '\n'; - throw std::runtime_error(ss.str()); + namespace { + TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else if( tag == "!benchmark" ) + return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); + else + return TestCaseInfo::None; + } + bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast<unsigned char>(tag[0]) ); + } + void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + CATCH_ENFORCE( !isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alphanumeric characters are reserved\n" + << _lineInfo ); } } - TestCase makeTestCase( ITestCase* _testCase, + TestCase makeTestCase( ITestInvoker* _testCase, std::string const& _className, - std::string const& _name, - std::string const& _descOrTags, + NameAndTags const& nameAndTags, SourceLineInfo const& _lineInfo ) { - bool isHidden( startsWith( _name, "./" ) ); // Legacy support + bool isHidden = false; // Parse out tags - std::set<std::string> tags; + std::vector<std::string> tags; std::string desc, tag; bool inTag = false; - for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { - char c = _descOrTags[i]; + for (char c : nameAndTags.tags) { if( !inTag ) { if( c == '[' ) inTag = true; @@ -8237,12 +14063,18 @@ namespace Catch { else { if( c == ']' ) { TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); - if( prop == TestCaseInfo::IsHidden ) + if( ( prop & TestCaseInfo::IsHidden ) != 0 ) isHidden = true; else if( prop == TestCaseInfo::None ) enforceNotReservedTag( tag, _lineInfo ); - tags.insert( tag ); + // Merged hide tags like `[.approvals]` should be added as + // `[.][approvals]`. The `[.]` is added at later point, so + // we only strip the prefix + if (startsWith(tag, '.') && tag.size() > 1) { + tag.erase(0, 1); + } + tags.push_back( tag ); tag.clear(); inTag = false; } @@ -8251,33 +14083,31 @@ namespace Catch { } } if( isHidden ) { - tags.insert( "hide" ); - tags.insert( "." ); + // Add all "hidden" tags to make them behave identically + tags.insert( tags.end(), { ".", "!hide" } ); } - TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); - return TestCase( _testCase, info ); + TestCaseInfo info( static_cast<std::string>(nameAndTags.name), _className, desc, tags, _lineInfo ); + return TestCase( _testCase, std::move(info) ); } - void setTags( TestCaseInfo& testCaseInfo, std::set<std::string> const& tags ) - { - testCaseInfo.tags = tags; + void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) { + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), end(tags)); testCaseInfo.lcaseTags.clear(); - std::ostringstream oss; - for( std::set<std::string>::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { - oss << '[' << *it << ']'; - std::string lcaseTag = toLower( *it ); + for( auto const& tag : tags ) { + std::string lcaseTag = toLower( tag ); testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); - testCaseInfo.lcaseTags.insert( lcaseTag ); + testCaseInfo.lcaseTags.push_back( lcaseTag ); } - testCaseInfo.tagsAsString = oss.str(); + testCaseInfo.tags = std::move(tags); } TestCaseInfo::TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, - std::set<std::string> const& _tags, + std::vector<std::string> const& _tags, SourceLineInfo const& _lineInfo ) : name( _name ), className( _className ), @@ -8288,17 +14118,6 @@ namespace Catch { setTags( *this, _tags ); } - TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) - : name( other.name ), - className( other.className ), - description( other.description ), - tags( other.tags ), - lcaseTags( other.lcaseTags ), - tagsAsString( other.tagsAsString ), - lineInfo( other.lineInfo ), - properties( other.properties ) - {} - bool TestCaseInfo::isHidden() const { return ( properties & IsHidden ) != 0; } @@ -8312,12 +14131,24 @@ namespace Catch { return ( properties & (ShouldFail ) ) != 0; } - TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + std::string TestCaseInfo::tagsAsString() const { + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto& tag : tags) { + full_size += tag.size(); + } + ret.reserve(full_size); + for (const auto& tag : tags) { + ret.push_back('['); + ret.append(tag); + ret.push_back(']'); + } + + return ret; + } - TestCase::TestCase( TestCase const& other ) - : TestCaseInfo( other ), - test( other.test ) - {} + TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {} TestCase TestCase::withName( std::string const& _newName ) const { TestCase other( *this ); @@ -8325,18 +14156,6 @@ namespace Catch { return other; } - void TestCase::swap( TestCase& other ) { - test.swap( other.test ); - name.swap( other.name ); - className.swap( other.className ); - description.swap( other.description ); - tags.swap( other.tags ); - lcaseTags.swap( other.lcaseTags ); - tagsAsString.swap( other.tagsAsString ); - std::swap( TestCaseInfo::properties, static_cast<TestCaseInfo&>( other ).properties ); - std::swap( lineInfo, other.lineInfo ); - } - void TestCase::invoke() const { test->invoke(); } @@ -8350,11 +14169,6 @@ namespace Catch { bool TestCase::operator < ( TestCase const& other ) const { return name < other.name; } - TestCase& TestCase::operator = ( TestCase const& other ) { - TestCase temp( other ); - swap( temp ); - return *this; - } TestCaseInfo const& TestCase::getTestCaseInfo() const { @@ -8362,533 +14176,835 @@ namespace Catch { } } // end namespace Catch +// end catch_test_case_info.cpp +// start catch_test_case_registry_impl.cpp -// #included from: catch_version.hpp -#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED +#include <algorithm> +#include <sstream> namespace Catch { - Version::Version - ( unsigned int _majorVersion, - unsigned int _minorVersion, - unsigned int _patchNumber, - char const * const _branchName, - unsigned int _buildNumber ) - : majorVersion( _majorVersion ), - minorVersion( _minorVersion ), - patchNumber( _patchNumber ), - branchName( _branchName ), - buildNumber( _buildNumber ) - {} + namespace { + struct TestHasher { + using hash_t = uint64_t; + + explicit TestHasher( hash_t hashSuffix ): + m_hashSuffix{ hashSuffix } {} + + uint32_t operator()( TestCase const& t ) const { + // FNV-1a hash with multiplication fold. + const hash_t prime = 1099511628211u; + hash_t hash = 14695981039346656037u; + for ( const char c : t.name ) { + hash ^= c; + hash *= prime; + } + hash ^= m_hashSuffix; + hash *= prime; + const uint32_t low{ static_cast<uint32_t>( hash ) }; + const uint32_t high{ static_cast<uint32_t>( hash >> 32 ) }; + return low * high; + } - std::ostream& operator << ( std::ostream& os, Version const& version ) { - os << version.majorVersion << '.' - << version.minorVersion << '.' - << version.patchNumber; - // branchName is never null -> 0th char is \0 if it is empty - if (version.branchName[0]) { - os << '-' << version.branchName - << '.' << version.buildNumber; + private: + hash_t m_hashSuffix; + }; + } // end unnamed namespace + + std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) { + switch( config.runOrder() ) { + case RunTests::InDeclarationOrder: + // already in declaration order + break; + + case RunTests::InLexicographicalOrder: { + std::vector<TestCase> sorted = unsortedTestCases; + std::sort( sorted.begin(), sorted.end() ); + return sorted; + } + + case RunTests::InRandomOrder: { + seedRng( config ); + TestHasher h{ config.rngSeed() }; + + using hashedTest = std::pair<TestHasher::hash_t, TestCase const*>; + std::vector<hashedTest> indexed_tests; + indexed_tests.reserve( unsortedTestCases.size() ); + + for (auto const& testCase : unsortedTestCases) { + indexed_tests.emplace_back(h(testCase), &testCase); + } + + std::sort(indexed_tests.begin(), indexed_tests.end(), + [](hashedTest const& lhs, hashedTest const& rhs) { + if (lhs.first == rhs.first) { + return lhs.second->name < rhs.second->name; + } + return lhs.first < rhs.first; + }); + + std::vector<TestCase> sorted; + sorted.reserve( indexed_tests.size() ); + + for (auto const& hashed : indexed_tests) { + sorted.emplace_back(*hashed.second); + } + + return sorted; + } } - return os; + return unsortedTestCases; } - inline Version libraryVersion() { - static Version version( 1, 10, 0, "", 0 ); - return version; + bool isThrowSafe( TestCase const& testCase, IConfig const& config ) { + return !testCase.throws() || config.allowThrows(); } -} + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && isThrowSafe( testCase, config ); + } -// #included from: catch_message.hpp -#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) { + std::set<TestCase> seenFunctions; + for( auto const& function : functions ) { + auto prev = seenFunctions.insert( function ); + CATCH_ENFORCE( prev.second, + "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << function.getTestCaseInfo().lineInfo ); + } + } -namespace Catch { + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector<TestCase> filtered; + filtered.reserve( testCases.size() ); + for (auto const& testCase : testCases) { + if ((!testSpec.hasFilters() && !testCase.isHidden()) || + (testSpec.hasFilters() && matchTest(testCase, testSpec, config))) { + filtered.push_back(testCase); + } + } + return filtered; + } + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } - MessageInfo::MessageInfo( std::string const& _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ) - : macroName( _macroName ), - lineInfo( _lineInfo ), - type( _type ), - sequence( ++globalCount ) - {} + void TestRegistry::registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name.empty() ) { + ReusableStringStream rss; + rss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( rss.str() ) ); + } + m_functions.push_back( testCase ); + } - // This may need protecting if threading support is added - unsigned int MessageInfo::globalCount = 0; + std::vector<TestCase> const& TestRegistry::getAllTests() const { + return m_functions; + } + std::vector<TestCase> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); - //////////////////////////////////////////////////////////////////////////// + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } - ScopedMessage::ScopedMessage( MessageBuilder const& builder ) - : m_info( builder.m_info ) - { - m_info.message = builder.m_stream.str(); - getResultCapture().pushScopedMessage( m_info ); + /////////////////////////////////////////////////////////////////////////// + TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {} + + void TestInvokerAsFunction::invoke() const { + m_testAsFunction(); } - ScopedMessage::ScopedMessage( ScopedMessage const& other ) - : m_info( other.m_info ) - {} - ScopedMessage::~ScopedMessage() { - if ( !std::uncaught_exception() ){ - getResultCapture().popScopedMessage(m_info); + std::string extractClassName( StringRef const& classOrQualifiedMethodName ) { + std::string className(classOrQualifiedMethodName); + if( startsWith( className, '&' ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); } + return className; } } // end namespace Catch +// end catch_test_case_registry_impl.cpp +// start catch_test_case_tracker.cpp -// #included from: catch_legacy_reporter_adapter.hpp -#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED - -// #included from: catch_legacy_reporter_adapter.h -#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED +#include <algorithm> +#include <cassert> +#include <stdexcept> +#include <memory> +#include <sstream> -namespace Catch -{ - // Deprecated - struct IReporter : IShared { - virtual ~IReporter(); - - virtual bool shouldRedirectStdout() const = 0; - - virtual void StartTesting() = 0; - virtual void EndTesting( Totals const& totals ) = 0; - virtual void StartGroup( std::string const& groupName ) = 0; - virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; - virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; - virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; - virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; - virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; - virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; - virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; - virtual void Aborted() = 0; - virtual void Result( AssertionResult const& result ) = 0; - }; - - class LegacyReporterAdapter : public SharedImpl<IStreamingReporter> - { - public: - LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter ); - virtual ~LegacyReporterAdapter(); - - virtual ReporterPreferences getPreferences() const; - virtual void noMatchingTestCases( std::string const& ); - virtual void testRunStarting( TestRunInfo const& ); - virtual void testGroupStarting( GroupInfo const& groupInfo ); - virtual void testCaseStarting( TestCaseInfo const& testInfo ); - virtual void sectionStarting( SectionInfo const& sectionInfo ); - virtual void assertionStarting( AssertionInfo const& ); - virtual bool assertionEnded( AssertionStats const& assertionStats ); - virtual void sectionEnded( SectionStats const& sectionStats ); - virtual void testCaseEnded( TestCaseStats const& testCaseStats ); - virtual void testGroupEnded( TestGroupStats const& testGroupStats ); - virtual void testRunEnded( TestRunStats const& testRunStats ); - virtual void skipTest( TestCaseInfo const& ); +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif - private: - Ptr<IReporter> m_legacyReporter; - }; -} +namespace Catch { +namespace TestCaseTracking { -namespace Catch -{ - LegacyReporterAdapter::LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter ) - : m_legacyReporter( legacyReporter ) + NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) {} - LegacyReporterAdapter::~LegacyReporterAdapter() {} - ReporterPreferences LegacyReporterAdapter::getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); - return prefs; + ITracker::~ITracker() = default; + + ITracker& TrackerContext::startRun() { + m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; } - void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} - void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { - m_legacyReporter->StartTesting(); + void TrackerContext::endRun() { + m_rootTracker.reset(); + m_currentTracker = nullptr; + m_runState = NotStarted; } - void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { - m_legacyReporter->StartGroup( groupInfo.name ); + + void TrackerContext::startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; } - void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { - m_legacyReporter->StartTestCase( testInfo ); + void TrackerContext::completeCycle() { + m_runState = CompletedCycle; + } + + bool TrackerContext::completedCycle() const { + return m_runState == CompletedCycle; } - void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { - m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + ITracker& TrackerContext::currentTracker() { + return *m_currentTracker; } - void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { - // Not on legacy interface + void TrackerContext::setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; } - bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { - if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { - for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); - it != itEnd; - ++it ) { - if( it->type == ResultWas::Info ) { - ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); - rb << it->message; - rb.setResultType( ResultWas::Info ); - AssertionResult result = rb.build(); - m_legacyReporter->Result( result ); - } - } - } - m_legacyReporter->Result( assertionStats.assertionResult ); - return true; + TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ): + ITracker(nameAndLocation), + m_ctx( ctx ), + m_parent( parent ) + {} + + bool TrackerBase::isComplete() const { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + bool TrackerBase::isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; } - void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { - if( sectionStats.missingAssertions ) - m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); - m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + bool TrackerBase::isOpen() const { + return m_runState != NotStarted && !isComplete(); } - void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { - m_legacyReporter->EndTestCase - ( testCaseStats.testInfo, - testCaseStats.totals, - testCaseStats.stdOut, - testCaseStats.stdErr ); + bool TrackerBase::hasChildren() const { + return !m_children.empty(); } - void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { - if( testGroupStats.aborting ) - m_legacyReporter->Aborted(); - m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + + void TrackerBase::addChild( ITrackerPtr const& child ) { + m_children.push_back( child ); } - void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { - m_legacyReporter->EndTesting( testRunStats.totals ); + + ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { + auto it = std::find_if( m_children.begin(), m_children.end(), + [&nameAndLocation]( ITrackerPtr const& tracker ){ + return + tracker->nameAndLocation().location == nameAndLocation.location && + tracker->nameAndLocation().name == nameAndLocation.name; + } ); + return( it != m_children.end() ) + ? *it + : nullptr; } - void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + ITracker& TrackerBase::parent() { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; } -} -// #included from: catch_timer.hpp + void TrackerBase::openChild() { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wc++11-long-long" -#endif + bool TrackerBase::isSectionTracker() const { return false; } + bool TrackerBase::isGeneratorTracker() const { return false; } -#ifdef CATCH_PLATFORM_WINDOWS + void TrackerBase::open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } -#else + void TrackerBase::close() { -#include <sys/time.h> + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); -#endif + switch( m_runState ) { + case NeedsAnotherRun: + break; -namespace Catch { + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( std::all_of(m_children.begin(), m_children.end(), [](ITrackerPtr const& t){ return t->isComplete(); }) ) + m_runState = CompletedSuccessfully; + break; - namespace { -#ifdef CATCH_PLATFORM_WINDOWS - UInt64 getCurrentTicks() { - static UInt64 hz=0, hzo=0; - if (!hz) { - QueryPerformanceFrequency( reinterpret_cast<LARGE_INTEGER*>( &hz ) ); - QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &hzo ) ); - } - UInt64 t; - QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &t ) ); - return ((t-hzo)*1000000)/hz; - } -#else - UInt64 getCurrentTicks() { - timeval t; - gettimeofday(&t,CATCH_NULL); - return static_cast<UInt64>( t.tv_sec ) * 1000000ull + static_cast<UInt64>( t.tv_usec ); + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); + + default: + CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); } -#endif + moveToParent(); + m_ctx.completeCycle(); } - - void Timer::start() { - m_ticks = getCurrentTicks(); + void TrackerBase::fail() { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); } - unsigned int Timer::getElapsedMicroseconds() const { - return static_cast<unsigned int>(getCurrentTicks() - m_ticks); + void TrackerBase::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; } - unsigned int Timer::getElapsedMilliseconds() const { - return static_cast<unsigned int>(getElapsedMicroseconds()/1000); + + void TrackerBase::moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); } - double Timer::getElapsedSeconds() const { - return getElapsedMicroseconds()/1000000.0; + void TrackerBase::moveToThis() { + m_ctx.setCurrentTracker( this ); } -} // namespace Catch + SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_trimmed_name(trim(nameAndLocation.name)) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); -#ifdef __clang__ -#pragma clang diagnostic pop -#endif -// #included from: catch_common.hpp -#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + SectionTracker& parentSection = static_cast<SectionTracker&>( *parent ); + addNextFilters( parentSection.m_filters ); + } + } -#include <cstring> -#include <cctype> + bool SectionTracker::isComplete() const { + bool complete = true; -namespace Catch { + if (m_filters.empty() + || m_filters[0] == "" + || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) { + complete = TrackerBase::isComplete(); + } + return complete; + } - bool startsWith( std::string const& s, std::string const& prefix ) { - return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + bool SectionTracker::isSectionTracker() const { return true; } + + SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + std::shared_ptr<SectionTracker> section; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = std::static_pointer_cast<SectionTracker>( childTracker ); + } + else { + section = std::make_shared<SectionTracker>( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; } - bool startsWith( std::string const& s, char prefix ) { - return !s.empty() && s[0] == prefix; + + void SectionTracker::tryOpen() { + if( !isComplete() ) + open(); } - bool endsWith( std::string const& s, std::string const& suffix ) { - return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + + void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) { + if( !filters.empty() ) { + m_filters.reserve( m_filters.size() + filters.size() + 2 ); + m_filters.emplace_back(""); // Root - should never be consulted + m_filters.emplace_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } } - bool endsWith( std::string const& s, char suffix ) { - return !s.empty() && s[s.size()-1] == suffix; + void SectionTracker::addNextFilters( std::vector<std::string> const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() ); } - bool contains( std::string const& s, std::string const& infix ) { - return s.find( infix ) != std::string::npos; + + std::vector<std::string> const& SectionTracker::getFilters() const { + return m_filters; } - char toLowerCh(char c) { - return static_cast<char>( std::tolower( c ) ); + + std::string const& SectionTracker::trimmedName() const { + return m_trimmed_name; } - void toLowerInPlace( std::string& s ) { - std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_test_case_tracker.cpp +// start catch_test_registry.cpp + +namespace Catch { + + auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsFunction( testAsFunction ); } - std::string toLower( std::string const& s ) { - std::string lc = s; - toLowerInPlace( lc ); - return lc; + + NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {} + + AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept { + CATCH_TRY { + getMutableRegistryHub() + .registerTest( + makeTestCase( + invoker, + extractClassName( classOrMethod ), + nameAndTags, + lineInfo)); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } } - std::string trim( std::string const& str ) { - static char const* whitespaceChars = "\n\r\t "; - std::string::size_type start = str.find_first_not_of( whitespaceChars ); - std::string::size_type end = str.find_last_not_of( whitespaceChars ); - return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + AutoReg::~AutoReg() = default; +} +// end catch_test_registry.cpp +// start catch_test_spec.cpp + +#include <algorithm> +#include <string> +#include <vector> +#include <memory> + +namespace Catch { + + TestSpec::Pattern::Pattern( std::string const& name ) + : m_name( name ) + {} + + TestSpec::Pattern::~Pattern() = default; + + std::string const& TestSpec::Pattern::name() const { + return m_name; } - bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { - bool replaced = false; - std::size_t i = str.find( replaceThis ); - while( i != std::string::npos ) { - replaced = true; - str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); - if( i < str.size()-withThis.size() ) - i = str.find( replaceThis, i+withThis.size() ); - else - i = std::string::npos; - } - return replaced; + TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString ) + : Pattern( filterString ) + , m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( testCase.name ); } - pluralise::pluralise( std::size_t count, std::string const& label ) - : m_count( count ), - m_label( label ) + TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString ) + : Pattern( filterString ) + , m_tag( toLower( tag ) ) {} - std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { - os << pluraliser.m_count << ' ' << pluraliser.m_label; - if( pluraliser.m_count != 1 ) - os << 's'; - return os; + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { + return std::find(begin(testCase.lcaseTags), + end(testCase.lcaseTags), + m_tag) != end(testCase.lcaseTags); } - SourceLineInfo::SourceLineInfo() : file(""), line( 0 ){} - SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) - : file( _file ), - line( _line ) + TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) + : Pattern( underlyingPattern->name() ) + , m_underlyingPattern( underlyingPattern ) {} - bool SourceLineInfo::empty() const { - return file[0] == '\0'; + + bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { + return !m_underlyingPattern->matches( testCase ); } - bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { - return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + + bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { + return std::all_of( m_patterns.begin(), m_patterns.end(), [&]( PatternPtr const& p ){ return p->matches( testCase ); } ); } - bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { - return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); + + std::string TestSpec::Filter::name() const { + std::string name; + for( auto const& p : m_patterns ) + name += p->name(); + return name; } - void seedRng( IConfig const& config ) { - if( config.rngSeed() != 0 ) - std::srand( config.rngSeed() ); + bool TestSpec::hasFilters() const { + return !m_filters.empty(); } - unsigned int rngSeed() { - return getCurrentContext().getConfig()->rngSeed(); + + bool TestSpec::matches( TestCaseInfo const& testCase ) const { + return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } ); } - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { -#ifndef __GNUG__ - os << info.file << '(' << info.line << ')'; -#else - os << info.file << ':' << info.line; -#endif - return os; + TestSpec::Matches TestSpec::matchesByFilter( std::vector<TestCase> const& testCases, IConfig const& config ) const + { + Matches matches( m_filters.size() ); + std::transform( m_filters.begin(), m_filters.end(), matches.begin(), [&]( Filter const& filter ){ + std::vector<TestCase const*> currentMatches; + for( auto const& test : testCases ) + if( isThrowSafe( test, config ) && filter.matches( test ) ) + currentMatches.emplace_back( &test ); + return FilterMatch{ filter.name(), currentMatches }; + } ); + return matches; } - void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { - std::ostringstream oss; - oss << locationInfo << ": Internal Catch error: '" << message << '\''; - if( alwaysTrue() ) - throw std::logic_error( oss.str() ); + const TestSpec::vectorStrings& TestSpec::getInvalidArgs() const{ + return (m_invalidArgs); } -} -// #included from: catch_section.hpp -#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED +} +// end catch_test_spec.cpp +// start catch_test_spec_parser.cpp namespace Catch { - SectionInfo::SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description ) - : name( _name ), - description( _description ), - lineInfo( _lineInfo ) - {} - - Section::Section( SectionInfo const& info ) - : m_info( info ), - m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) - { - m_timer.start(); + TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& TestSpecParser::parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + m_substring.reserve(m_arg.size()); + m_patternName.reserve(m_arg.size()); + m_realPatternPos = 0; + + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + //if visitChar fails + if( !visitChar( m_arg[m_pos] ) ){ + m_testSpec.m_invalidArgs.push_back(arg); + break; + } + endMode(); + return *this; + } + TestSpec TestSpecParser::testSpec() { + addFilter(); + return m_testSpec; } + bool TestSpecParser::visitChar( char c ) { + if( (m_mode != EscapedName) && (c == '\\') ) { + escape(); + addCharToPattern(c); + return true; + }else if((m_mode != EscapedName) && (c == ',') ) { + return separate(); + } -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17 -#endif - Section::~Section() { - if( m_sectionIncluded ) { - SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); - if( std::uncaught_exception() ) - getResultCapture().sectionEndedEarly( endInfo ); + switch( m_mode ) { + case None: + if( processNoneChar( c ) ) + return true; + break; + case Name: + processNameChar( c ); + break; + case EscapedName: + endMode(); + addCharToPattern(c); + return true; + default: + case Tag: + case QuotedName: + if( processOtherChar( c ) ) + return true; + break; + } + + m_substring += c; + if( !isControlChar( c ) ) { + m_patternName += c; + m_realPatternPos++; + } + return true; + } + // Two of the processing methods return true to signal the caller to return + // without adding the given character to the current pattern strings + bool TestSpecParser::processNoneChar( char c ) { + switch( c ) { + case ' ': + return true; + case '~': + m_exclusion = true; + return false; + case '[': + startNewMode( Tag ); + return false; + case '"': + startNewMode( QuotedName ); + return false; + default: + startNewMode( Name ); + return false; + } + } + void TestSpecParser::processNameChar( char c ) { + if( c == '[' ) { + if( m_substring == "exclude:" ) + m_exclusion = true; else - getResultCapture().sectionEnded( endInfo ); + endMode(); + startNewMode( Tag ); + } + } + bool TestSpecParser::processOtherChar( char c ) { + if( !isControlChar( c ) ) + return false; + m_substring += c; + endMode(); + return true; + } + void TestSpecParser::startNewMode( Mode mode ) { + m_mode = mode; + } + void TestSpecParser::endMode() { + switch( m_mode ) { + case Name: + case QuotedName: + return addNamePattern(); + case Tag: + return addTagPattern(); + case EscapedName: + revertBackToLastMode(); + return; + case None: + default: + return startNewMode( None ); + } + } + void TestSpecParser::escape() { + saveLastMode(); + m_mode = EscapedName; + m_escapeChars.push_back(m_realPatternPos); + } + bool TestSpecParser::isControlChar( char c ) const { + switch( m_mode ) { + default: + return false; + case None: + return c == '~'; + case Name: + return c == '['; + case EscapedName: + return true; + case QuotedName: + return c == '"'; + case Tag: + return c == '[' || c == ']'; } } -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - // This indicates whether the section should be executed or not - Section::operator bool() const { - return m_sectionIncluded; + void TestSpecParser::addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } } -} // end namespace Catch + void TestSpecParser::saveLastMode() { + lastMode = m_mode; + } -// #included from: catch_debugger.hpp -#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + void TestSpecParser::revertBackToLastMode() { + m_mode = lastMode; + } -#ifdef CATCH_PLATFORM_MAC + bool TestSpecParser::separate() { + if( (m_mode==QuotedName) || (m_mode==Tag) ){ + //invalid argument, signal failure to previous scope. + m_mode = None; + m_pos = m_arg.size(); + m_substring.clear(); + m_patternName.clear(); + m_realPatternPos = 0; + return false; + } + endMode(); + addFilter(); + return true; //success + } - #include <assert.h> - #include <stdbool.h> - #include <sys/types.h> - #include <unistd.h> - #include <sys/sysctl.h> + std::string TestSpecParser::preprocessPattern() { + std::string token = m_patternName; + for (std::size_t i = 0; i < m_escapeChars.size(); ++i) + token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1); + m_escapeChars.clear(); + if (startsWith(token, "exclude:")) { + m_exclusion = true; + token = token.substr(8); + } - namespace Catch{ + m_patternName.clear(); + m_realPatternPos = 0; - // The following function is taken directly from the following technical note: - // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + return token; + } - // Returns true if the current process is being debugged (either - // running under the debugger or has a debugger attached post facto). - bool isDebuggerActive(){ + void TestSpecParser::addNamePattern() { + auto token = preprocessPattern(); - int mib[4]; - struct kinfo_proc info; - size_t size; + if (!token.empty()) { + TestSpec::PatternPtr pattern = std::make_shared<TestSpec::NamePattern>(token, m_substring); + if (m_exclusion) + pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern); + m_currentFilter.m_patterns.push_back(pattern); + } + m_substring.clear(); + m_exclusion = false; + m_mode = None; + } - // Initialize the flags so that, if sysctl fails for some bizarre - // reason, we get a predictable result. + void TestSpecParser::addTagPattern() { + auto token = preprocessPattern(); + + if (!token.empty()) { + // If the tag pattern is the "hide and tag" shorthand (e.g. [.foo]) + // we have to create a separate hide tag and shorten the real one + if (token.size() > 1 && token[0] == '.') { + token.erase(token.begin()); + TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(".", m_substring); + if (m_exclusion) { + pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern); + } + m_currentFilter.m_patterns.push_back(pattern); + } - info.kp_proc.p_flag = 0; + TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(token, m_substring); - // Initialize mib, which tells sysctl the info we want, in this case - // we're looking for information about a specific process ID. + if (m_exclusion) { + pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern); + } + m_currentFilter.m_patterns.push_back(pattern); + } + m_substring.clear(); + m_exclusion = false; + m_mode = None; + } - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PID; - mib[3] = getpid(); + TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } - // Call sysctl. +} // namespace Catch +// end catch_test_spec_parser.cpp +// start catch_timer.cpp - size = sizeof(info); - if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { - Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; - return false; - } +#include <chrono> - // We're being debugged if the P_TRACED flag is set. +static const uint64_t nanosecondsInSecond = 1000000000; - return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); - } - } // namespace Catch +namespace Catch { -#elif defined(CATCH_PLATFORM_LINUX) - #include <fstream> - #include <string> + auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); + } - namespace Catch{ - // The standard POSIX way of detecting a debugger is to attempt to - // ptrace() the process, but this needs to be done from a child and not - // this process itself to still allow attaching to this process later - // if wanted, so is rather heavy. Under Linux we have the PID of the - // "debugger" (which doesn't need to be gdb, of course, it could also - // be strace, for example) in /proc/$PID/status, so just get it from - // there instead. - bool isDebuggerActive(){ - // Libstdc++ has a bug, where std::ifstream sets errno to 0 - // This way our users can properly assert over errno values - ErrnoGuard guard; - std::ifstream in("/proc/self/status"); - for( std::string line; std::getline(in, line); ) { - static const int PREFIX_LEN = 11; - if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { - // We're traced if the PID is not 0 and no other PID starts - // with 0 digit, so it's enough to check for just a single - // character. - return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + namespace { + auto estimateClockResolution() -> uint64_t { + uint64_t sum = 0; + static const uint64_t iterations = 1000000; + + auto startTime = getCurrentNanosecondsSinceEpoch(); + + for( std::size_t i = 0; i < iterations; ++i ) { + + uint64_t ticks; + uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); + do { + ticks = getCurrentNanosecondsSinceEpoch(); + } while( ticks == baseTicks ); + + auto delta = ticks - baseTicks; + sum += delta; + + // If we have been calibrating for over 3 seconds -- the clock + // is terrible and we should move on. + // TBD: How to signal that the measured resolution is probably wrong? + if (ticks > startTime + 3 * nanosecondsInSecond) { + return sum / ( i + 1u ); } } - return false; - } - } // namespace Catch -#elif defined(_MSC_VER) - extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); - namespace Catch { - bool isDebuggerActive() { - return IsDebuggerPresent() != 0; + // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers + // - and potentially do more iterations if there's a high variance. + return sum/iterations; } } -#elif defined(__MINGW32__) - extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); - namespace Catch { - bool isDebuggerActive() { - return IsDebuggerPresent() != 0; - } + auto getEstimatedClockResolution() -> uint64_t { + static auto s_resolution = estimateClockResolution(); + return s_resolution; } -#else - namespace Catch { - inline bool isDebuggerActive() { return false; } - } -#endif // Platform - -#ifdef CATCH_PLATFORM_WINDOWS - namespace Catch { - void writeToDebugConsole( std::string const& text ) { - ::OutputDebugStringA( text.c_str() ); - } + void Timer::start() { + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); } -#else - namespace Catch { - void writeToDebugConsole( std::string const& text ) { - // !TBD: Need a version for Mac/ XCode and other IDEs - Catch::cout() << text; - } + auto Timer::getElapsedNanoseconds() const -> uint64_t { + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; } -#endif // Platform + auto Timer::getElapsedMicroseconds() const -> uint64_t { + return getElapsedNanoseconds()/1000; + } + auto Timer::getElapsedMilliseconds() const -> unsigned int { + return static_cast<unsigned int>(getElapsedMicroseconds()/1000); + } + auto Timer::getElapsedSeconds() const -> double { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch +// end catch_timer.cpp +// start catch_tostring.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +# pragma clang diagnostic ignored "-Wglobal-constructors" +#endif -// #included from: catch_tostring.hpp -#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED +// Enable specific decls locally +#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +#endif + +#include <cmath> +#include <iomanip> namespace Catch { @@ -8903,19 +15019,16 @@ namespace Detail { enum Arch { Big, Little }; static Arch which() { - union _{ - int asInt; - char asChar[sizeof (int)]; - } u; - - u.asInt = 1; - return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + int one = 1; + // If the lowest byte we read is non-zero, we can assume + // that little endian format is used. + auto value = *reinterpret_cast<char*>(&one); + return value ? Little : Big; } }; } - std::string rawMemoryToString( const void *object, std::size_t size ) - { + std::string rawMemoryToString( const void *object, std::size_t size ) { // Reverse order for little endian architectures int i = 0, end = static_cast<int>( size ), inc = 1; if( Endianness::which() == Endianness::Little ) { @@ -8924,1424 +15037,1763 @@ namespace Detail { } unsigned char const *bytes = static_cast<unsigned char const *>(object); - std::ostringstream os; - os << "0x" << std::setfill('0') << std::hex; + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; for( ; i != end; i += inc ) - os << std::setw(2) << static_cast<unsigned>(bytes[i]); - return os.str(); + rss << std::setw(2) << static_cast<unsigned>(bytes[i]); + return rss.str(); } } -std::string toString( std::string const& value ) { - std::string s = value; - if( getCurrentContext().getConfig()->showInvisibles() ) { - for(size_t i = 0; i < s.size(); ++i ) { - std::string subs; - switch( s[i] ) { - case '\n': subs = "\\n"; break; - case '\t': subs = "\\t"; break; - default: break; - } - if( !subs.empty() ) { - s = s.substr( 0, i ) + subs + s.substr( i+1 ); - ++i; - } - } +template<typename T> +std::string fpToString( T value, int precision ) { + if (Catch::isnan(value)) { + return "nan"; } - return '"' + s + '"'; -} -std::string toString( std::wstring const& value ) { - std::string s; - s.reserve( value.size() ); - for(size_t i = 0; i < value.size(); ++i ) - s += value[i] <= 0xff ? static_cast<char>( value[i] ) : '?'; - return Catch::toString( s ); + ReusableStringStream rss; + rss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = rss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; } -std::string toString( const char* const value ) { - return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +//// ======================================================= //// +// +// Out-of-line defs for full specialization of StringMaker +// +//// ======================================================= //// + +std::string StringMaker<std::string>::convert(const std::string& str) { + if (!getCurrentContext().getConfig()->showInvisibles()) { + return '"' + str + '"'; + } + + std::string s("\""); + for (char c : str) { + switch (c) { + case '\n': + s.append("\\n"); + break; + case '\t': + s.append("\\t"); + break; + default: + s.push_back(c); + break; + } + } + s.append("\""); + return s; } -std::string toString( char* const value ) { - return Catch::toString( static_cast<const char*>( value ) ); +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker<std::string_view>::convert(std::string_view str) { + return ::Catch::Detail::stringify(std::string{ str }); } +#endif -std::string toString( const wchar_t* const value ) -{ - return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +std::string StringMaker<char const*>::convert(char const* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker<char*>::convert(char* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } } -std::string toString( wchar_t* const value ) -{ - return Catch::toString( static_cast<const wchar_t*>( value ) ); +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker<std::wstring>::convert(const std::wstring& wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast<char>(c) : '?'; + } + return ::Catch::Detail::stringify(s); } -std::string toString( int value ) { - std::ostringstream oss; - oss << value; - if( value > Detail::hexThreshold ) - oss << " (0x" << std::hex << value << ')'; - return oss.str(); +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker<std::wstring_view>::convert(std::wstring_view str) { + return StringMaker<std::wstring>::convert(std::wstring(str)); } +# endif -std::string toString( unsigned long value ) { - std::ostringstream oss; - oss << value; - if( value > Detail::hexThreshold ) - oss << " (0x" << std::hex << value << ')'; - return oss.str(); +std::string StringMaker<wchar_t const*>::convert(wchar_t const * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker<wchar_t *>::convert(wchar_t * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } } +#endif -std::string toString( unsigned int value ) { - return Catch::toString( static_cast<unsigned long>( value ) ); +#if defined(CATCH_CONFIG_CPP17_BYTE) +#include <cstddef> +std::string StringMaker<std::byte>::convert(std::byte value) { + return ::Catch::Detail::stringify(std::to_integer<unsigned long long>(value)); } +#endif // defined(CATCH_CONFIG_CPP17_BYTE) -template<typename T> -std::string fpToString( T value, int precision ) { - std::ostringstream oss; - oss << std::setprecision( precision ) - << std::fixed - << value; - std::string d = oss.str(); - std::size_t i = d.find_last_not_of( '0' ); - if( i != std::string::npos && i != d.size()-1 ) { - if( d[i] == '.' ) - i++; - d = d.substr( 0, i+1 ); +std::string StringMaker<int>::convert(int value) { + return ::Catch::Detail::stringify(static_cast<long long>(value)); +} +std::string StringMaker<long>::convert(long value) { + return ::Catch::Detail::stringify(static_cast<long long>(value)); +} +std::string StringMaker<long long>::convert(long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; } - return d; + return rss.str(); } -std::string toString( const double value ) { - return fpToString( value, 10 ); +std::string StringMaker<unsigned int>::convert(unsigned int value) { + return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); +} +std::string StringMaker<unsigned long>::convert(unsigned long value) { + return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); } -std::string toString( const float value ) { - return fpToString( value, 5 ) + 'f'; +std::string StringMaker<unsigned long long>::convert(unsigned long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); } -std::string toString( bool value ) { - return value ? "true" : "false"; +std::string StringMaker<bool>::convert(bool b) { + return b ? "true" : "false"; } -std::string toString( char value ) { - if ( value == '\r' ) +std::string StringMaker<signed char>::convert(signed char value) { + if (value == '\r') { return "'\\r'"; - if ( value == '\f' ) + } else if (value == '\f') { return "'\\f'"; - if ( value == '\n' ) + } else if (value == '\n') { return "'\\n'"; - if ( value == '\t' ) + } else if (value == '\t') { return "'\\t'"; - if ( '\0' <= value && value < ' ' ) - return toString( static_cast<unsigned int>( value ) ); - char chstr[] = "' '"; - chstr[1] = value; - return chstr; + } else if ('\0' <= value && value < ' ') { + return ::Catch::Detail::stringify(static_cast<unsigned int>(value)); + } else { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } } - -std::string toString( signed char value ) { - return toString( static_cast<char>( value ) ); +std::string StringMaker<char>::convert(char c) { + return ::Catch::Detail::stringify(static_cast<signed char>(c)); } - -std::string toString( unsigned char value ) { - return toString( static_cast<char>( value ) ); +std::string StringMaker<unsigned char>::convert(unsigned char c) { + return ::Catch::Detail::stringify(static_cast<char>(c)); } -#ifdef CATCH_CONFIG_CPP11_LONG_LONG -std::string toString( long long value ) { - std::ostringstream oss; - oss << value; - if( value > Detail::hexThreshold ) - oss << " (0x" << std::hex << value << ')'; - return oss.str(); +std::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) { + return "nullptr"; } -std::string toString( unsigned long long value ) { - std::ostringstream oss; - oss << value; - if( value > Detail::hexThreshold ) - oss << " (0x" << std::hex << value << ')'; - return oss.str(); + +int StringMaker<float>::precision = 5; + +std::string StringMaker<float>::convert(float value) { + return fpToString(value, precision) + 'f'; } -#endif -#ifdef CATCH_CONFIG_CPP11_NULLPTR -std::string toString( std::nullptr_t ) { - return "nullptr"; +int StringMaker<double>::precision = 10; + +std::string StringMaker<double>::convert(double value) { + return fpToString(value, precision); } -#endif -#ifdef __OBJC__ - std::string toString( NSString const * const& nsstring ) { - if( !nsstring ) - return "nil"; - return "@" + toString([nsstring UTF8String]); - } - std::string toString( NSString * CATCH_ARC_STRONG & nsstring ) { - if( !nsstring ) - return "nil"; - return "@" + toString([nsstring UTF8String]); - } - std::string toString( NSObject* const& nsObject ) { - return toString( [nsObject description] ); - } -#endif +std::string ratio_string<std::atto>::symbol() { return "a"; } +std::string ratio_string<std::femto>::symbol() { return "f"; } +std::string ratio_string<std::pico>::symbol() { return "p"; } +std::string ratio_string<std::nano>::symbol() { return "n"; } +std::string ratio_string<std::micro>::symbol() { return "u"; } +std::string ratio_string<std::milli>::symbol() { return "m"; } } // end namespace Catch -// #included from: catch_result_builder.hpp -#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED +#if defined(__clang__) +# pragma clang diagnostic pop +#endif -namespace Catch { +// end catch_tostring.cpp +// start catch_totals.cpp - ResultBuilder::ResultBuilder( char const* macroName, - SourceLineInfo const& lineInfo, - char const* capturedExpression, - ResultDisposition::Flags resultDisposition, - char const* secondArg ) - : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition, secondArg ), - m_shouldDebugBreak( false ), - m_shouldThrow( false ), - m_guardException( false ), - m_usedStream( false ) - {} +namespace Catch { - ResultBuilder::~ResultBuilder() { -#if defined(CATCH_CONFIG_FAST_COMPILE) - if ( m_guardException ) { - stream().oss << "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; - captureResult( ResultWas::ThrewException ); - getCurrentContext().getResultCapture()->exceptionEarlyReported(); - } -#endif + Counts Counts::operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; } - ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { - m_data.resultType = result; + Counts& Counts::operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; return *this; } - ResultBuilder& ResultBuilder::setResultType( bool result ) { - m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; - return *this; - } - - void ResultBuilder::endExpression( DecomposedExpression const& expr ) { - // Flip bool results if FalseTest flag is set - if( isFalseTest( m_assertionInfo.resultDisposition ) ) { - m_data.negate( expr.isBinaryExpression() ); - } - getResultCapture().assertionRun(); - - if(getCurrentContext().getConfig()->includeSuccessfulResults() || m_data.resultType != ResultWas::Ok) - { - AssertionResult result = build( expr ); - handleResult( result ); - } - else - getResultCapture().assertionPassed(); + std::size_t Counts::total() const { + return passed + failed + failedButOk; + } + bool Counts::allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool Counts::allOk() const { + return failed == 0; } - void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { - m_assertionInfo.resultDisposition = resultDisposition; - stream().oss << Catch::translateActiveException(); - captureResult( ResultWas::ThrewException ); + Totals Totals::operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; } - void ResultBuilder::captureResult( ResultWas::OfType resultType ) { - setResultType( resultType ); - captureExpression(); + Totals& Totals::operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; } - void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { - if( expectedMessage.empty() ) - captureExpectedException( Matchers::Impl::MatchAllOf<std::string>() ); + Totals Totals::delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; else - captureExpectedException( Matchers::Equals( expectedMessage ) ); + ++diff.testCases.passed; + return diff; } - void ResultBuilder::captureExpectedException( Matchers::Impl::MatcherBase<std::string> const& matcher ) { +} +// end catch_totals.cpp +// start catch_uncaught_exceptions.cpp - assert( !isFalseTest( m_assertionInfo.resultDisposition ) ); - AssertionResultData data = m_data; - data.resultType = ResultWas::Ok; - data.reconstructedExpression = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg); +// start catch_config_uncaught_exceptions.hpp - std::string actualMessage = Catch::translateActiveException(); - if( !matcher.match( actualMessage ) ) { - data.resultType = ResultWas::ExpressionFailed; - data.reconstructedExpression = actualMessage; - } - AssertionResult result( m_assertionInfo, data ); - handleResult( result ); - } +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) - void ResultBuilder::captureExpression() { - AssertionResult result = build(); - handleResult( result ); - } +// SPDX-License-Identifier: BSL-1.0 - void ResultBuilder::handleResult( AssertionResult const& result ) - { - getResultCapture().assertionEnded( result ); - - if( !result.isOk() ) { - if( getCurrentContext().getConfig()->shouldDebugBreak() ) - m_shouldDebugBreak = true; - if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) - m_shouldThrow = true; - } - } +#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP +#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP - void ResultBuilder::react() { -#if defined(CATCH_CONFIG_FAST_COMPILE) - if (m_shouldDebugBreak) { - /////////////////////////////////////////////////////////////////// - // To inspect the state during test, you need to go one level up the callstack - // To go back to the test and change execution, jump over the throw statement - /////////////////////////////////////////////////////////////////// - CATCH_BREAK_INTO_DEBUGGER(); - } +#if defined(_MSC_VER) +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif #endif - if( m_shouldThrow ) - throw Catch::TestFailureException(); - } - bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } - bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } +#include <exception> - AssertionResult ResultBuilder::build() const - { - return build( *this ); - } +#if defined(__cpp_lib_uncaught_exceptions) \ + && !defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) - // CAVEAT: The returned AssertionResult stores a pointer to the argument expr, - // a temporary DecomposedExpression, which in turn holds references to - // operands, possibly temporary as well. - // It should immediately be passed to handleResult; if the expression - // needs to be reported, its string expansion must be composed before - // the temporaries are destroyed. - AssertionResult ResultBuilder::build( DecomposedExpression const& expr ) const - { - assert( m_data.resultType != ResultWas::Unknown ); - AssertionResultData data = m_data; +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif // __cpp_lib_uncaught_exceptions - if(m_usedStream) - data.message = m_stream().oss.str(); - data.decomposedExpression = &expr; // for lazy reconstruction - return AssertionResult( m_assertionInfo, data ); - } +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) \ + && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) \ + && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) - void ResultBuilder::reconstructExpression( std::string& dest ) const { - dest = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg); - } +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif - void ResultBuilder::setExceptionGuard() { - m_guardException = true; - } - void ResultBuilder::unsetExceptionGuard() { - m_guardException = false; - } +#endif // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP +// end catch_config_uncaught_exceptions.hpp +#include <exception> +namespace Catch { + bool uncaught_exceptions() { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + return false; +#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif + } } // end namespace Catch +// end catch_uncaught_exceptions.cpp +// start catch_version.cpp -// #included from: catch_tag_alias_registry.hpp -#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED +#include <ostream> namespace Catch { - TagAliasRegistry::~TagAliasRegistry() {} + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} - Option<TagAlias> TagAliasRegistry::find( std::string const& alias ) const { - std::map<std::string, TagAlias>::const_iterator it = m_registry.find( alias ); - if( it != m_registry.end() ) - return it->second; - else - return Option<TagAlias>(); + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; } - std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { - std::string expandedTestSpec = unexpandedTestSpec; - for( std::map<std::string, TagAlias>::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); - it != itEnd; - ++it ) { - std::size_t pos = expandedTestSpec.find( it->first ); - if( pos != std::string::npos ) { - expandedTestSpec = expandedTestSpec.substr( 0, pos ) + - it->second.tag + - expandedTestSpec.substr( pos + it->first.size() ); - } - } - return expandedTestSpec; + Version const& libraryVersion() { + static Version version( 2, 13, 7, "", 0 ); + return version; } - void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { +} +// end catch_version.cpp +// start catch_wildcard_pattern.cpp - if( !startsWith( alias, "[@" ) || !endsWith( alias, ']' ) ) { - std::ostringstream oss; - oss << Colour( Colour::Red ) - << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" - << Colour( Colour::FileName ) - << lineInfo << '\n'; - throw std::domain_error( oss.str().c_str() ); +namespace Catch { + + WildcardPattern::WildcardPattern( std::string const& pattern, + CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_pattern( normaliseString( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; } - if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { - std::ostringstream oss; - oss << Colour( Colour::Red ) - << "error: tag alias, \"" << alias << "\" already registered.\n" - << "\tFirst seen at " - << Colour( Colour::Red ) << find(alias)->lineInfo << '\n' - << Colour( Colour::Red ) << "\tRedefined at " - << Colour( Colour::FileName) << lineInfo << '\n'; - throw std::domain_error( oss.str().c_str() ); + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd ); } } - ITagAliasRegistry::~ITagAliasRegistry() {} - - ITagAliasRegistry const& ITagAliasRegistry::get() { - return getRegistryHub().getTagAliasRegistry(); + bool WildcardPattern::matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == normaliseString( str ); + case WildcardAtStart: + return endsWith( normaliseString( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( normaliseString( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( normaliseString( str ), m_pattern ); + default: + CATCH_INTERNAL_ERROR( "Unknown enum" ); + } } - RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { - getMutableRegistryHub().registerTagAlias( alias, tag, lineInfo ); + std::string WildcardPattern::normaliseString( std::string const& str ) const { + return trim( m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str ); } +} +// end catch_wildcard_pattern.cpp +// start catch_xmlwriter.cpp -} // end namespace Catch - -// #included from: catch_matchers_string.hpp +#include <iomanip> +#include <type_traits> namespace Catch { -namespace Matchers { - namespace StdString { +namespace { - CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) - : m_caseSensitivity( caseSensitivity ), - m_str( adjustString( str ) ) - {} - std::string CasedString::adjustString( std::string const& str ) const { - return m_caseSensitivity == CaseSensitive::No - ? toLower( str ) - : str; + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; } - std::string CasedString::caseSensitivitySuffix() const { - return m_caseSensitivity == CaseSensitive::No - ? " (case insensitive)" - : std::string(); + if ((c & 0xF0) == 0xE0) { + return 3; } - - StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) - : m_comparator( comparator ), - m_operation( operation ) { + if ((c & 0xF8) == 0xF0) { + return 4; } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } - std::string StringMatcherBase::describe() const { - std::string description; - description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + - m_comparator.caseSensitivitySuffix().size()); - description += m_operation; - description += ": \""; - description += m_comparator.m_str; - description += "\""; - description += m_comparator.caseSensitivitySuffix(); - return description; + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } - EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast<int>(c); + os.flags(f); + } - bool EqualsMatcher::match( std::string const& source ) const { - return m_comparator.adjustString( source ) == m_comparator.m_str; - } + bool shouldNewline(XmlFormatting fmt) { + return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Newline)); + } - ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + bool shouldIndent(XmlFormatting fmt) { + return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Indent)); + } - bool ContainsMatcher::match( std::string const& source ) const { - return contains( m_comparator.adjustString( source ), m_comparator.m_str ); - } +} // anonymous namespace - StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs) { + return static_cast<XmlFormatting>( + static_cast<std::underlying_type<XmlFormatting>::type>(lhs) | + static_cast<std::underlying_type<XmlFormatting>::type>(rhs) + ); + } - bool StartsWithMatcher::match( std::string const& source ) const { - return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); - } + XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs) { + return static_cast<XmlFormatting>( + static_cast<std::underlying_type<XmlFormatting>::type>(lhs) & + static_cast<std::underlying_type<XmlFormatting>::type>(rhs) + ); + } - EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} - bool EndsWithMatcher::match( std::string const& source ) const { - return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); - } + 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) - } // namespace StdString + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + unsigned char c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; - StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); - } - StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); - } - StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); - } - StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); - } + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; -} // namespace Matchers -} // namespace Catch -// #included from: ../reporters/catch_reporter_multi.hpp -#define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; -namespace Catch { + default: + // Check for control characters and invalid utf-8 -class MultipleReporters : public SharedImpl<IStreamingReporter> { - typedef std::vector<Ptr<IStreamingReporter> > Reporters; - Reporters m_reporters; + // 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); + break; + } -public: - void add( Ptr<IStreamingReporter> const& reporter ) { - m_reporters.push_back( reporter ); - } + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + unsigned char nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } -public: // IStreamingReporter + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + (0x80 <= value && value < 0x800 && encBytes > 2) || + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } - virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { - return m_reporters[0]->getPreferences(); + // 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; + } + } } - virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - (*it)->noMatchingTestCases( spec ); + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; } - virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - (*it)->testRunStarting( testRunInfo ); + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer, XmlFormatting fmt ) + : m_writer( writer ), + m_fmt(fmt) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ), + m_fmt(other.m_fmt) + { + other.m_writer = nullptr; + other.m_fmt = XmlFormatting::None; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + m_fmt = other.m_fmt; + other.m_fmt = XmlFormatting::None; + return *this; } - virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - (*it)->testGroupStarting( groupInfo ); + XmlWriter::ScopedElement::~ScopedElement() { + if (m_writer) { + m_writer->endElement(m_fmt); + } } - virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - (*it)->testCaseStarting( testInfo ); + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, XmlFormatting fmt ) { + m_writer->writeText( text, fmt ); + return *this; } - virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - (*it)->sectionStarting( sectionInfo ); + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); } - virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - (*it)->assertionStarting( assertionInfo ); + XmlWriter::~XmlWriter() { + while (!m_tags.empty()) { + endElement(); + } + newlineIfNecessary(); } - // The return value indicates if the messages buffer should be cleared: - virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { - bool clearBuffer = false; - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - clearBuffer |= (*it)->assertionEnded( assertionStats ); - return clearBuffer; + XmlWriter& XmlWriter::startElement( std::string const& name, XmlFormatting fmt ) { + ensureTagClosed(); + newlineIfNecessary(); + if (shouldIndent(fmt)) { + m_os << m_indent; + m_indent += " "; + } + m_os << '<' << name; + m_tags.push_back( name ); + m_tagIsOpen = true; + applyFormatting(fmt); + return *this; } - virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - (*it)->sectionEnded( sectionStats ); + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name, XmlFormatting fmt ) { + ScopedElement scoped( this, fmt ); + startElement( name, fmt ); + return scoped; } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - (*it)->testCaseEnded( testCaseStats ); + XmlWriter& XmlWriter::endElement(XmlFormatting fmt) { + m_indent = m_indent.substr(0, m_indent.size() - 2); + + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } else { + newlineIfNecessary(); + if (shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << "</" << m_tags.back() << ">"; + } + m_os << std::flush; + applyFormatting(fmt); + m_tags.pop_back(); + return *this; } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - (*it)->testGroupEnded( testGroupStats ); + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; } - virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - (*it)->testRunEnded( testRunStats ); + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; } - virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { - for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); - it != itEnd; - ++it ) - (*it)->skipTest( testInfo ); + XmlWriter& XmlWriter::writeText( std::string const& text, XmlFormatting fmt) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if (tagWasOpen && shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << XmlEncode( text ); + applyFormatting(fmt); + } + return *this; } - virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE { - return this; + XmlWriter& XmlWriter::writeComment( std::string const& text, XmlFormatting fmt) { + ensureTagClosed(); + if (shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << "<!--" << text << "-->"; + applyFormatting(fmt); + return *this; } -}; + void XmlWriter::writeStylesheetRef( std::string const& url ) { + m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n"; + } -Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingReporter, Ptr<IStreamingReporter> const& additionalReporter ) { - Ptr<IStreamingReporter> resultingReporter; + XmlWriter& XmlWriter::writeBlankLine() { + ensureTagClosed(); + m_os << '\n'; + return *this; + } - if( existingReporter ) { - MultipleReporters* multi = existingReporter->tryAsMulti(); - if( !multi ) { - multi = new MultipleReporters; - resultingReporter = Ptr<IStreamingReporter>( multi ); - if( existingReporter ) - multi->add( existingReporter ); + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << '>' << std::flush; + newlineIfNecessary(); + m_tagIsOpen = false; } - else - resultingReporter = existingReporter; - multi->add( additionalReporter ); } - else - resultingReporter = additionalReporter; - return resultingReporter; -} - -} // end namespace Catch + void XmlWriter::applyFormatting(XmlFormatting fmt) { + m_needsNewline = shouldNewline(fmt); + } -// #included from: ../reporters/catch_reporter_xml.hpp -#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED + void XmlWriter::writeDeclaration() { + m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + } -// #included from: catch_reporter_bases.hpp -#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } +} +// end catch_xmlwriter.cpp +// start catch_reporter_bases.cpp #include <cstring> #include <cfloat> #include <cstdio> -#include <assert.h> +#include <cassert> +#include <memory> namespace Catch { + void prepareExpandedExpression(AssertionResult& result) { + result.getExpandedExpression(); + } - namespace { - // Because formatting using c++ streams is stateful, drop down to C is required - // Alternatively we could use stringstream, but its performance is... not good. - std::string getFormattedDuration( double duration ) { - // Max exponent + 1 is required to represent the whole part - // + 1 for decimal point - // + 3 for the 3 decimal places - // + 1 for null terminator - const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; - char buffer[maxDoubleSize]; - - // Save previous errno, to prevent sprintf from overwriting it - ErrnoGuard guard; + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; #ifdef _MSC_VER - sprintf_s(buffer, "%.3f", duration); + sprintf_s(buffer, "%.3f", duration); #else - sprintf(buffer, "%.3f", duration); + std::sprintf(buffer, "%.3f", duration); #endif - return std::string(buffer); - } + return std::string(buffer); } - struct StreamingReporterBase : SharedImpl<IStreamingReporter> { - - StreamingReporterBase( ReporterConfig const& _config ) - : m_config( _config.fullConfig() ), - stream( _config.stream() ) - { - m_reporterPrefs.shouldRedirectStdOut = false; - } - - virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { - return m_reporterPrefs; - } - - virtual ~StreamingReporterBase() CATCH_OVERRIDE; - - virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} - - virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { - currentTestRunInfo = _testRunInfo; + bool shouldShowDuration( IConfig const& config, double duration ) { + if ( config.showDurations() == ShowDurations::Always ) { + return true; } - virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { - currentGroupInfo = _groupInfo; + if ( config.showDurations() == ShowDurations::Never ) { + return false; } + const double min = config.minDuration(); + return min >= 0 && duration >= min; + } - virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { - currentTestCaseInfo = _testInfo; - } - virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { - m_sectionStack.push_back( _sectionInfo ); - } + std::string serializeFilters( std::vector<std::string> const& container ) { + ReusableStringStream oss; + bool first = true; + for (auto&& filter : container) + { + if (!first) + oss << ' '; + else + first = false; - virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { - m_sectionStack.pop_back(); - } - virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { - currentTestCaseInfo.reset(); - } - virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { - currentGroupInfo.reset(); - } - virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { - currentTestCaseInfo.reset(); - currentGroupInfo.reset(); - currentTestRunInfo.reset(); + oss << filter; } + return oss.str(); + } - virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { - // Don't do anything with this by default. - // It can optionally be overridden in the derived class. - } + TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) + :StreamingReporterBase(_config) {} - Ptr<IConfig const> m_config; - std::ostream& stream; + std::set<Verbosity> TestEventListenerBase::getSupportedVerbosities() { + return { Verbosity::Quiet, Verbosity::Normal, Verbosity::High }; + } - LazyStat<TestRunInfo> currentTestRunInfo; - LazyStat<GroupInfo> currentGroupInfo; - LazyStat<TestCaseInfo> currentTestCaseInfo; + void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} - std::vector<SectionInfo> m_sectionStack; - ReporterPreferences m_reporterPrefs; - }; + bool TestEventListenerBase::assertionEnded(AssertionStats const &) { + return false; + } - struct CumulativeReporterBase : SharedImpl<IStreamingReporter> { - template<typename T, typename ChildNodeT> - struct Node : SharedImpl<> { - explicit Node( T const& _value ) : value( _value ) {} - virtual ~Node() {} +} // end namespace Catch +// end catch_reporter_bases.cpp +// start catch_reporter_compact.cpp - typedef std::vector<Ptr<ChildNodeT> > ChildNodes; - T value; - ChildNodes children; - }; - struct SectionNode : SharedImpl<> { - explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} - virtual ~SectionNode(); +namespace { - bool operator == ( SectionNode const& other ) const { - return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; - } - bool operator == ( Ptr<SectionNode> const& other ) const { - return operator==( *other ); - } +#ifdef CATCH_PLATFORM_MAC + const char* failedString() { return "FAILED"; } + const char* passedString() { return "PASSED"; } +#else + const char* failedString() { return "failed"; } + const char* passedString() { return "passed"; } +#endif - SectionStats stats; - typedef std::vector<Ptr<SectionNode> > ChildSections; - typedef std::vector<AssertionStats> Assertions; - ChildSections childSections; - Assertions assertions; - std::string stdOut; - std::string stdErr; - }; + // Colour::LightGrey + Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } - struct BySectionInfo { - BySectionInfo( SectionInfo const& other ) : m_other( other ) {} - BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} - bool operator() ( Ptr<SectionNode> const& node ) const { - return ((node->stats.sectionInfo.name == m_other.name) && - (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); - } - private: - void operator=( BySectionInfo const& ); - SectionInfo const& m_other; - }; + std::string bothOrAll( std::size_t count ) { + return count == 1 ? std::string() : + count == 2 ? "both " : "all " ; + } - typedef Node<TestCaseStats, SectionNode> TestCaseNode; - typedef Node<TestGroupStats, TestCaseNode> TestGroupNode; - typedef Node<TestRunStats, TestGroupNode> TestRunNode; +} // anon namespace - CumulativeReporterBase( ReporterConfig const& _config ) - : m_config( _config.fullConfig() ), - stream( _config.stream() ) - { - m_reporterPrefs.shouldRedirectStdOut = false; - } - ~CumulativeReporterBase(); +namespace Catch { +namespace { +// Colour, message variants: +// - white: No tests ran. +// - red: Failed [both/all] N test cases, failed [both/all] M assertions. +// - white: Passed [both/all] N test cases (no assertions). +// - red: Failed N tests cases, failed M assertions. +// - green: Passed [both/all] N tests cases with M assertions. +void printTotals(std::ostream& out, const Totals& totals) { + if (totals.testCases.total() == 0) { + out << "No tests ran."; + } else if (totals.testCases.failed == totals.testCases.total()) { + Colour colour(Colour::ResultError); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll(totals.assertions.failed) : std::string(); + out << + "Failed " << bothOrAll(totals.testCases.failed) + << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << qualify_assertions_failed << + pluralise(totals.assertions.failed, "assertion") << '.'; + } else if (totals.assertions.total() == 0) { + out << + "Passed " << bothOrAll(totals.testCases.total()) + << pluralise(totals.testCases.total(), "test case") + << " (no assertions)."; + } else if (totals.assertions.failed) { + Colour colour(Colour::ResultError); + out << + "Failed " << pluralise(totals.testCases.failed, "test case") << ", " + "failed " << pluralise(totals.assertions.failed, "assertion") << '.'; + } else { + Colour colour(Colour::ResultSuccess); + out << + "Passed " << bothOrAll(totals.testCases.passed) + << pluralise(totals.testCases.passed, "test case") << + " with " << pluralise(totals.assertions.passed, "assertion") << '.'; + } +} - virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { - return m_reporterPrefs; +// Implementation of CompactReporter formatting +class AssertionPrinter { +public: + AssertionPrinter& operator= (AssertionPrinter const&) = delete; + AssertionPrinter(AssertionPrinter const&) = delete; + AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(_printInfoMessages) {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(Colour::ResultSuccess, passedString()); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) + printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok")); + else + printResultType(Colour::Error, failedString()); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(Colour::Error, failedString()); + printIssue("unexpected exception with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(Colour::Error, failedString()); + printIssue("fatal error condition with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(Colour::Error, failedString()); + printIssue("expected exception, got none"); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType(Colour::None, "info"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType(Colour::None, "warning"); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(Colour::Error, failedString()); + printIssue("explicitly"); + printRemainingMessages(Colour::None); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType(Colour::Error, "** internal error **"); + break; } + } - virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} - virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} - - virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} +private: + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ':'; + } - virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { - SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); - Ptr<SectionNode> node; - if( m_sectionStack.empty() ) { - if( !m_rootSection ) - m_rootSection = new SectionNode( incompleteStats ); - node = m_rootSection; - } - else { - SectionNode& parentNode = *m_sectionStack.back(); - SectionNode::ChildSections::const_iterator it = - std::find_if( parentNode.childSections.begin(), - parentNode.childSections.end(), - BySectionInfo( sectionInfo ) ); - if( it == parentNode.childSections.end() ) { - node = new SectionNode( incompleteStats ); - parentNode.childSections.push_back( node ); - } - else - node = *it; + void printResultType(Colour::Code colour, std::string const& passOrFail) const { + if (!passOrFail.empty()) { + { + Colour colourGuard(colour); + stream << ' ' << passOrFail; } - m_sectionStack.push_back( node ); - m_deepestSection = node; + stream << ':'; } + } - virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + void printIssue(std::string const& issue) const { + stream << ' ' << issue; + } - virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { - assert( !m_sectionStack.empty() ); - SectionNode& sectionNode = *m_sectionStack.back(); - sectionNode.assertions.push_back( assertionStats ); - // AssertionResult holds a pointer to a temporary DecomposedExpression, - // which getExpandedExpression() calls to build the expression string. - // Our section stack copy of the assertionResult will likely outlive the - // temporary, so it must be expanded or discarded now to avoid calling - // a destroyed object later. - prepareExpandedExpression( sectionNode.assertions.back().assertionResult ); - return true; - } - virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { - assert( !m_sectionStack.empty() ); - SectionNode& node = *m_sectionStack.back(); - node.stats = sectionStats; - m_sectionStack.pop_back(); + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + { + Colour colour(dimColour()); + stream << " expression was:"; + } + printOriginalExpression(); } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { - Ptr<TestCaseNode> node = new TestCaseNode( testCaseStats ); - assert( m_sectionStack.size() == 0 ); - node->children.push_back( m_rootSection ); - m_testCases.push_back( node ); - m_rootSection.reset(); + } - assert( m_deepestSection ); - m_deepestSection->stdOut = testCaseStats.stdOut; - m_deepestSection->stdErr = testCaseStats.stdErr; + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { - Ptr<TestGroupNode> node = new TestGroupNode( testGroupStats ); - node->children.swap( m_testCases ); - m_testGroups.push_back( node ); - } - virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { - Ptr<TestRunNode> node = new TestRunNode( testRunStats ); - node->children.swap( m_testGroups ); - m_testRuns.push_back( node ); - testRunEndedCumulative(); - } - virtual void testRunEndedCumulative() = 0; - - virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} - - virtual void prepareExpandedExpression( AssertionResult& result ) const { - if( result.isOk() ) - result.discardDecomposedExpression(); - else - result.expandDecomposedExpression(); - } - - Ptr<IConfig const> m_config; - std::ostream& stream; - std::vector<AssertionStats> m_assertions; - std::vector<std::vector<Ptr<SectionNode> > > m_sections; - std::vector<Ptr<TestCaseNode> > m_testCases; - std::vector<Ptr<TestGroupNode> > m_testGroups; - - std::vector<Ptr<TestRunNode> > m_testRuns; - - Ptr<SectionNode> m_rootSection; - Ptr<SectionNode> m_deepestSection; - std::vector<Ptr<SectionNode> > m_sectionStack; - ReporterPreferences m_reporterPrefs; - - }; + } - template<char C> - char const* getLineOfChars() { - static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; - if( !*line ) { - std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); - line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + { + Colour colour(dimColour()); + stream << " for: "; + } + stream << result.getExpandedExpression(); } - return line; } - struct TestEventListenerBase : StreamingReporterBase { - TestEventListenerBase( ReporterConfig const& _config ) - : StreamingReporterBase( _config ) - {} - - virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} - virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE { - return false; + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; } - }; + } -} // end namespace Catch + void printRemainingMessages(Colour::Code colour = dimColour()) { + if (itMessage == messages.end()) + return; -// #included from: ../internal/catch_reporter_registrars.hpp -#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + const auto itEnd = messages.cend(); + const auto N = static_cast<std::size_t>(std::distance(itMessage, itEnd)); -namespace Catch { - - template<typename T> - class LegacyReporterRegistrar { + { + Colour colourGuard(colour); + stream << " with " << pluralise(N, "message") << ':'; + } - class ReporterFactory : public IReporterFactory { - virtual IStreamingReporter* create( ReporterConfig const& config ) const { - return new LegacyReporterAdapter( new T( config ) ); + while (itMessage != itEnd) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + printMessage(); + if (itMessage != itEnd) { + Colour colourGuard(dimColour()); + stream << " and"; + } + continue; } + ++itMessage; + } + } - virtual std::string getDescription() const { - return T::getDescription(); - } - }; +private: + std::ostream& stream; + AssertionResult const& result; + std::vector<MessageInfo> messages; + std::vector<MessageInfo>::const_iterator itMessage; + bool printInfoMessages; +}; - public: +} // anon namespace - LegacyReporterRegistrar( std::string const& name ) { - getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + std::string CompactReporter::getDescription() { + return "Reports test results on a single line, suitable for IDEs"; } - }; - template<typename T> - class ReporterRegistrar { + void CompactReporter::noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } - class ReporterFactory : public SharedImpl<IReporterFactory> { + void CompactReporter::assertionStarting( AssertionInfo const& ) {} - // *** Please Note ***: - // - If you end up here looking at a compiler error because it's trying to register - // your custom reporter class be aware that the native reporter interface has changed - // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via - // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. - // However please consider updating to the new interface as the old one is now - // deprecated and will probably be removed quite soon! - // Please contact me via github if you have any questions at all about this. - // In fact, ideally, please contact me anyway to let me know you've hit this - as I have - // no idea who is actually using custom reporters at all (possibly no-one!). - // The new interface is designed to minimise exposure to interface changes in the future. - virtual IStreamingReporter* create( ReporterConfig const& config ) const { - return new T( config ); - } + bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; - virtual std::string getDescription() const { - return T::getDescription(); + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; } - }; - public: + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); - ReporterRegistrar( std::string const& name ) { - getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + stream << std::endl; + return true; } - }; - - template<typename T> - class ListenerRegistrar { - class ListenerFactory : public SharedImpl<IReporterFactory> { - - virtual IStreamingReporter* create( ReporterConfig const& config ) const { - return new T( config ); - } - virtual std::string getDescription() const { - return std::string(); + void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { + double dur = _sectionStats.durationInSeconds; + if ( shouldShowDuration( *m_config, dur ) ) { + stream << getFormattedDuration( dur ) << " s: " << _sectionStats.sectionInfo.name << std::endl; } - }; - - public: + } - ListenerRegistrar() { - getMutableRegistryHub().registerListener( new ListenerFactory() ); + void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( stream, _testRunStats.totals ); + stream << '\n' << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); } - }; -} -#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ - namespace{ Catch::LegacyReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } + CompactReporter::~CompactReporter() {} -#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ - namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } + CATCH_REGISTER_REPORTER( "compact", CompactReporter ) -// Deprecated - use the form without INTERNAL_ -#define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ - namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } +} // end namespace Catch +// end catch_reporter_compact.cpp +// start catch_reporter_console.cpp -#define CATCH_REGISTER_LISTENER( listenerType ) \ - namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } +#include <cfloat> +#include <cstdio> -// #included from: ../internal/catch_xmlwriter.hpp -#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled and default is missing) is enabled +#endif -#include <sstream> -#include <string> -#include <vector> -#include <iomanip> +#if defined(__clang__) +# pragma clang diagnostic push +// For simplicity, benchmarking-only helpers are always enabled +# pragma clang diagnostic ignored "-Wunused-function" +#endif namespace Catch { - class XmlEncode { - public: - enum ForWhat { ForTextNodes, ForAttributes }; - - XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) - : m_str( str ), - m_forWhat( forWhat ) - {} +namespace { - void 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 i = 0; i < m_str.size(); ++ i ) { - char c = m_str[i]; - switch( c ) { - case '<': os << "<"; break; - case '&': os << "&"; break; - - case '>': - // See: http://www.w3.org/TR/xml/#syntax - if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) - os << ">"; - else - os << c; - break; - - case '\"': - if( m_forWhat == ForAttributes ) - os << """; - else - os << c; - break; - - default: - // Escape control chars - based on contribution by @espenalb in PR #465 and - // by @mrpi PR #588 - if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) { - // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 - os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) - << static_cast<int>( c ); - } - else - os << c; - } +// Formatter impl for ConsoleReporter +class ConsoleAssertionPrinter { +public: + ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) + : stream(_stream), + stats(_stats), + result(_stats.assertionResult), + colour(Colour::None), + message(result.getMessage()), + messages(_stats.infoMessages), + printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } else { + colour = Colour::Error; + passOrFail = "FAILED"; } + if (_stats.infoMessages.size() == 1) + messageLabel = "with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if (_stats.infoMessages.size() == 1) + messageLabel = "explicitly with message"; + if (_stats.infoMessages.size() > 1) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; } + } - friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { - xmlEncode.encodeTo( os ); - return os; + void print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { + stream << '\n'; } + printMessage(); + } - private: - std::string m_str; - ForWhat m_forWhat; - }; - - class XmlWriter { - public: - - class ScopedElement { - public: - ScopedElement( XmlWriter* writer ) - : m_writer( writer ) - {} +private: + void printResultType() const { + if (!passOrFail.empty()) { + Colour colourGuard(colour); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if (result.hasExpression()) { + Colour colourGuard(Colour::OriginalExpression); + stream << " "; + stream << result.getExpressionInMacro(); + stream << '\n'; + } + } + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + Colour colourGuard(Colour::ReconstructedExpression); + stream << Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } + void printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const& msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << Column(msg.message).indent(2) << '\n'; + } + } + void printSourceInfo() const { + Colour colourGuard(Colour::FileName); + stream << result.getSourceInfo() << ": "; + } - ScopedElement( ScopedElement const& other ) - : m_writer( other.m_writer ){ - other.m_writer = CATCH_NULL; - } + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector<MessageInfo> messages; + bool printInfoMessages; +}; - ~ScopedElement() { - if( m_writer ) - m_writer->endElement(); - } +std::size_t makeRatio(std::size_t number, std::size_t total) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : ratio; +} - ScopedElement& writeText( std::string const& text, bool indent = true ) { - m_writer->writeText( text, indent ); - return *this; - } +std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) { + if (i > j && i > k) + return i; + else if (j > k) + return j; + else + return k; +} - template<typename T> - ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { - m_writer->writeAttribute( name, attribute ); - return *this; - } +struct ColumnInfo { + enum Justification { Left, Right }; + std::string name; + int width; + Justification justification; +}; +struct ColumnBreak {}; +struct RowBreak {}; - private: - mutable XmlWriter* m_writer; - }; +class Duration { + enum class Unit { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; - XmlWriter() - : m_tagIsOpen( false ), - m_needsNewline( false ), - m_os( Catch::cout() ) - { - writeDeclaration(); - } + double m_inNanoseconds; + Unit m_units; - XmlWriter( std::ostream& os ) - : m_tagIsOpen( false ), - m_needsNewline( false ), - m_os( os ) - { - writeDeclaration(); +public: + explicit Duration(double inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), + m_units(units) { + if (m_units == Unit::Auto) { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; } - ~XmlWriter() { - while( !m_tags.empty() ) - endElement(); - } + } - XmlWriter& startElement( std::string const& name ) { - ensureTagClosed(); - newlineIfNecessary(); - m_os << m_indent << '<' << name; - m_tags.push_back( name ); - m_indent += " "; - m_tagIsOpen = true; - return *this; + auto value() const -> double { + switch (m_units) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute); + default: + return m_inNanoseconds; } - - ScopedElement scopedElement( std::string const& name ) { - ScopedElement scoped( this ); - startElement( name ); - return scoped; + } + auto unitsAsString() const -> std::string { + switch (m_units) { + case Unit::Nanoseconds: + return "ns"; + case Unit::Microseconds: + return "us"; + case Unit::Milliseconds: + return "ms"; + case Unit::Seconds: + return "s"; + case Unit::Minutes: + return "m"; + default: + return "** internal error **"; } - XmlWriter& endElement() { - newlineIfNecessary(); - m_indent = m_indent.substr( 0, m_indent.size()-2 ); - if( m_tagIsOpen ) { - m_os << "/>"; - m_tagIsOpen = false; - } - else { - m_os << m_indent << "</" << m_tags.back() << ">"; - } - m_os << std::endl; - m_tags.pop_back(); - return *this; - } + } + friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { + return os << duration.value() << ' ' << duration.unitsAsString(); + } +}; +} // end anon namespace - XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { - if( !name.empty() && !attribute.empty() ) - m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; - return *this; - } +class TablePrinter { + std::ostream& m_os; + std::vector<ColumnInfo> m_columnInfos; + std::ostringstream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; - XmlWriter& writeAttribute( std::string const& name, bool attribute ) { - m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; - return *this; - } +public: + TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos ) + : m_os( os ), + m_columnInfos( std::move( columnInfos ) ) {} - template<typename T> - XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { - std::ostringstream oss; - oss << attribute; - return writeAttribute( name, oss.str() ); - } + auto columnInfos() const -> std::vector<ColumnInfo> const& { + return m_columnInfos; + } - XmlWriter& writeText( std::string const& text, bool indent = true ) { - if( !text.empty() ){ - bool tagWasOpen = m_tagIsOpen; - ensureTagClosed(); - if( tagWasOpen && indent ) - m_os << m_indent; - m_os << XmlEncode( text ); - m_needsNewline = true; - } - return *this; - } + void open() { + if (!m_isOpen) { + m_isOpen = true; + *this << RowBreak(); - XmlWriter& writeComment( std::string const& text ) { - ensureTagClosed(); - m_os << m_indent << "<!--" << text << "-->"; - m_needsNewline = true; - return *this; - } + Columns headerCols; + Spacer spacer(2); + for (auto const& info : m_columnInfos) { + headerCols += Column(info.name).width(static_cast<std::size_t>(info.width - 2)); + headerCols += spacer; + } + m_os << headerCols << '\n'; - void writeStylesheetRef( std::string const& url ) { - m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n"; + m_os << Catch::getLineOfChars<'-'>() << '\n'; } - - XmlWriter& writeBlankLine() { - ensureTagClosed(); - m_os << '\n'; - return *this; + } + void close() { + if (m_isOpen) { + *this << RowBreak(); + m_os << std::endl; + m_isOpen = false; } + } - void ensureTagClosed() { - if( m_tagIsOpen ) { - m_os << ">" << std::endl; - m_tagIsOpen = false; - } - } + template<typename T> + friend TablePrinter& operator << (TablePrinter& tp, T const& value) { + tp.m_oss << value; + return tp; + } - private: - XmlWriter( XmlWriter const& ); - void operator=( XmlWriter const& ); + friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) { + auto colStr = tp.m_oss.str(); + const auto strSize = colStr.size(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) { + tp.m_currentColumn = -1; + tp.m_os << '\n'; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 1 < static_cast<std::size_t>(colInfo.width)) + ? std::string(colInfo.width - (strSize + 1), ' ') + : std::string(); + if (colInfo.justification == ColumnInfo::Left) + tp.m_os << colStr << padding << ' '; + else + tp.m_os << padding << colStr << ' '; + return tp; + } - void writeDeclaration() { - m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + friend TablePrinter& operator << (TablePrinter& tp, RowBreak) { + if (tp.m_currentColumn > 0) { + tp.m_os << '\n'; + tp.m_currentColumn = -1; } + return tp; + } +}; - void newlineIfNecessary() { - if( m_needsNewline ) { - m_os << std::endl; - m_needsNewline = false; - } +ConsoleReporter::ConsoleReporter(ReporterConfig const& config) + : StreamingReporterBase(config), + m_tablePrinter(new TablePrinter(config.stream(), + [&config]() -> std::vector<ColumnInfo> { + if (config.fullConfig()->benchmarkNoAnalysis()) + { + return{ + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, ColumnInfo::Left }, + { " samples", 14, ColumnInfo::Right }, + { " iterations", 14, ColumnInfo::Right }, + { " mean", 14, ColumnInfo::Right } + }; } - - bool m_tagIsOpen; - bool m_needsNewline; - std::vector<std::string> m_tags; - std::string m_indent; - std::ostream& m_os; - }; - -} - -namespace Catch { - class XmlReporter : public StreamingReporterBase { - public: - XmlReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ), - m_xml(_config.stream()), - m_sectionDepth( 0 ) + else { - m_reporterPrefs.shouldRedirectStdOut = true; + return{ + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, ColumnInfo::Left }, + { "samples mean std dev", 14, ColumnInfo::Right }, + { "iterations low mean low std dev", 14, ColumnInfo::Right }, + { "estimated high mean high std dev", 14, ColumnInfo::Right } + }; } + }())) {} +ConsoleReporter::~ConsoleReporter() = default; - virtual ~XmlReporter() CATCH_OVERRIDE; +std::string ConsoleReporter::getDescription() { + return "Reports test results as plain lines of text"; +} - static std::string getDescription() { - return "Reports test results as an XML document"; - } +void ConsoleReporter::noMatchingTestCases(std::string const& spec) { + stream << "No test cases matched '" << spec << '\'' << std::endl; +} - virtual std::string getStylesheetRef() const { - return std::string(); - } +void ConsoleReporter::reportInvalidArguments(std::string const&arg){ + stream << "Invalid Filter: " << arg << std::endl; +} - void writeSourceInfo( SourceLineInfo const& sourceInfo ) { - m_xml - .writeAttribute( "filename", sourceInfo.file ) - .writeAttribute( "line", sourceInfo.line ); - } +void ConsoleReporter::assertionStarting(AssertionInfo const&) {} - public: // StreamingReporterBase +bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { + AssertionResult const& result = _assertionStats.assertionResult; - virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { - StreamingReporterBase::noMatchingTestCases( s ); - } + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { - StreamingReporterBase::testRunStarting( testInfo ); - std::string stylesheetRef = getStylesheetRef(); - if( !stylesheetRef.empty() ) - m_xml.writeStylesheetRef( stylesheetRef ); - m_xml.startElement( "Catch" ); - if( !m_config->name().empty() ) - m_xml.writeAttribute( "name", m_config->name() ); - } + // Drop out if result was successful but we're not printing them. + if (!includeResults && result.getResultType() != ResultWas::Warning) + return false; - virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { - StreamingReporterBase::testGroupStarting( groupInfo ); - m_xml.startElement( "Group" ) - .writeAttribute( "name", groupInfo.name ); - } + lazyPrint(); - virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { - StreamingReporterBase::testCaseStarting(testInfo); - m_xml.startElement( "TestCase" ) - .writeAttribute( "name", trim( testInfo.name ) ) - .writeAttribute( "description", testInfo.description ) - .writeAttribute( "tags", testInfo.tagsAsString ); + ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); + printer.print(); + stream << std::endl; + return true; +} - writeSourceInfo( testInfo.lineInfo ); +void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { + m_tablePrinter->close(); + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); +} +void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { + m_tablePrinter->close(); + if (_sectionStats.missingAssertions) { + lazyPrint(); + Colour colour(Colour::ResultError); + if (m_sectionStack.size() > 1) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + double dur = _sectionStats.durationInSeconds; + if (shouldShowDuration(*m_config, dur)) { + stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if (m_headerPrinted) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); +} - if ( m_config->showDurations() == ShowDurations::Always ) - m_testCaseTimer.start(); - m_xml.ensureTagClosed(); - } +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +void ConsoleReporter::benchmarkPreparing(std::string const& name) { + lazyPrintWithoutClosingBenchmarkTable(); - virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { - StreamingReporterBase::sectionStarting( sectionInfo ); - if( m_sectionDepth++ > 0 ) { - m_xml.startElement( "Section" ) - .writeAttribute( "name", trim( sectionInfo.name ) ) - .writeAttribute( "description", sectionInfo.description ); - writeSourceInfo( sectionInfo.lineInfo ); - m_xml.ensureTagClosed(); - } - } + auto nameCol = Column(name).width(static_cast<std::size_t>(m_tablePrinter->columnInfos()[0].width - 2)); - virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; - virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + (*m_tablePrinter) << line << ColumnBreak(); + } +} - AssertionResult const& result = assertionStats.assertionResult; +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { + (*m_tablePrinter) << info.samples << ColumnBreak() + << info.iterations << ColumnBreak(); + if (!m_config->benchmarkNoAnalysis()) + (*m_tablePrinter) << Duration(info.estimatedDuration) << ColumnBreak(); +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) { + if (m_config->benchmarkNoAnalysis()) + { + (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak(); + } + else + { + (*m_tablePrinter) << ColumnBreak() + << Duration(stats.mean.point.count()) << ColumnBreak() + << Duration(stats.mean.lower_bound.count()) << ColumnBreak() + << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak() + << Duration(stats.standardDeviation.point.count()) << ColumnBreak() + << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak() + << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak(); + } +} - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); +void ConsoleReporter::benchmarkFailed(std::string const& error) { + Colour colour(Colour::Red); + (*m_tablePrinter) + << "Benchmark failed (" << error << ')' + << ColumnBreak() << RowBreak(); +} +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING - if( includeResults ) { - // Print any info messages in <Info> tags. - for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); - it != itEnd; - ++it ) { - if( it->type == ResultWas::Info ) { - m_xml.scopedElement( "Info" ) - .writeText( it->message ); - } else if ( it->type == ResultWas::Warning ) { - m_xml.scopedElement( "Warning" ) - .writeText( it->message ); - } - } - } +void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; +} +void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) { + if (currentGroupInfo.used) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals(_testGroupStats.totals); + stream << '\n' << std::endl; + } + StreamingReporterBase::testGroupEnded(_testGroupStats); +} +void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { + printTotalsDivider(_testRunStats.totals); + printTotals(_testRunStats.totals); + stream << std::endl; + StreamingReporterBase::testRunEnded(_testRunStats); +} +void ConsoleReporter::testRunStarting(TestRunInfo const& _testInfo) { + StreamingReporterBase::testRunStarting(_testInfo); + printTestFilters(); +} - // Drop out if result was successful but we're not printing them. - if( !includeResults && result.getResultType() != ResultWas::Warning ) - return true; +void ConsoleReporter::lazyPrint() { - // Print the expression if there is one. - if( result.hasExpression() ) { - m_xml.startElement( "Expression" ) - .writeAttribute( "success", result.succeeded() ) - .writeAttribute( "type", result.getTestMacroName() ); + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); +} - writeSourceInfo( result.getSourceInfo() ); +void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { - m_xml.scopedElement( "Original" ) - .writeText( result.getExpression() ); - m_xml.scopedElement( "Expanded" ) - .writeText( result.getExpandedExpression() ); - } + if (!currentTestRunInfo.used) + lazyPrintRunInfo(); + if (!currentGroupInfo.used) + lazyPrintGroupInfo(); - // And... Print a result applicable to each result type. - switch( result.getResultType() ) { - case ResultWas::ThrewException: - m_xml.startElement( "Exception" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - case ResultWas::FatalErrorCondition: - m_xml.startElement( "FatalErrorCondition" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - case ResultWas::Info: - m_xml.scopedElement( "Info" ) - .writeText( result.getMessage() ); - break; - case ResultWas::Warning: - // Warning will already have been written - break; - case ResultWas::ExplicitFailure: - m_xml.startElement( "Failure" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - default: - break; - } + if (!m_headerPrinted) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } +} +void ConsoleReporter::lazyPrintRunInfo() { + stream << '\n' << getLineOfChars<'~'>() << '\n'; + Colour colour(Colour::SecondaryText); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion() << " host application.\n" + << "Run with -? for options\n\n"; - if( result.hasExpression() ) - m_xml.endElement(); + if (m_config->rngSeed() != 0) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; - return true; - } + currentTestRunInfo.used = true; +} +void ConsoleReporter::lazyPrintGroupInfo() { + if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) { + printClosedHeader("Group: " + currentGroupInfo->name); + currentGroupInfo.used = true; + } +} +void ConsoleReporter::printTestCaseAndSectionHeader() { + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) { + Colour colourGuard(Colour::Headers); + + auto + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(it->name, 2); + } - virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { - StreamingReporterBase::sectionEnded( sectionStats ); - if( --m_sectionDepth > 0 ) { - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); - e.writeAttribute( "successes", sectionStats.assertions.passed ); - e.writeAttribute( "failures", sectionStats.assertions.failed ); - e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard(Colour::FileName); + stream << lineInfo << '\n'; + stream << getLineOfChars<'.'>() << '\n' << std::endl; +} - m_xml.endElement(); - } - } +void ConsoleReporter::printClosedHeader(std::string const& _name) { + printOpenHeader(_name); + stream << getLineOfChars<'.'>() << '\n'; +} +void ConsoleReporter::printOpenHeader(std::string const& _name) { + stream << getLineOfChars<'-'>() << '\n'; + { + Colour colourGuard(Colour::Headers); + printHeaderString(_name); + } +} - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { - StreamingReporterBase::testCaseEnded( testCaseStats ); - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); - e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); +// if string has a : in first line will set indent to follow it on +// subsequent lines +void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; +} - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); +struct SummaryColumn { + + SummaryColumn( std::string _label, Colour::Code _colour ) + : label( std::move( _label ) ), + colour( _colour ) {} + SummaryColumn addRow( std::size_t count ) { + ReusableStringStream rss; + rss << count; + std::string row = rss.str(); + for (auto& oldRow : rows) { + while (oldRow.size() < row.size()) + oldRow = ' ' + oldRow; + while (oldRow.size() > row.size()) + row = ' ' + row; + } + rows.push_back(row); + return *this; + } - if( !testCaseStats.stdOut.empty() ) - m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); - if( !testCaseStats.stdErr.empty() ) - m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + std::string label; + Colour::Code colour; + std::vector<std::string> rows; - m_xml.endElement(); - } +}; - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { - StreamingReporterBase::testGroupEnded( testGroupStats ); - // TODO: Check testGroupStats.aborting and act accordingly. - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) - .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); - m_xml.endElement(); +void ConsoleReporter::printTotals( Totals const& totals ) { + if (totals.testCases.total() == 0) { + stream << Colour(Colour::Warning) << "No tests ran\n"; + } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { + stream << Colour(Colour::ResultSuccess) << "All tests passed"; + stream << " (" + << pluralise(totals.assertions.passed, "assertion") << " in " + << pluralise(totals.testCases.passed, "test case") << ')' + << '\n'; + } else { + + std::vector<SummaryColumn> columns; + columns.push_back(SummaryColumn("", Colour::None) + .addRow(totals.testCases.total()) + .addRow(totals.assertions.total())); + columns.push_back(SummaryColumn("passed", Colour::Success) + .addRow(totals.testCases.passed) + .addRow(totals.assertions.passed)); + columns.push_back(SummaryColumn("failed", Colour::ResultError) + .addRow(totals.testCases.failed) + .addRow(totals.assertions.failed)); + columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure) + .addRow(totals.testCases.failedButOk) + .addRow(totals.assertions.failedButOk)); + + printSummaryRow("test cases", columns, 0); + printSummaryRow("assertions", columns, 1); + } +} +void ConsoleReporter::printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row) { + for (auto col : cols) { + std::string value = col.rows[row]; + if (col.label.empty()) { + stream << label << ": "; + if (value != "0") + stream << value; + else + stream << Colour(Colour::Warning) << "- none -"; + } else if (value != "0") { + stream << Colour(Colour::LightGrey) << " | "; + stream << Colour(col.colour) + << value << ' ' << col.label; } + } + stream << '\n'; +} - virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { - StreamingReporterBase::testRunEnded( testRunStats ); - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testRunStats.totals.assertions.passed ) - .writeAttribute( "failures", testRunStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); - m_xml.endElement(); - } +void ConsoleReporter::printTotalsDivider(Totals const& totals) { + if (totals.testCases.total() > 0) { + std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); + std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); + std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); + while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)++; + while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio)--; + + stream << Colour(Colour::Error) << std::string(failedRatio, '='); + stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '='); + if (totals.testCases.allPassed()) + stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '='); + else + stream << Colour(Colour::Success) << std::string(passedRatio, '='); + } else { + stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); + } + stream << '\n'; +} +void ConsoleReporter::printSummaryDivider() { + stream << getLineOfChars<'-'>() << '\n'; +} - private: - Timer m_testCaseTimer; - XmlWriter m_xml; - int m_sectionDepth; - }; +void ConsoleReporter::printTestFilters() { + if (m_config->testSpec().hasFilters()) { + Colour guard(Colour::BrightYellow); + stream << "Filters: " << serializeFilters(m_config->getTestsOrTags()) << '\n'; + } +} - INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) +CATCH_REGISTER_REPORTER("console", ConsoleReporter) } // end namespace Catch -// #included from: ../reporters/catch_reporter_junit.hpp -#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED +#if defined(_MSC_VER) +#pragma warning(pop) +#endif -#include <assert.h> +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_reporter_console.cpp +// start catch_reporter_junit.cpp + +#include <cassert> +#include <sstream> +#include <ctime> +#include <algorithm> +#include <iomanip> namespace Catch { @@ -10351,7 +16803,7 @@ namespace Catch { // Also, UTC only, again because of backward compatibility (%z is C++11) time_t rawtime; std::time(&rawtime); - const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z"); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); #ifdef _MSC_VER std::tm timeInfo = {}; @@ -10369,1017 +16821,701 @@ namespace Catch { #else std::strftime(timeStamp, timeStampSize, fmt, timeInfo); #endif - return std::string(timeStamp); - } - - } - - class JunitReporter : public CumulativeReporterBase { - public: - JunitReporter( ReporterConfig const& _config ) - : CumulativeReporterBase( _config ), - xml( _config.stream() ), - unexpectedExceptions( 0 ), - m_okToFail( false ) - { - m_reporterPrefs.shouldRedirectStdOut = true; - } - - virtual ~JunitReporter() CATCH_OVERRIDE; - - static std::string getDescription() { - return "Reports test results in an XML format that looks like Ant's junitreport target"; - } - - virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} - - virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { - CumulativeReporterBase::testRunStarting( runInfo ); - xml.startElement( "testsuites" ); - } - - virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { - suiteTimer.start(); - stdOutForSuite.str(""); - stdErrForSuite.str(""); - unexpectedExceptions = 0; - CumulativeReporterBase::testGroupStarting( groupInfo ); + return std::string(timeStamp, timeStampSize-1); } - virtual void testCaseStarting( TestCaseInfo const& testCaseInfo ) CATCH_OVERRIDE { - m_okToFail = testCaseInfo.okToFail(); - } - virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { - if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) - unexpectedExceptions++; - return CumulativeReporterBase::assertionEnded( assertionStats ); + std::string fileNameTag(const std::vector<std::string> &tags) { + auto it = std::find_if(begin(tags), + end(tags), + [] (std::string const& tag) {return tag.front() == '#'; }); + if (it != tags.end()) + return it->substr(1); + return std::string(); } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { - stdOutForSuite << testCaseStats.stdOut; - stdErrForSuite << testCaseStats.stdErr; - CumulativeReporterBase::testCaseEnded( testCaseStats ); + // Formats the duration in seconds to 3 decimal places. + // This is done because some genius defined Maven Surefire schema + // in a way that only accepts 3 decimal places, and tools like + // Jenkins use that schema for validation JUnit reporter output. + std::string formatDuration( double seconds ) { + ReusableStringStream rss; + rss << std::fixed << std::setprecision( 3 ) << seconds; + return rss.str(); } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { - double suiteTime = suiteTimer.getElapsedSeconds(); - CumulativeReporterBase::testGroupEnded( testGroupStats ); - writeGroup( *m_testGroups.back(), suiteTime ); - } + } // anonymous namespace - virtual void testRunEndedCumulative() CATCH_OVERRIDE { - xml.endElement(); + JunitReporter::JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; } - void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); - TestGroupStats const& stats = groupNode.value; - xml.writeAttribute( "name", stats.groupInfo.name ); - xml.writeAttribute( "errors", unexpectedExceptions ); - xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); - xml.writeAttribute( "tests", stats.totals.assertions.total() ); - xml.writeAttribute( "hostname", "tbd" ); // !TBD - if( m_config->showDurations() == ShowDurations::Never ) - xml.writeAttribute( "time", "" ); - else - xml.writeAttribute( "time", suiteTime ); - xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + JunitReporter::~JunitReporter() {} - // Write test cases - for( TestGroupNode::ChildNodes::const_iterator - it = groupNode.children.begin(), itEnd = groupNode.children.end(); - it != itEnd; - ++it ) - writeTestCase( **it ); + std::string JunitReporter::getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } - xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); - xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); - } + void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {} - void writeTestCase( TestCaseNode const& testCaseNode ) { - TestCaseStats const& stats = testCaseNode.value; + void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } - // All test cases have exactly one section - which represents the - // test case itself. That section may have 0-n nested sections - assert( testCaseNode.children.size() == 1 ); - SectionNode const& rootSection = *testCaseNode.children.front(); + void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } - std::string className = stats.testInfo.className; + void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { + m_okToFail = testCaseInfo.okToFail(); + } - if( className.empty() ) { - if( rootSection.childSections.empty() ) - className = "global"; - } - writeSection( className, "", rootSection ); - } - - void writeSection( std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode ) { - std::string name = trim( sectionNode.stats.sectionInfo.name ); - if( !rootName.empty() ) - name = rootName + '/' + name; - - if( !sectionNode.assertions.empty() || - !sectionNode.stdOut.empty() || - !sectionNode.stdErr.empty() ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); - if( className.empty() ) { - xml.writeAttribute( "classname", name ); - xml.writeAttribute( "name", "root" ); - } - else { - xml.writeAttribute( "classname", className ); - xml.writeAttribute( "name", name ); - } - xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); - - writeAssertions( sectionNode ); - - if( !sectionNode.stdOut.empty() ) - xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); - if( !sectionNode.stdErr.empty() ) - xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); - } - for( SectionNode::ChildSections::const_iterator - it = sectionNode.childSections.begin(), - itEnd = sectionNode.childSections.end(); - it != itEnd; - ++it ) - if( className.empty() ) - writeSection( name, "", **it ); - else - writeSection( className, name, **it ); - } - - void writeAssertions( SectionNode const& sectionNode ) { - for( SectionNode::Assertions::const_iterator - it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); - it != itEnd; - ++it ) - writeAssertion( *it ); - } - void writeAssertion( AssertionStats const& stats ) { - AssertionResult const& result = stats.assertionResult; - if( !result.isOk() ) { - std::string elementName; - switch( result.getResultType() ) { - case ResultWas::ThrewException: - case ResultWas::FatalErrorCondition: - elementName = "error"; - break; - case ResultWas::ExplicitFailure: - elementName = "failure"; - break; - case ResultWas::ExpressionFailed: - elementName = "failure"; - break; - case ResultWas::DidntThrowException: - elementName = "failure"; - break; - - // We should never see these here: - case ResultWas::Info: - case ResultWas::Warning: - case ResultWas::Ok: - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - elementName = "internalError"; - break; - } + bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } - XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } - xml.writeAttribute( "message", result.getExpandedExpression() ); - xml.writeAttribute( "type", result.getTestMacroName() ); + void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } - std::ostringstream oss; - if( !result.getMessage().empty() ) - oss << result.getMessage() << '\n'; - for( std::vector<MessageInfo>::const_iterator - it = stats.infoMessages.begin(), - itEnd = stats.infoMessages.end(); - it != itEnd; - ++it ) - if( it->type == ResultWas::Info ) - oss << it->message << '\n'; + void JunitReporter::testRunEndedCumulative() { + xml.endElement(); + } - oss << "at " << result.getSourceInfo(); - xml.writeText( oss.str(), false ); + void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", formatDuration( suiteTime ) ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write properties if there are any + if (m_config->hasTestFilters() || m_config->rngSeed() != 0) { + auto properties = xml.scopedElement("properties"); + if (m_config->hasTestFilters()) { + xml.scopedElement("property") + .writeAttribute("name", "filters") + .writeAttribute("value", serializeFilters(m_config->getTestsOrTags())); + } + if (m_config->rngSeed() != 0) { + xml.scopedElement("property") + .writeAttribute("name", "random-seed") + .writeAttribute("value", m_config->rngSeed()); } } - XmlWriter xml; - Timer suiteTimer; - std::ostringstream stdOutForSuite; - std::ostringstream stdErrForSuite; - unsigned int unexpectedExceptions; - bool m_okToFail; - }; - - INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) - -} // end namespace Catch - -// #included from: ../reporters/catch_reporter_console.hpp -#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED - -#include <cfloat> -#include <cstdio> + // Write test cases + for( auto const& child : groupNode.children ) + writeTestCase( *child ); -namespace Catch { + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), XmlFormatting::Newline ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), XmlFormatting::Newline ); + } - struct ConsoleReporter : StreamingReporterBase { - ConsoleReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ), - m_headerPrinted( false ) - {} + void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; - virtual ~ConsoleReporter() CATCH_OVERRIDE; - static std::string getDescription() { - return "Reports test results as plain lines of text"; - } + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); - virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { - stream << "No test cases matched '" << spec << '\'' << std::endl; - } + std::string className = stats.testInfo.className; - virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { + if( className.empty() ) { + className = fileNameTag(stats.testInfo.tags); + if ( className.empty() ) + className = "global"; } - virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { - AssertionResult const& result = _assertionStats.assertionResult; - - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - // Drop out if result was successful but we're not printing them. - if( !includeResults && result.getResultType() != ResultWas::Warning ) - return false; + if ( !m_config->name().empty() ) + className = m_config->name() + "." + className; - lazyPrint(); - - AssertionPrinter printer( stream, _assertionStats, includeResults ); - printer.print(); - stream << std::endl; - return true; - } + writeSection( className, "", rootSection, stats.testInfo.okToFail() ); + } - virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { - m_headerPrinted = false; - StreamingReporterBase::sectionStarting( _sectionInfo ); - } - virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { - if( _sectionStats.missingAssertions ) { - lazyPrint(); - Colour colour( Colour::ResultError ); - if( m_sectionStack.size() > 1 ) - stream << "\nNo assertions in section"; - else - stream << "\nNo assertions in test case"; - stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); } - if( m_config->showDurations() == ShowDurations::Always ) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); } - if( m_headerPrinted ) { - m_headerPrinted = false; + xml.writeAttribute( "time", formatDuration( sectionNode.stats.durationInSeconds ) ); + // This is not ideal, but it should be enough to mimic gtest's + // junit output. + // Ideally the JUnit reporter would also handle `skipTest` + // events and write those out appropriately. + xml.writeAttribute( "status", "run" ); + + if (sectionNode.stats.assertions.failedButOk) { + xml.scopedElement("skipped") + .writeAttribute("message", "TEST_CASE tagged with !mayfail"); } - StreamingReporterBase::sectionEnded( _sectionStats ); - } - virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { - StreamingReporterBase::testCaseEnded( _testCaseStats ); - m_headerPrinted = false; - } - virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { - if( currentGroupInfo.used ) { - printSummaryDivider(); - stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; - printTotals( _testGroupStats.totals ); - stream << '\n' << std::endl; - } - StreamingReporterBase::testGroupEnded( _testGroupStats ); - } - virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { - printTotalsDivider( _testRunStats.totals ); - printTotals( _testRunStats.totals ); - stream << std::endl; - StreamingReporterBase::testRunEnded( _testRunStats ); + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), XmlFormatting::Newline ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), XmlFormatting::Newline ); } + for( auto const& childNode : sectionNode.childSections ) + if( className.empty() ) + writeSection( name, "", *childNode, testOkToFail ); + else + writeSection( className, name, *childNode, testOkToFail ); + } - private: + void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { + for( auto const& assertion : sectionNode.assertions ) + writeAssertion( assertion ); + } - class AssertionPrinter { - void operator= ( AssertionPrinter const& ); - public: - AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) - : stream( _stream ), - stats( _stats ), - result( _stats.assertionResult ), - colour( Colour::None ), - message( result.getMessage() ), - messages( _stats.infoMessages ), - printInfoMessages( _printInfoMessages ) - { - switch( result.getResultType() ) { - case ResultWas::Ok: - colour = Colour::Success; - passOrFail = "PASSED"; - //if( result.hasMessage() ) - if( _stats.infoMessages.size() == 1 ) - messageLabel = "with message"; - if( _stats.infoMessages.size() > 1 ) - messageLabel = "with messages"; - break; - case ResultWas::ExpressionFailed: - if( result.isOk() ) { - colour = Colour::Success; - passOrFail = "FAILED - but was ok"; - } - else { - colour = Colour::Error; - passOrFail = "FAILED"; - } - if( _stats.infoMessages.size() == 1 ) - messageLabel = "with message"; - if( _stats.infoMessages.size() > 1 ) - messageLabel = "with messages"; - break; - case ResultWas::ThrewException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to unexpected exception with "; - if (_stats.infoMessages.size() == 1) - messageLabel += "message"; - if (_stats.infoMessages.size() > 1) - messageLabel += "messages"; - break; - case ResultWas::FatalErrorCondition: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to a fatal error condition"; - break; - case ResultWas::DidntThrowException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "because no exception was thrown where one was expected"; - break; - case ResultWas::Info: - messageLabel = "info"; - break; - case ResultWas::Warning: - messageLabel = "warning"; - break; - case ResultWas::ExplicitFailure: - passOrFail = "FAILED"; - colour = Colour::Error; - if( _stats.infoMessages.size() == 1 ) - messageLabel = "explicitly with message"; - if( _stats.infoMessages.size() > 1 ) - messageLabel = "explicitly with messages"; - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - passOrFail = "** internal error **"; - colour = Colour::Error; - break; - } - } + void JunitReporter::writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + case ResultWas::ExpressionFailed: + case ResultWas::DidntThrowException: + elementName = "failure"; + break; - void print() const { - printSourceInfo(); - if( stats.totals.assertions.total() > 0 ) { - if( result.isOk() ) - stream << '\n'; - printResultType(); - printOriginalExpression(); - printReconstructedExpression(); - } - else { - stream << '\n'; - } - printMessage(); + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; } - private: - void printResultType() const { - if( !passOrFail.empty() ) { - Colour colourGuard( colour ); - stream << passOrFail << ":\n"; - } - } - void printOriginalExpression() const { - if( result.hasExpression() ) { - Colour colourGuard( Colour::OriginalExpression ); - stream << " "; - stream << result.getExpressionInMacro(); - stream << '\n'; - } - } - void printReconstructedExpression() const { - if( result.hasExpandedExpression() ) { - stream << "with expansion:\n"; - Colour colourGuard( Colour::ReconstructedExpression ); - stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << '\n'; + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + ReusableStringStream rss; + if (stats.totals.assertions.total() > 0) { + rss << "FAILED" << ":\n"; + if (result.hasExpression()) { + rss << " "; + rss << result.getExpressionInMacro(); + rss << '\n'; } - } - void printMessage() const { - if( !messageLabel.empty() ) - stream << messageLabel << ':' << '\n'; - for( std::vector<MessageInfo>::const_iterator it = messages.begin(), itEnd = messages.end(); - it != itEnd; - ++it ) { - // If this assertion is a warning ignore any INFO messages - if( printInfoMessages || it->type != ResultWas::Info ) - stream << Text( it->message, TextAttributes().setIndent(2) ) << '\n'; + if (result.hasExpandedExpression()) { + rss << "with expansion:\n"; + rss << Column(result.getExpandedExpression()).indent(2) << '\n'; } - } - void printSourceInfo() const { - Colour colourGuard( Colour::FileName ); - stream << result.getSourceInfo() << ": "; + } else { + rss << '\n'; } - std::ostream& stream; - AssertionStats const& stats; - AssertionResult const& result; - Colour::Code colour; - std::string passOrFail; - std::string messageLabel; - std::string message; - std::vector<MessageInfo> messages; - bool printInfoMessages; - }; + if( !result.getMessage().empty() ) + rss << result.getMessage() << '\n'; + for( auto const& msg : stats.infoMessages ) + if( msg.type == ResultWas::Info ) + rss << msg.message << '\n'; - void lazyPrint() { + rss << "at " << result.getSourceInfo(); + xml.writeText( rss.str(), XmlFormatting::Newline ); + } + } - if( !currentTestRunInfo.used ) - lazyPrintRunInfo(); - if( !currentGroupInfo.used ) - lazyPrintGroupInfo(); + CATCH_REGISTER_REPORTER( "junit", JunitReporter ) - if( !m_headerPrinted ) { - printTestCaseAndSectionHeader(); - m_headerPrinted = true; - } - } - void lazyPrintRunInfo() { - stream << '\n' << getLineOfChars<'~'>() << '\n'; - Colour colour( Colour::SecondaryText ); - stream << currentTestRunInfo->name - << " is a Catch v" << libraryVersion() << " host application.\n" - << "Run with -? for options\n\n"; +} // end namespace Catch +// end catch_reporter_junit.cpp +// start catch_reporter_listening.cpp - if( m_config->rngSeed() != 0 ) - stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; +#include <cassert> - currentTestRunInfo.used = true; - } - void lazyPrintGroupInfo() { - if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { - printClosedHeader( "Group: " + currentGroupInfo->name ); - currentGroupInfo.used = true; - } - } - void printTestCaseAndSectionHeader() { - assert( !m_sectionStack.empty() ); - printOpenHeader( currentTestCaseInfo->name ); +namespace Catch { - if( m_sectionStack.size() > 1 ) { - Colour colourGuard( Colour::Headers ); + ListeningReporter::ListeningReporter() { + // We will assume that listeners will always want all assertions + m_preferences.shouldReportAllAssertions = true; + } - std::vector<SectionInfo>::const_iterator - it = m_sectionStack.begin()+1, // Skip first section (test case) - itEnd = m_sectionStack.end(); - for( ; it != itEnd; ++it ) - printHeaderString( it->name, 2 ); - } + void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) { + m_listeners.push_back( std::move( listener ) ); + } - SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) { + assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); + m_reporter = std::move( reporter ); + m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut; + } - if( !lineInfo.empty() ){ - stream << getLineOfChars<'-'>() << '\n'; - Colour colourGuard( Colour::FileName ); - stream << lineInfo << '\n'; - } - stream << getLineOfChars<'.'>() << '\n' << std::endl; + ReporterPreferences ListeningReporter::getPreferences() const { + return m_preferences; + } + + std::set<Verbosity> ListeningReporter::getSupportedVerbosities() { + return std::set<Verbosity>{ }; + } + + void ListeningReporter::noMatchingTestCases( std::string const& spec ) { + for ( auto const& listener : m_listeners ) { + listener->noMatchingTestCases( spec ); } + m_reporter->noMatchingTestCases( spec ); + } - void printClosedHeader( std::string const& _name ) { - printOpenHeader( _name ); - stream << getLineOfChars<'.'>() << '\n'; + void ListeningReporter::reportInvalidArguments(std::string const&arg){ + for ( auto const& listener : m_listeners ) { + listener->reportInvalidArguments( arg ); } - void printOpenHeader( std::string const& _name ) { - stream << getLineOfChars<'-'>() << '\n'; - { - Colour colourGuard( Colour::Headers ); - printHeaderString( _name ); - } + m_reporter->reportInvalidArguments( arg ); + } + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void ListeningReporter::benchmarkPreparing( std::string const& name ) { + for (auto const& listener : m_listeners) { + listener->benchmarkPreparing(name); + } + m_reporter->benchmarkPreparing(name); + } + void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkStarting( benchmarkInfo ); + } + m_reporter->benchmarkStarting( benchmarkInfo ); + } + void ListeningReporter::benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) { + for ( auto const& listener : m_listeners ) { + listener->benchmarkEnded( benchmarkStats ); } + m_reporter->benchmarkEnded( benchmarkStats ); + } - // if string has a : in first line will set indent to follow it on - // subsequent lines - void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { - std::size_t i = _string.find( ": " ); - if( i != std::string::npos ) - i+=2; - else - i = 0; - stream << Text( _string, TextAttributes() - .setIndent( indent+i) - .setInitialIndent( indent ) ) << '\n'; + void ListeningReporter::benchmarkFailed( std::string const& error ) { + for (auto const& listener : m_listeners) { + listener->benchmarkFailed(error); + } + m_reporter->benchmarkFailed(error); + } +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + + void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testRunStarting( testRunInfo ); } + m_reporter->testRunStarting( testRunInfo ); + } - struct SummaryColumn { + void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupStarting( groupInfo ); + } + m_reporter->testGroupStarting( groupInfo ); + } - SummaryColumn( std::string const& _label, Colour::Code _colour ) - : label( _label ), - colour( _colour ) - {} - SummaryColumn addRow( std::size_t count ) { - std::ostringstream oss; - oss << count; - std::string row = oss.str(); - for( std::vector<std::string>::iterator it = rows.begin(); it != rows.end(); ++it ) { - while( it->size() < row.size() ) - *it = ' ' + *it; - while( it->size() > row.size() ) - row = ' ' + row; - } - rows.push_back( row ); - return *this; - } + void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseStarting( testInfo ); + } + m_reporter->testCaseStarting( testInfo ); + } - std::string label; - Colour::Code colour; - std::vector<std::string> rows; + void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->sectionStarting( sectionInfo ); + } + m_reporter->sectionStarting( sectionInfo ); + } - }; + void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) { + for ( auto const& listener : m_listeners ) { + listener->assertionStarting( assertionInfo ); + } + m_reporter->assertionStarting( assertionInfo ); + } - void printTotals( Totals const& totals ) { - if( totals.testCases.total() == 0 ) { - stream << Colour( Colour::Warning ) << "No tests ran\n"; - } - else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { - stream << Colour( Colour::ResultSuccess ) << "All tests passed"; - stream << " (" - << pluralise( totals.assertions.passed, "assertion" ) << " in " - << pluralise( totals.testCases.passed, "test case" ) << ')' - << '\n'; - } - else { + // The return value indicates if the messages buffer should be cleared: + bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) { + for( auto const& listener : m_listeners ) { + static_cast<void>( listener->assertionEnded( assertionStats ) ); + } + return m_reporter->assertionEnded( assertionStats ); + } - std::vector<SummaryColumn> columns; - columns.push_back( SummaryColumn( "", Colour::None ) - .addRow( totals.testCases.total() ) - .addRow( totals.assertions.total() ) ); - columns.push_back( SummaryColumn( "passed", Colour::Success ) - .addRow( totals.testCases.passed ) - .addRow( totals.assertions.passed ) ); - columns.push_back( SummaryColumn( "failed", Colour::ResultError ) - .addRow( totals.testCases.failed ) - .addRow( totals.assertions.failed ) ); - columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) - .addRow( totals.testCases.failedButOk ) - .addRow( totals.assertions.failedButOk ) ); - - printSummaryRow( "test cases", columns, 0 ); - printSummaryRow( "assertions", columns, 1 ); - } - } - void printSummaryRow( std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row ) { - for( std::vector<SummaryColumn>::const_iterator it = cols.begin(); it != cols.end(); ++it ) { - std::string value = it->rows[row]; - if( it->label.empty() ) { - stream << label << ": "; - if( value != "0" ) - stream << value; - else - stream << Colour( Colour::Warning ) << "- none -"; - } - else if( value != "0" ) { - stream << Colour( Colour::LightGrey ) << " | "; - stream << Colour( it->colour ) - << value << ' ' << it->label; - } - } - stream << '\n'; + void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) { + for ( auto const& listener : m_listeners ) { + listener->sectionEnded( sectionStats ); } + m_reporter->sectionEnded( sectionStats ); + } - static std::size_t makeRatio( std::size_t number, std::size_t total ) { - std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; - return ( ratio == 0 && number > 0 ) ? 1 : ratio; + void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + for ( auto const& listener : m_listeners ) { + listener->testCaseEnded( testCaseStats ); } - static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { - if( i > j && i > k ) - return i; - else if( j > k ) - return j; - else - return k; - } - - void printTotalsDivider( Totals const& totals ) { - if( totals.testCases.total() > 0 ) { - std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); - std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); - std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); - while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) - findMax( failedRatio, failedButOkRatio, passedRatio )++; - while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) - findMax( failedRatio, failedButOkRatio, passedRatio )--; - - stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); - stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); - if( totals.testCases.allPassed() ) - stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); - else - stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); - } - else { - stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); - } - stream << '\n'; + m_reporter->testCaseEnded( testCaseStats ); + } + + void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + for ( auto const& listener : m_listeners ) { + listener->testGroupEnded( testGroupStats ); } - void printSummaryDivider() { - stream << getLineOfChars<'-'>() << '\n'; + m_reporter->testGroupEnded( testGroupStats ); + } + + void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) { + for ( auto const& listener : m_listeners ) { + listener->testRunEnded( testRunStats ); } + m_reporter->testRunEnded( testRunStats ); + } - private: - bool m_headerPrinted; - }; + void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) { + for ( auto const& listener : m_listeners ) { + listener->skipTest( testInfo ); + } + m_reporter->skipTest( testInfo ); + } - INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + bool ListeningReporter::isMulti() const { + return true; + } } // end namespace Catch +// end catch_reporter_listening.cpp +// start catch_reporter_xml.cpp -// #included from: ../reporters/catch_reporter_compact.hpp -#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif namespace Catch { + XmlReporter::XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()) + { + m_reporterPrefs.shouldRedirectStdOut = true; + m_reporterPrefs.shouldReportAllAssertions = true; + } - struct CompactReporter : StreamingReporterBase { + XmlReporter::~XmlReporter() = default; - CompactReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ) - {} + std::string XmlReporter::getDescription() { + return "Reports test results as an XML document"; + } - virtual ~CompactReporter(); + std::string XmlReporter::getStylesheetRef() const { + return std::string(); + } - static std::string getDescription() { - return "Reports test results on a single line, suitable for IDEs"; - } + void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = false; - return prefs; - } + void XmlReporter::noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } - virtual void noMatchingTestCases( std::string const& spec ) { - stream << "No test cases matched '" << spec << '\'' << std::endl; - } + void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + if (m_config->testSpec().hasFilters()) + m_xml.writeAttribute( "filters", serializeFilters( m_config->getTestsOrTags() ) ); + if( m_config->rngSeed() != 0 ) + m_xml.scopedElement( "Randomness" ) + .writeAttribute( "seed", m_config->rngSeed() ); + } - virtual void assertionStarting( AssertionInfo const& ) {} + void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } - virtual bool assertionEnded( AssertionStats const& _assertionStats ) { - AssertionResult const& result = _assertionStats.assertionResult; + void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString() ); - bool printInfoMessages = true; + writeSourceInfo( testInfo.lineInfo ); - // Drop out if result was successful and we're not printing those - if( !m_config->includeSuccessfulResults() && result.isOk() ) { - if( result.getResultType() != ResultWas::Warning ) - return false; - printInfoMessages = false; - } - - AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); - printer.print(); + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } - stream << std::endl; - return true; + void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); } + } - virtual void sectionEnded(SectionStats const& _sectionStats) CATCH_OVERRIDE { - if (m_config->showDurations() == ShowDurations::Always) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; - } - } + void XmlReporter::assertionStarting( AssertionInfo const& ) { } - virtual void testRunEnded( TestRunStats const& _testRunStats ) { - printTotals( _testRunStats.totals ); - stream << '\n' << std::endl; - StreamingReporterBase::testRunEnded( _testRunStats ); - } + bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { - private: - class AssertionPrinter { - void operator= ( AssertionPrinter const& ); - public: - AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) - : stream( _stream ) - , stats( _stats ) - , result( _stats.assertionResult ) - , messages( _stats.infoMessages ) - , itMessage( _stats.infoMessages.begin() ) - , printInfoMessages( _printInfoMessages ) - {} + AssertionResult const& result = assertionStats.assertionResult; - void print() { - printSourceInfo(); - - itMessage = messages.begin(); - - switch( result.getResultType() ) { - case ResultWas::Ok: - printResultType( Colour::ResultSuccess, passedString() ); - printOriginalExpression(); - printReconstructedExpression(); - if ( ! result.hasExpression() ) - printRemainingMessages( Colour::None ); - else - printRemainingMessages(); - break; - case ResultWas::ExpressionFailed: - if( result.isOk() ) - printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); - else - printResultType( Colour::Error, failedString() ); - printOriginalExpression(); - printReconstructedExpression(); - printRemainingMessages(); - break; - case ResultWas::ThrewException: - printResultType( Colour::Error, failedString() ); - printIssue( "unexpected exception with message:" ); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::FatalErrorCondition: - printResultType( Colour::Error, failedString() ); - printIssue( "fatal error condition with message:" ); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::DidntThrowException: - printResultType( Colour::Error, failedString() ); - printIssue( "expected exception, got none" ); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::Info: - printResultType( Colour::None, "info" ); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::Warning: - printResultType( Colour::None, "warning" ); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::ExplicitFailure: - printResultType( Colour::Error, failedString() ); - printIssue( "explicitly" ); - printRemainingMessages( Colour::None ); - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - printResultType( Colour::Error, "** internal error **" ); - break; + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults || result.getResultType() == ResultWas::Warning ) { + // Print any info messages in <Info> tags. + for( auto const& msg : assertionStats.infoMessages ) { + if( msg.type == ResultWas::Info && includeResults ) { + m_xml.scopedElement( "Info" ) + .writeText( msg.message ); + } else if ( msg.type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( msg.message ); } } + } - private: - // Colour::LightGrey - - static Colour::Code dimColour() { return Colour::FileName; } + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; -#ifdef CATCH_PLATFORM_MAC - static const char* failedString() { return "FAILED"; } - static const char* passedString() { return "PASSED"; } -#else - static const char* failedString() { return "failed"; } - static const char* passedString() { return "passed"; } -#endif + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); - void printSourceInfo() const { - Colour colourGuard( Colour::FileName ); - stream << result.getSourceInfo() << ':'; - } + writeSourceInfo( result.getSourceInfo() ); - void printResultType( Colour::Code colour, std::string const& passOrFail ) const { - if( !passOrFail.empty() ) { - { - Colour colourGuard( colour ); - stream << ' ' << passOrFail; - } - stream << ':'; - } - } + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } - void printIssue( std::string const& issue ) const { - stream << ' ' << issue; - } + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } - void printExpressionWas() { - if( result.hasExpression() ) { - stream << ';'; - { - Colour colour( dimColour() ); - stream << " expression was:"; - } - printOriginalExpression(); - } - } + if( result.hasExpression() ) + m_xml.endElement(); - void printOriginalExpression() const { - if( result.hasExpression() ) { - stream << ' ' << result.getExpression(); - } - } + return true; + } - void printReconstructedExpression() const { - if( result.hasExpandedExpression() ) { - { - Colour colour( dimColour() ); - stream << " for: "; - } - stream << result.getExpandedExpression(); - } - } + void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); - void printMessage() { - if ( itMessage != messages.end() ) { - stream << " '" << itMessage->message << '\''; - ++itMessage; - } - } + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); - void printRemainingMessages( Colour::Code colour = dimColour() ) { - if ( itMessage == messages.end() ) - return; + m_xml.endElement(); + } + } - // using messages.end() directly yields compilation error: - std::vector<MessageInfo>::const_iterator itEnd = messages.end(); - const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) ); + void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); - { - Colour colourGuard( colour ); - stream << " with " << pluralise( N, "message" ) << ':'; - } + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); - for(; itMessage != itEnd; ) { - // If this assertion is a warning ignore any INFO messages - if( printInfoMessages || itMessage->type != ResultWas::Info ) { - stream << " '" << itMessage->message << '\''; - if ( ++itMessage != itEnd ) { - Colour colourGuard( dimColour() ); - stream << " and"; - } - } - } - } + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), XmlFormatting::Newline ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), XmlFormatting::Newline ); - private: - std::ostream& stream; - AssertionStats const& stats; - AssertionResult const& result; - std::vector<MessageInfo> messages; - std::vector<MessageInfo>::const_iterator itMessage; - bool printInfoMessages; - }; + m_xml.endElement(); + } - // Colour, message variants: - // - white: No tests ran. - // - red: Failed [both/all] N test cases, failed [both/all] M assertions. - // - white: Passed [both/all] N test cases (no assertions). - // - red: Failed N tests cases, failed M assertions. - // - green: Passed [both/all] N tests cases with M assertions. - - std::string bothOrAll( std::size_t count ) const { - return count == 1 ? std::string() : count == 2 ? "both " : "all " ; - } - - void printTotals( const Totals& totals ) const { - if( totals.testCases.total() == 0 ) { - stream << "No tests ran."; - } - else if( totals.testCases.failed == totals.testCases.total() ) { - Colour colour( Colour::ResultError ); - const std::string qualify_assertions_failed = - totals.assertions.failed == totals.assertions.total() ? - bothOrAll( totals.assertions.failed ) : std::string(); - stream << - "Failed " << bothOrAll( totals.testCases.failed ) - << pluralise( totals.testCases.failed, "test case" ) << ", " - "failed " << qualify_assertions_failed << - pluralise( totals.assertions.failed, "assertion" ) << '.'; - } - else if( totals.assertions.total() == 0 ) { - stream << - "Passed " << bothOrAll( totals.testCases.total() ) - << pluralise( totals.testCases.total(), "test case" ) - << " (no assertions)."; - } - else if( totals.assertions.failed ) { - Colour colour( Colour::ResultError ); - stream << - "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " - "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.'; - } - else { - Colour colour( Colour::ResultSuccess ); - stream << - "Passed " << bothOrAll( totals.testCases.passed ) - << pluralise( totals.testCases.passed, "test case" ) << - " with " << pluralise( totals.assertions.passed, "assertion" ) << '.'; - } - } - }; + void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.scopedElement( "OverallResultsCases") + .writeAttribute( "successes", testGroupStats.totals.testCases.passed ) + .writeAttribute( "failures", testGroupStats.totals.testCases.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.testCases.failedButOk ); + m_xml.endElement(); + } - INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.scopedElement( "OverallResultsCases") + .writeAttribute( "successes", testRunStats.totals.testCases.passed ) + .writeAttribute( "failures", testRunStats.totals.testCases.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.testCases.failedButOk ); + m_xml.endElement(); + } -} // end namespace Catch +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) + void XmlReporter::benchmarkPreparing(std::string const& name) { + m_xml.startElement("BenchmarkResults") + .writeAttribute("name", name); + } -namespace Catch { - // These are all here to avoid warnings about not having any out of line - // virtual methods - NonCopyable::~NonCopyable() {} - IShared::~IShared() {} - IStream::~IStream() CATCH_NOEXCEPT {} - FileStream::~FileStream() CATCH_NOEXCEPT {} - CoutStream::~CoutStream() CATCH_NOEXCEPT {} - DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {} - StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} - IContext::~IContext() {} - IResultCapture::~IResultCapture() {} - ITestCase::~ITestCase() {} - ITestCaseRegistry::~ITestCaseRegistry() {} - IRegistryHub::~IRegistryHub() {} - IMutableRegistryHub::~IMutableRegistryHub() {} - IExceptionTranslator::~IExceptionTranslator() {} - IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} - IReporter::~IReporter() {} - IReporterFactory::~IReporterFactory() {} - IReporterRegistry::~IReporterRegistry() {} - IStreamingReporter::~IStreamingReporter() {} - AssertionStats::~AssertionStats() {} - SectionStats::~SectionStats() {} - TestCaseStats::~TestCaseStats() {} - TestGroupStats::~TestGroupStats() {} - TestRunStats::~TestRunStats() {} - CumulativeReporterBase::SectionNode::~SectionNode() {} - CumulativeReporterBase::~CumulativeReporterBase() {} - - StreamingReporterBase::~StreamingReporterBase() {} - ConsoleReporter::~ConsoleReporter() {} - CompactReporter::~CompactReporter() {} - IRunner::~IRunner() {} - IMutableContext::~IMutableContext() {} - IConfig::~IConfig() {} - XmlReporter::~XmlReporter() {} - JunitReporter::~JunitReporter() {} - TestRegistry::~TestRegistry() {} - FreeFunctionTestCase::~FreeFunctionTestCase() {} - IGeneratorInfo::~IGeneratorInfo() {} - IGeneratorsForTest::~IGeneratorsForTest() {} - WildcardPattern::~WildcardPattern() {} - TestSpec::Pattern::~Pattern() {} - TestSpec::NamePattern::~NamePattern() {} - TestSpec::TagPattern::~TagPattern() {} - TestSpec::ExcludedPattern::~ExcludedPattern() {} - Matchers::Impl::MatcherUntypedBase::~MatcherUntypedBase() {} + void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) { + m_xml.writeAttribute("samples", info.samples) + .writeAttribute("resamples", info.resamples) + .writeAttribute("iterations", info.iterations) + .writeAttribute("clockResolution", info.clockResolution) + .writeAttribute("estimatedDuration", info.estimatedDuration) + .writeComment("All values in nano seconds"); + } - void Config::dummy() {} + void XmlReporter::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) { + m_xml.startElement("mean") + .writeAttribute("value", benchmarkStats.mean.point.count()) + .writeAttribute("lowerBound", benchmarkStats.mean.lower_bound.count()) + .writeAttribute("upperBound", benchmarkStats.mean.upper_bound.count()) + .writeAttribute("ci", benchmarkStats.mean.confidence_interval); + m_xml.endElement(); + m_xml.startElement("standardDeviation") + .writeAttribute("value", benchmarkStats.standardDeviation.point.count()) + .writeAttribute("lowerBound", benchmarkStats.standardDeviation.lower_bound.count()) + .writeAttribute("upperBound", benchmarkStats.standardDeviation.upper_bound.count()) + .writeAttribute("ci", benchmarkStats.standardDeviation.confidence_interval); + m_xml.endElement(); + m_xml.startElement("outliers") + .writeAttribute("variance", benchmarkStats.outlierVariance) + .writeAttribute("lowMild", benchmarkStats.outliers.low_mild) + .writeAttribute("lowSevere", benchmarkStats.outliers.low_severe) + .writeAttribute("highMild", benchmarkStats.outliers.high_mild) + .writeAttribute("highSevere", benchmarkStats.outliers.high_severe); + m_xml.endElement(); + m_xml.endElement(); + } - namespace TestCaseTracking { - ITracker::~ITracker() {} - TrackerBase::~TrackerBase() {} - SectionTracker::~SectionTracker() {} - IndexTracker::~IndexTracker() {} + void XmlReporter::benchmarkFailed(std::string const &error) { + m_xml.scopedElement("failed"). + writeAttribute("message", error); + m_xml.endElement(); } +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + + CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_xml.cpp + +namespace Catch { + LeakDetector leakDetector; } #ifdef __clang__ #pragma clang diagnostic pop #endif +// end catch_impl.hpp #endif #ifdef CATCH_CONFIG_MAIN -// #included from: internal/catch_default_main.hpp -#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED +// start catch_default_main.hpp #ifndef __OBJC__ -#if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) // Standard C/C++ Win32 Unicode wmain entry point extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { #else @@ -11387,8 +17523,7 @@ extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { int main (int argc, char * argv[]) { #endif - int result = Catch::Session().run( argc, argv ); - return ( result < 0xff ? result : 0xff ); + return Catch::Session().run( argc, argv ); } #else // __OBJC__ @@ -11400,197 +17535,413 @@ int main (int argc, char * const argv[]) { #endif Catch::registerTestMethods(); - int result = Catch::Session().run( argc, (char* const*)argv ); + int result = Catch::Session().run( argc, (char**)argv ); #if !CATCH_ARC_ENABLED [pool drain]; #endif - return ( result < 0xff ? result : 0xff ); + return result; } #endif // __OBJC__ +// end catch_default_main.hpp #endif +#if !defined(CATCH_CONFIG_IMPL_ONLY) + #ifdef CLARA_CONFIG_MAIN_NOT_DEFINED # undef CLARA_CONFIG_MAIN #endif +#if !defined(CATCH_CONFIG_DISABLE) ////// - // If this config identifier is defined then all CATCH macros are prefixed with CATCH_ #ifdef CATCH_CONFIG_PREFIX_ALL -#if defined(CATCH_CONFIG_FAST_COMPILE) -#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) -#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) -#else -#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) -#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) -#endif +#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) -#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) +#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) -#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) -#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) - -#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) -#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) - -#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) -#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) #define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) -#if defined(CATCH_CONFIG_FAST_COMPILE) -#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) -#else #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) -#endif +#endif // CATCH_CONFIG_DISABLE_MATCHERS #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg ) #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) -#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) -#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) -#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) - -#ifdef CATCH_CONFIG_VARIADIC_MACROS - #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) - #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) - #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) - #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) - #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) - #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) - #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_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE",__VA_ARGS__ ) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#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_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_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) #else - #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) - #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) - #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) - #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description ) - #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) - #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) - #define CATCH_FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) - #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) #endif -#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) -#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) -#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) - -#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) +#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) +#define CATCH_STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) +#else +#define CATCH_STATIC_REQUIRE( ... ) CATCH_REQUIRE( __VA_ARGS__ ) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ ) +#endif // "BDD-style" convenience wrappers -#ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) -#else -#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) -#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) -#endif -#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) -#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) -#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) -#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) -#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) +#define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +#define CATCH_BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define CATCH_BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), name) +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else -#if defined(CATCH_CONFIG_FAST_COMPILE) -#define REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE", Catch::ResultDisposition::Normal, expr ) -#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) - -#else -#define REQUIRE( expr ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, expr ) -#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) -#endif +#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) -#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) +#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) -#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) -#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) - -#define CHECK( expr ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) -#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) - -#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) -#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) -#if defined(CATCH_CONFIG_FAST_COMPILE) -#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) -#else #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) -#endif +#endif // CATCH_CONFIG_DISABLE_MATCHERS #define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg ) #define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) -#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) -#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) -#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) +#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE",__VA_ARGS__ ) -#ifdef CATCH_CONFIG_VARIADIC_MACROS #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #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_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 ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) +#define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__) +#define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) #else -#define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) - #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) - #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) - #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description ) - #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) - #define FAIL( msg ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) - #define FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) - #define SUCCEED( msg ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) +#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) +#define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE( __VA_ARGS__ ) ) +#define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) #endif -#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) - -#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) -#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) -#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) +#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) +#define STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) +#define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) +#else +#define STATIC_REQUIRE( ... ) REQUIRE( __VA_ARGS__ ) +#define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ ) +#endif #endif #define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) // "BDD-style" convenience wrappers -#ifdef CATCH_CONFIG_VARIADIC_MACROS #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + +#define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) +#define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) +#define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) +#define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) +#define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) +#define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +#define BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(____C_A_T_C_H____B_E_N_C_H____), name) +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + +using Catch::Detail::Approx; + +#else // CATCH_CONFIG_DISABLE + +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) (void)(0) +#define CATCH_REQUIRE_FALSE( ... ) (void)(0) + +#define CATCH_REQUIRE_THROWS( ... ) (void)(0) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + +#define CATCH_CHECK( ... ) (void)(0) +#define CATCH_CHECK_FALSE( ... ) (void)(0) +#define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) +#define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CATCH_CHECK_NOFAIL( ... ) (void)(0) + +#define CATCH_CHECK_THROWS( ... ) (void)(0) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) (void)(0) + +#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) (void)(0) +#define CATCH_UNSCOPED_INFO( msg ) (void)(0) +#define CATCH_WARN( msg ) (void)(0) +#define CATCH_CAPTURE( msg ) (void)(0) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define CATCH_SECTION( ... ) +#define CATCH_DYNAMIC_SECTION( ... ) +#define CATCH_FAIL( ... ) (void)(0) +#define CATCH_FAIL_CHECK( ... ) (void)(0) +#define CATCH_SUCCEED( ... ) (void)(0) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) #else -#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) -#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) ) +#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) #endif -#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) -#define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) -#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) -#define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) -#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define CATCH_GIVEN( desc ) +#define CATCH_AND_GIVEN( desc ) +#define CATCH_WHEN( desc ) +#define CATCH_AND_WHEN( desc ) +#define CATCH_THEN( desc ) +#define CATCH_AND_THEN( desc ) + +#define CATCH_STATIC_REQUIRE( ... ) (void)(0) +#define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( ... ) (void)(0) +#define REQUIRE_FALSE( ... ) (void)(0) + +#define REQUIRE_THROWS( ... ) (void)(0) +#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) (void)(0) + +#define CHECK( ... ) (void)(0) +#define CHECK_FALSE( ... ) (void)(0) +#define CHECKED_IF( ... ) if (__VA_ARGS__) +#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CHECK_NOFAIL( ... ) (void)(0) + +#define CHECK_THROWS( ... ) (void)(0) +#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) (void)(0) + +#define REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) (void)(0) +#define UNSCOPED_INFO( msg ) (void)(0) +#define WARN( msg ) (void)(0) +#define CAPTURE( msg ) (void)(0) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define METHOD_AS_TEST_CASE( method, ... ) +#define REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define SECTION( ... ) +#define DYNAMIC_SECTION( ... ) +#define FAIL( ... ) (void)(0) +#define FAIL_CHECK( ... ) (void)(0) +#define SUCCEED( ... ) (void)(0) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) +#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__) +#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#else +#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) ) +#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) ) +#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) ) +#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) ) +#define TEMPLATE_PRODUCT_TEST_CASE( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#endif + +#define STATIC_REQUIRE( ... ) (void)(0) +#define STATIC_REQUIRE_FALSE( ... ) (void)(0) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) + +#define GIVEN( desc ) +#define AND_GIVEN( desc ) +#define WHEN( desc ) +#define AND_WHEN( desc ) +#define THEN( desc ) +#define AND_THEN( desc ) using Catch::Detail::Approx; -// #included from: internal/catch_reenable_warnings.h +#endif + +#endif // ! CATCH_CONFIG_IMPL_ONLY + +// start catch_reenable_warnings.h -#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro @@ -11602,5 +17953,7 @@ using Catch::Detail::Approx; # pragma GCC diagnostic pop #endif +// end catch_reenable_warnings.h +// end catch.hpp #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED |