diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 60999e9..3769591 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,14 +1,15 @@ name: Main Workflow -on: [push, pull_request] +on: [ push, pull_request ] env: - LLVM_VERSION: 13 + LLVM_VERSION: 15 jobs: debug: + if: github.event_name == 'push' runs-on: ubuntu-latest - container: ghcr.io/opencyphal/toolshed:ts20.4.1 + container: ghcr.io/opencyphal/toolshed:ts22.4.3 strategy: matrix: - toolchain: ['clang', 'gcc'] + toolchain: [ 'clang', 'gcc' ] include: - toolchain: gcc c-compiler: gcc @@ -18,6 +19,13 @@ jobs: cxx-compiler: clang++ steps: - uses: actions/checkout@v3 + # language=bash + - run: | + # TODO https://github.com/OpenCyphal/docker_toolchains/issues/36 + dpkg --add-architecture i386 + apt-get update + apt-get install -y libgtest-dev libgtest-dev:i386 + # language=bash - run: > cmake -B ${{ github.workspace }}/build @@ -25,50 +33,162 @@ jobs: -DCMAKE_C_COMPILER=${{ matrix.c-compiler }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx-compiler }} tests - - name: make - run: | + # language=bash + - run: | cd ${{ github.workspace }}/build - make VERBOSE=1 - make test - - uses: actions/upload-artifact@v2 + make VERBOSE=1 -j$(nproc) + make test ARGS="--verbose" + - uses: actions/upload-artifact@v3 if: always() with: - name: ${{github.job}} + # The matrix is shown for convenience but this is fragile because the values may not be string-convertible. + # Shall it break one day, feel free to remove the matrix from here. + name: ${{github.job}}-#${{strategy.job-index}}-${{job.status}}-${{join(matrix.*, ',')}} path: ${{github.workspace}}/**/* retention-days: 2 optimizations: + if: github.event_name == 'push' runs-on: ubuntu-latest - container: ghcr.io/opencyphal/toolshed:ts20.4.1 + container: ghcr.io/opencyphal/toolshed:ts22.4.3 strategy: matrix: - toolchain: ['clang', 'gcc'] - build_type: [Release, MinSizeRel] + toolchain: [ 'clang', 'gcc' ] + build_type: [ Release, MinSizeRel ] include: - toolchain: gcc c-compiler: gcc cxx-compiler: g++ + cxx-flags: -fno-strict-aliasing # GCC in MinSizeRel C++20 mode misoptimizes the Cavl test. - toolchain: clang c-compiler: clang cxx-compiler: clang++ steps: - uses: actions/checkout@v3 + # language=bash + - run: | + # TODO https://github.com/OpenCyphal/docker_toolchains/issues/36 + dpkg --add-architecture i386 + apt-get update + apt-get install -y libgtest-dev libgtest-dev:i386 + # language=bash - run: > cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_C_COMPILER=${{ matrix.c-compiler }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx-compiler }} + -DCMAKE_CXX_FLAGS="${{ matrix.cxx-flags }}" -DNO_STATIC_ANALYSIS=1 tests - - name: make - run: | + # language=bash + - run: | cd ${{ github.workspace }}/build - make VERBOSE=1 - make test - - uses: actions/upload-artifact@v2 + make VERBOSE=1 -j$(nproc) + make test ARGS="--verbose" + - uses: actions/upload-artifact@v3 if: always() with: - name: ${{github.job}} + # The matrix is shown for convenience but this is fragile because the values may not be string-convertible. + # Shall it break one day, feel free to remove the matrix from here. + name: ${{github.job}}-#${{strategy.job-index}}-${{job.status}}-${{join(matrix.*, ',')}} path: ${{github.workspace}}/**/* retention-days: 2 + + avr: + if: github.event_name == 'push' + runs-on: ubuntu-latest + env: + mcu: at90can64 + flags: -Wall -Wextra -Werror -pedantic -Wconversion -Wtype-limits + strategy: + matrix: + std: [ 'c99', 'c11', 'gnu99', 'gnu11' ] + steps: + - uses: actions/checkout@v3 + # language=bash + - run: | + sudo apt update -y && sudo apt upgrade -y + sudo apt install gcc-avr avr-libc + avr-gcc --version + - run: avr-gcc libudpard/*.c -c -std=${{matrix.std}} -mmcu=${{env.mcu}} ${{env.flags}} + + arm: + if: github.event_name == 'push' + runs-on: ubuntu-latest + env: + flags: -Wall -Wextra -Werror -pedantic -Wconversion -Wtype-limits -Wcast-align -Wfatal-errors + strategy: + matrix: + std: [ 'c99', 'c11', 'gnu99', 'gnu11' ] + steps: + - uses: actions/checkout@v3 + # language=bash + - run: | + sudo apt update -y && sudo apt upgrade -y + sudo apt-get install -y gcc-arm-none-eabi + - run: arm-none-eabi-gcc libudpard/*.c -c -std=${{matrix.std}} ${{ env.flags }} + + sonar: + runs-on: ubuntu-latest + container: ghcr.io/opencyphal/toolshed:ts22.4.3 + if: > + ( + (github.event_name == 'pull_request' || contains(github.ref, '/main') || contains(github.ref, '/release')) && + !contains(github.event.head_commit.message, '#yolo') + ) || ( + contains(github.event.head_commit.message, '#sonar') + ) + env: + SONAR_SCANNER_VERSION: 4.8.0.2856 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - uses: actions/setup-java@v3 + with: + java-version: 11 + distribution: 'zulu' + # language=bash + - run: | + clang --version + # TODO https://github.com/OpenCyphal/docker_toolchains/issues/36 + dpkg --add-architecture i386 + apt-get update + apt-get install -y libgtest-dev libgtest-dev:i386 + - name: Install Sonar tools + env: + SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip + BUILD_WRAPPER_DOWNLOAD_URL: https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip + # language=bash + run: | + mkdir -p $HOME/.sonar + curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }} + unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/ + echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH + curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }} + unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/ + echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH + # Sonar is not run on builds originating from forks due to secrets not being available (avoids errors). + # language=bash + - run: | + [ -z "$SONAR_TOKEN" ] || tools/run_sonar.sh + - uses: actions/upload-artifact@v3 + if: always() + with: + name: ${{github.job}} + path: ${{github.workspace}}/ + retention-days: 3 + + style_check: + if: github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: DoozyX/clang-format-lint-action@v0.15 + with: + source: './libudpard ./tests' + extensions: 'c,h,cpp,hpp' + clangFormatVersion: ${{ env.LLVM_VERSION }} diff --git a/.gitignore b/.gitignore index f0e9f65..30e85f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,52 +1,38 @@ -# Object files *.o *.ko *.obj *.elf - -# Precompiled Headers *.gch *.pch - -# Libraries *.lib *.a *.la *.lo - -# Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib - -# Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex - -# Debug files *.dSYM/ - -# CMake build directory -build/ +*build/ cmake-build-*/ build-avr/ - -# IDE and tools .metadata .settings .project .cproject .pydevproject .gdbinit +.scannerwork/ .vscode/ **/.idea/* !**/.idea/dictionaries !**/.idea/dictionaries/* - -# Pycache __pycache__/ +*.pyo +*.pyc diff --git a/README.md b/README.md index 0e9fa18..4324c26 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,19 @@ This package is a staging package to make changes before committing a pull reque # Compact Cyphal/UDP v1 in C -Libudpard is a compact implementation of the Cyphal/UDP protocol stack in C99/C11 for high-integrity real-time +[![Main Workflow](https://github.com/OpenCyphal-Garage/libudpard/actions/workflows/main.yml/badge.svg)](https://github.com/OpenCyphal-Garage/libudpard/actions/workflows/main.yml) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=libudpard&metric=reliability_rating)](https://sonarcloud.io/summary?id=libudpard) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=libudpard&metric=coverage)](https://sonarcloud.io/summary?id=libudpard) +[![Forum](https://img.shields.io/discourse/users.svg?server=https%3A%2F%2Fforum.opencyphal.org&color=1700b3)](https://forum.opencyphal.org) + +LibUDPard is a compact implementation of the Cyphal/UDP protocol stack in C99/C11 for high-integrity real-time embedded systems. [Cyphal](https://opencyphal.org) is an open lightweight data bus standard designed for reliable intravehicular communication in aerospace and robotic applications via CAN bus, UDP, and other robust transports. +We pronounce LibUDPard as *lib-you-dee-pee-ard*. + ## WORK IN PROGRESS, NOT READY FOR FORMAL USE **Read the docs in [`libudpard/udpard.h`](/libudpard/udpard.h).** diff --git a/libudpard/.clang-tidy b/libudpard/.clang-tidy index d7f291d..bd04775 100644 --- a/libudpard/.clang-tidy +++ b/libudpard/.clang-tidy @@ -21,6 +21,7 @@ Checks: >- -cert-dcl03-c, -hicpp-static-assert, -misc-static-assert, + -modernize-macro-to-enum, CheckOptions: - key: readability-function-cognitive-complexity.Threshold value: '99' diff --git a/libudpard/cavl.h b/libudpard/_udpard_cavl.h similarity index 97% rename from libudpard/cavl.h rename to libudpard/_udpard_cavl.h index c9213b8..2d8927a 100644 --- a/libudpard/cavl.h +++ b/libudpard/_udpard_cavl.h @@ -42,7 +42,7 @@ extern "C" { // ---------------------------------------- PUBLIC API SECTION ---------------------------------------- -/// Modified for use with Libudpard: expose the Cavl structure via public API as UdpardTreeNode. +/// Modified for use with LibUDPard: expose the Cavl structure via public API as UdpardTreeNode. typedef UdpardTreeNode Cavl; /// Returns POSITIVE if the search target is GREATER than the provided node, negative if smaller, zero on match (found). @@ -118,7 +118,7 @@ static inline Cavl* cavlPrivateAdjustBalance(Cavl* const x, const bool increment { CAVL_ASSERT((x != NULL) && ((x->bf >= -1) && (x->bf <= +1))); Cavl* out = x; - const int8_t new_bf = (int8_t)(x->bf + (increment ? +1 : -1)); + const int8_t new_bf = (int8_t) (x->bf + (increment ? +1 : -1)); if ((new_bf < -1) || (new_bf > 1)) { const bool r = new_bf < 0; // bf<0 if left-heavy --> right rotation is needed. @@ -131,8 +131,8 @@ static inline Cavl* cavlPrivateAdjustBalance(Cavl* const x, const bool increment cavlPrivateRotate(x, r); if (0 == z->bf) { - x->bf = (int8_t)(-sign); - z->bf = (int8_t)(+sign); + x->bf = (int8_t) (-sign); + z->bf = (int8_t) (+sign); } else { @@ -149,7 +149,7 @@ static inline Cavl* cavlPrivateAdjustBalance(Cavl* const x, const bool increment cavlPrivateRotate(x, r); if ((y->bf * sign) < 0) { - x->bf = (int8_t)(+sign); + x->bf = (int8_t) (+sign); y->bf = 0; z->bf = 0; } @@ -157,7 +157,7 @@ static inline Cavl* cavlPrivateAdjustBalance(Cavl* const x, const bool increment { x->bf = 0; y->bf = 0; - z->bf = (int8_t)(-sign); + z->bf = (int8_t) (-sign); } else { diff --git a/libudpard/udpard.c b/libudpard/udpard.c index ab28b4e..1b61688 100644 --- a/libudpard/udpard.c +++ b/libudpard/udpard.c @@ -1,1382 +1,14 @@ -/// This library is based heavily on libcanard /// This software is distributed under the terms of the MIT License. /// Copyright (c) 2016 OpenCyphal. -/// Author: Pavel Kirienko /// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +/// Author: Pavel Kirienko #include "udpard.h" -#include "cavl.h" +#include "_udpard_cavl.h" #include -// --------------------------------------------- BUILD CONFIGURATION --------------------------------------------- - -/// Define this macro to include build configuration header. -/// Usage example with CMake: "-DUDPARD_CONFIG_HEADER=\"${CMAKE_CURRENT_SOURCE_DIR}/my_udpard_config.h\"" -#ifdef UDPARD_CONFIG_HEADER -# include UDPARD_CONFIG_HEADER -#endif - -/// By default, this macro resolves to the standard assert(). The user can redefine this if necessary. -/// To disable assertion checks completely, make it expand into `(void)(0)`. -#ifndef UDPARD_ASSERT -// Intentional violation of MISRA: inclusion not at the top of the file to eliminate unnecessary dependency on assert.h. -# include // NOSONAR -// Intentional violation of MISRA: assertion macro cannot be replaced with a function definition. -# define UDPARD_ASSERT(x) assert(x) // NOSONAR -#endif - -/// Define UDPARD_CRC_TABLE=0 to use slow but ROM-efficient transfer-CRC computation algorithm. -/// Doing so is expected to save ca. 500 bytes of ROM and increase the cost of RX/TX transfer processing by ~half. -#ifndef UDPARD_CRC_TABLE -# define UDPARD_CRC_TABLE 1 -#endif - -/// This macro is needed for testing and for library development. -#ifndef UDPARD_PRIVATE -# define UDPARD_PRIVATE static inline -#endif - -#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) -# error "Unsupported language: ISO C99 or a newer version is required." -#endif - -// --------------------------------------------- COMMON DEFINITIONS --------------------------------------------- - -static const uint8_t BITS_PER_BYTE = 8U; -static const uint8_t BYTE_MAX = 0xFFU; - -static const uint8_t UDPARD_END_OF_TRANSFER_OFFSET = 31U; -static const uint32_t UDPARD_MAX_FRAME_INDEX = 0x7FFFFFFFU; // ((1U << UDPARD_END_OF_TRANSFER_OFFSET) - 1U); -static const uint16_t UDPARD_NODE_ID_MASK = 65535U; /// 0xFFFF - -/* - fixed Cyphal/UDP - (9 bits) Ipv4 Addr - Version Destination Node ID - ________________(1 bit) SNM _______________________________ - / \ | | / \ - 1 1 1 0 1 1 1 1 . 0 c d d d d d m . 0 s s s s s s s . s s s s s s s s - \_____/ \______/ \_______/ | \_____________________________/ - (4 bits) (4 bits) Subnet | (15 bits) subject-ID - IPv4 Scope Reserved Reserved - multicast \_______________________________________________/ - prefix (23 bits) - collision-free multicast - addressing limit of - Ethernet MAC for IPv4 -*/ - -/// The multicast message transfer IP address node ID is formed of 1 reserved 0 bits and 15 bits for a subject id. -static const uint16_t UDPARD_SUBJECT_ID_MASK = 0x7FFFU; -static const uint32_t UDPARD_SUBNET_MASK = 0x3E0000U; // (31U << UDPARD_SUBNET_OFFSET (17)); -static const uint8_t UDPARD_TRANSMIT_SUBNET_VALUE = 0U; -static const uint16_t UDPARD_RESERVED_1BIT_MASK = 0x8000U; // (1U << UDPARD_RESERVED_1BIT_OFFSET (15)); -static const uint32_t UDPARD_SERVICE_NOT_MESSAGE_MASK = 0x10000U; // (1U << UDPARD_SERVICE_NOT_MESSAGE_OFFSET (16)); -static const uint32_t UDPARD_MULTICAST_PREFIX = 0xEF000000U; // (478U << UDPARD_MULTICAST_OFFSET (23)) - -/* The 16 bit data specifier in the Cyphal header consists of -SNM + 15 bit Subject-ID (Message) -SNM + IRNR + Service-ID (Service Request/Response) - -SNM - Service, Not Message -IRNR - Is Request, Not Response -*/ -static const uint16_t UDPARD_SERVICE_NOT_MESSAGE_DATA_SPECIFIER_OFFSET = 15U; -static const uint16_t UDPARD_IRNR_DATA_SPECIFIER_OFFSET = 14U; -static const uint16_t UDPARD_SERVICE_ID_MASK = 0x3FFFU; -static const uint16_t UDPARD_DATA_SPECIFIER_MESSAGE_MASK = 0x7FFFU; // SNM (0) + SubjectID -static const uint16_t UDPARD_DATA_SPECIFIER_SERVICE_RESPONSE = 0x8000U; // (2U << UDPARD_IRNR_DATA_SPECIFIER_OFFSET) // Set SNM in Cyphal data specifier - SNM (1) + IRNR (0) + ServiceID -static const uint16_t UDPARD_DATA_SPECIFIER_SERVICE_REQUEST = 0xC000U; // (3U << UDPARD_IRNR_DATA_SPECIFIER_OFFSET) // Set SNM and IRNR in Cyphal data specifier - SNM (1) + IRNR (1) + ServiceID - -static const uint16_t UDPARD_UDP_PORT = 9382U; - -/// Used for inserting new items into AVL trees. -UDPARD_PRIVATE UdpardTreeNode* avlTrivialFactory(void* const user_reference) -{ - return (UdpardTreeNode*) user_reference; -} - -/// --------------------------------------------- TRANSFER CRC --------------------------------------------- - -typedef uint32_t TransferCRC; - -// Those crc values come from https://crc32c.machinezoo.com/ -static const TransferCRC CRC_INITIAL = 0xFFFFFFFFU; -static const TransferCRC CRC_RESIDUE = 0xB798B438U; -static const TransferCRC CRC_XOR = 0xFFFFFFFFU; -static const size_t CRC_SIZE_BYTES = 4U; - -static const uint8_t CRC_BYTE_MASK = 0xFFU; - -static const uint32_t CRCTable[256] = - {0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, - 0x78b2dbcc, 0x6be22838, 0x9989ab3b, 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c, - 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384, 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, - 0x89d76c54, 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b, 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, - 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, - 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa, 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad, - 0x1642ae59, 0xe4292d5a, 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, 0x7da08661, 0x8fcb0562, 0x9c9bf696, - 0x6ef07595, 0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957, - 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198, 0x5125dad3, - 0xa34e59d0, 0xb01eaa24, 0x42752927, 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 0xdbfc821c, 0x2997011f, - 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7, 0x61c69362, 0x93ad1061, 0x80fde395, - 0x72966096, 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789, 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, - 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, 0xb602c312, - 0x44694011, 0x5739b3e5, 0xa55230e6, 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de, - 0xdde0eb2a, 0x2f8b6829, 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, 0x456cac67, 0xb7072f64, 0xa457dc90, - 0x563c5f93, 0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c, - 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc, 0x1871a4d8, - 0xea1a27db, 0xf94ad42f, 0x0b21572c, 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 0xa24bb5a6, 0x502036a5, - 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d, 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, - 0x3bc21e9d, 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982, 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, - 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, - 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed, 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8, - 0xe52cc12c, 0x1747422f, 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, 0x8ecee914, 0x7ca56a17, 0x6ff599e3, - 0x9d9e1ae0, 0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540, - 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f, 0xe330a81a, - 0x115b2b19, 0x020bd8ed, 0xf0605bee, 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 0x69e9f0d5, 0x9b8273d6, - 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e, 0xf36e6f75, 0x0105ec76, 0x12551f82, - 0xe03e9c81, 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e, 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, - 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351}; - -UDPARD_PRIVATE TransferCRC crcAddByte(const TransferCRC crc, const uint8_t byte) -{ - return CRCTable[(uint32_t) ( byte ^ (crc & BYTE_MAX))] ^ (crc >> BITS_PER_BYTE); -} - -UDPARD_PRIVATE TransferCRC crcAdd(const TransferCRC crc, const size_t size, const void* const data) -{ - UDPARD_ASSERT((data != NULL) || (size == 0U)); - TransferCRC out = crc; - const uint8_t* p = (const uint8_t*) data; - for (size_t i = 0; i < size; i++) - { - out = crcAddByte(out, *p); - ++p; - } - return out; -} - -UDPARD_PRIVATE TransferCRC crcValue(const TransferCRC crc) -{ - return (uint32_t) (crc ^ CRC_XOR); -} - -/// --------------------------------------------- CYPHAL HEADER CRC --------------------------------------------- - -typedef uint16_t CyphalHeaderCRC; - -// Based on CRC-16-CCITT-FALSE Function -static const uint16_t CYPHAL_HEADER_CRC_INITIAL = 0xFFFFU; -static const size_t CYPHAL_HEADER_CRC_SIZE_BYTES = 2U; - -UDPARD_PRIVATE CyphalHeaderCRC cyphalHeaderCrcAddByte(const CyphalHeaderCRC crc, const uint8_t byte) -{ - // Based on CRC-16-CCITT-FALSE Function - static const CyphalHeaderCRC Top = 0x8000U; - static const CyphalHeaderCRC Poly = 0x1021U; - CyphalHeaderCRC out = crc ^ (uint16_t) ((uint16_t) (byte) << BITS_PER_BYTE); - // Do not fold this into a loop because a size-optimizing compiler won't unroll it degrading the performance. - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - out = (uint16_t) ((uint16_t) (out << 1U) ^ (((out & Top) != 0U) ? Poly : 0U)); - return out; -} - -UDPARD_PRIVATE CyphalHeaderCRC cyphalHeaderCrcAdd(const CyphalHeaderCRC crc, const size_t size, const void* const header) -{ - UDPARD_ASSERT(header != NULL); - CyphalHeaderCRC out = crc; - const uint8_t* p = (const uint8_t*) header; - for (size_t i = 0; i < size; i++) - { - out = cyphalHeaderCrcAddByte(out, *p); - ++p; - } - return out; -} - -// --------------------------------------------- TRANSMISSION --------------------------------------------- - -/// This is a subclass of UdpardTxQueueItem. A pointer to this type can be cast to UdpardTxQueueItem safely. -/// This is standard-compliant. The paragraph 6.7.2.1.15 says: -/// A pointer to a structure object, suitably converted, points to its initial member (or if that member is a -/// bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a -/// structure object, but not at its beginning. -typedef struct TxItem -{ - UdpardTxQueueItem base; - uint8_t payload_buffer[UDPARD_MTU_MAX]; -} TxItem; - -/// Chain of TX frames prepared for insertion into a TX queue. -typedef struct -{ - TxItem* head; - TxItem* tail; - size_t size; -} TxChain; - -/// TODO - Determine what is needed for a Message Sessions Specifier -UDPARD_PRIVATE int32_t txMakeMessageSessionSpecifier(const UdpardPortID subject_id, - const UdpardNodeID src_node_id, - const UdpardIPv4Addr local_node_addr, - UdpardSessionSpecifier* const out_spec) -{ - UDPARD_ASSERT(subject_id <= UDPARD_SUBJECT_ID_MAX); - /// Just the local ip address + source node id - out_spec->source_route_specifier = - (local_node_addr & ~(UdpardIPv4Addr) UDPARD_NODE_ID_MASK) | - (UdpardIPv4Addr) src_node_id; - out_spec->destination_route_specifier = - ((UDPARD_TRANSMIT_SUBNET_VALUE & (UdpardIPv4Addr) UDPARD_SUBNET_MASK) | - (UdpardIPv4Addr) UDPARD_MULTICAST_PREFIX | - ((UdpardIPv4Addr) UDPARD_SUBJECT_ID_MASK & (UdpardIPv4Addr) subject_id)) & - ~(UdpardIPv4Addr) UDPARD_SERVICE_NOT_MESSAGE_MASK & - ~(UdpardIPv4Addr) UDPARD_RESERVED_1BIT_MASK; - out_spec->data_specifier = (UdpardUdpPortID) UDPARD_UDP_PORT; - return UDPARD_SUCCESS; -} - -UDPARD_PRIVATE int32_t txMakeServiceSessionSpecifier(const UdpardPortID service_id, - const UdpardNodeID src_node_id, - const UdpardIPv4Addr local_node_addr, - UdpardSessionSpecifier* const out_spec) -{ - UDPARD_ASSERT(service_id < UDPARD_SERVICE_ID_MAX); - /// Just the local ip address + source node id - out_spec->source_route_specifier = - (local_node_addr & ~(UdpardIPv4Addr) UDPARD_NODE_ID_MASK) | - (UdpardIPv4Addr) src_node_id; - out_spec->destination_route_specifier = - ((UDPARD_TRANSMIT_SUBNET_VALUE & (UdpardIPv4Addr) UDPARD_SUBNET_MASK) | - (UdpardIPv4Addr) UDPARD_MULTICAST_PREFIX | - ((UdpardIPv4Addr) UDPARD_NODE_ID_MASK & (UdpardIPv4Addr) service_id)) | - (UdpardIPv4Addr) UDPARD_SERVICE_NOT_MESSAGE_MASK; - out_spec->data_specifier = (UdpardUdpPortID) UDPARD_UDP_PORT; - return UDPARD_SUCCESS; -} - -// This may need to be adjusted... -UDPARD_PRIVATE size_t adjustPresentationLayerMTU(const size_t mtu_bytes) -{ - if (mtu_bytes >= UDPARD_MTU_UDP_IPV4) - { - return UDPARD_MTU_UDP_IPV4; - } - return mtu_bytes; -} - -UDPARD_PRIVATE int32_t txMakeSessionSpecifier(const UdpardTransferMetadata* const tr, - const UdpardNodeID local_node_id, - const UdpardIPv4Addr local_node_addr, - UdpardSessionSpecifier* const spec) -{ - UDPARD_ASSERT(tr != NULL); - int32_t out = -UDPARD_ERROR_INVALID_ARGUMENT; - if ((tr->transfer_kind == UdpardTransferKindMessage) && (UDPARD_NODE_ID_UNSET == tr->remote_node_id) && - (tr->port_id <= UDPARD_SUBJECT_ID_MAX)) - { - if ((local_node_id == UDPARD_NODE_ID_UNSET) || (local_node_id <= UDPARD_NODE_ID_MAX)) { - - out = txMakeMessageSessionSpecifier(tr->port_id, local_node_id, local_node_addr, spec); - UDPARD_ASSERT(out >= 0); - } else { - out = -UDPARD_ERROR_INVALID_ARGUMENT; // Node must be at least 1 less than the unset id - } - } - else if (((tr->transfer_kind == UdpardTransferKindRequest) || (tr->transfer_kind == UdpardTransferKindResponse)) && - (tr->port_id < UDPARD_SERVICE_ID_MAX) && (tr->remote_node_id != UDPARD_NODE_ID_UNSET)) - { - if (local_node_id != UDPARD_NODE_ID_UNSET) - { - out = txMakeServiceSessionSpecifier(tr->port_id, - local_node_id, - local_node_addr, - spec); - UDPARD_ASSERT(out >= 0); - } - else - { - out = -UDPARD_ERROR_INVALID_ARGUMENT; // Anonymous service transfers are not allowed. - } - } - else - { - out = -UDPARD_ERROR_INVALID_ARGUMENT; - } - - if (out >= 0) - { - const uint32_t prio = (uint32_t) tr->priority; - if (prio > UDPARD_PRIORITY_MAX) - { - out = -UDPARD_ERROR_INVALID_ARGUMENT; // Priority can't be greater than max value - } - } - return out; -} - -UDPARD_PRIVATE void txMakeFrameHeader(UdpardFrameHeader* const header, - const UdpardNodeID src_node_id, - const UdpardNodeID dst_node_id, - const UdpardPortID port_id, - const UdpardTransferKind transfer_kind, - const UdpardPriority priority, - const UdpardTransferID transfer_id, - const bool end_of_transfer, - const uint32_t frame_index) -{ - UDPARD_ASSERT(frame_index <= UDPARD_MAX_FRAME_INDEX); - uint32_t end_of_transfer_mask = (uint32_t) (end_of_transfer ? 1 : 0) << (uint32_t) UDPARD_END_OF_TRANSFER_OFFSET; - size_t cyphal_header_size_without_crc = sizeof(UdpardFrameHeader) - CYPHAL_HEADER_CRC_SIZE_BYTES; - header->transfer_id = transfer_id; - header->version = (uint8_t) UDPARD_CYPHAL_HEADER_VERSION; - header->priority = (uint8_t) priority; - header->frame_index_eot = end_of_transfer_mask | frame_index; - header->source_node_id = src_node_id; - header->destination_node_id = dst_node_id; - header->cyphal_header_checksum = cyphalHeaderCrcAdd(CYPHAL_HEADER_CRC_INITIAL, cyphal_header_size_without_crc, header); - if (transfer_kind == UdpardTransferKindMessage) - { - // both port_id and the data_specifier start at bit-0. No shift of the port_id value is necessary. - header->data_specifier = port_id & UDPARD_DATA_SPECIFIER_MESSAGE_MASK; - } - else - { - // SNM (1) + IRNR + ServiceID - if (transfer_kind == UdpardTransferKindRequest) - { - header->data_specifier = UDPARD_DATA_SPECIFIER_SERVICE_REQUEST | port_id; - } - else - { - header->data_specifier = UDPARD_DATA_SPECIFIER_SERVICE_RESPONSE | port_id; - } - } -} - -/// The item is only allocated and initialized, but NOT included into the queue! The caller needs to do that. -UDPARD_PRIVATE TxItem* txAllocateQueueItem(UdpardInstance* const ins, - const UdpardSessionSpecifier* const spec, - const UdpardMicrosecond deadline_usec, - const size_t payload_size) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(payload_size > 0U); - TxItem* const out = (TxItem*) ins->memory_allocate(ins, sizeof(TxItem) - UDPARD_MTU_MAX + payload_size); - if (out != NULL) - { - out->base.base.up = NULL; - out->base.base.lr[0] = NULL; - out->base.base.lr[1] = NULL; - out->base.base.bf = 0; - - out->base.next_in_transfer = NULL; // Last by default. - out->base.tx_deadline_usec = deadline_usec; - - out->base.frame.payload_size = payload_size; - out->base.frame.payload = out->payload_buffer; - out->base.specifier.data_specifier = spec->data_specifier; - out->base.specifier.destination_route_specifier = spec->destination_route_specifier; - out->base.specifier.source_route_specifier = spec->source_route_specifier; - } - return out; -} - -/// Frames with identical UDP ID that are added later always compare greater than their counterparts with same UDP ID. -/// This ensures that UDP frames with the same UDP ID are transmitted in the FIFO order. -/// Frames that should be transmitted earlier compare smaller (i.e., put on the left side of the tree). -UDPARD_PRIVATE int8_t txAVLPredicate(void* const user_reference, // NOSONAR Cavl API requires pointer to non-const. - const UdpardTreeNode* const node) -{ - const UdpardTxQueueItem* const target = (const UdpardTxQueueItem*) user_reference; - const UdpardTxQueueItem* const other = (const UdpardTxQueueItem*) node; - UDPARD_ASSERT((target != NULL) && (other != NULL)); - if (target->frame.udp_cyphal_header.priority > other->frame.udp_cyphal_header.priority) - { - return +1; - } - if (target->frame.udp_cyphal_header.transfer_id >= other->frame.udp_cyphal_header.transfer_id) - { - return +1; - } - return -1; -} - -/// Returns the number of frames enqueued or error (i.e., =1 or <0). -UDPARD_PRIVATE int32_t txPushSingleFrame(UdpardTxQueue* const que, - UdpardInstance* const ins, - const UdpardMicrosecond deadline_usec, - const UdpardSessionSpecifier* const specifier, - const UdpardNodeID src_node_id, - const UdpardNodeID dst_node_id, - const UdpardPortID port_id, - const UdpardTransferKind transfer_kind, - const UdpardPriority priority, - const UdpardTransferID transfer_id, - const size_t payload_size, - const void* const payload) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT((payload != NULL) || (payload_size == 0)); - // The size of a Frame header shouldn't change, but best to check it is at least bigger than 0 - UDPARD_ASSERT(sizeof(UdpardFrameHeader) > 0); // NOLINT - const size_t frame_payload_size = payload_size + sizeof(UdpardFrameHeader) + CRC_SIZE_BYTES; - UDPARD_ASSERT(frame_payload_size > payload_size); - - int32_t out = 0; - uint32_t frame_index = 0U; - bool end_of_transfer = true; - - TxItem* const tqi = - (que->size < que->capacity) ? txAllocateQueueItem(ins, specifier, deadline_usec, frame_payload_size) : NULL; - if (tqi != NULL) - { - if (payload_size > 0U) // The check is needed to avoid calling memcpy() with a NULL pointer, it's an UB. - { - UDPARD_ASSERT(payload != NULL); - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&tqi->payload_buffer[sizeof(UdpardFrameHeader)], payload, payload_size); // NOLINT - } - /// Create the FrameHeader - txMakeFrameHeader( - &tqi->base.frame.udp_cyphal_header, - src_node_id, - dst_node_id, - port_id, - transfer_kind, - priority, - transfer_id, - end_of_transfer, - frame_index); - - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&tqi->payload_buffer[0], &tqi->base.frame.udp_cyphal_header, sizeof(UdpardFrameHeader)); // NOLINT - - // Insert CRC - size_t frame_offset = payload_size + sizeof(UdpardFrameHeader); - TransferCRC crc = crcValue(crcAdd(CRC_INITIAL, payload_size, payload)); - uint8_t crc_as_byte[] = {(uint8_t)(crc & BYTE_MAX & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 1U) & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 2U) & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 3U) & CRC_BYTE_MASK)}; - for (unsigned int i = 0; i < sizeof(crc_as_byte); i++) - { - tqi->payload_buffer[frame_offset++] = crc_as_byte[i]; - } - - // Insert the newly created TX item into the queue. - const UdpardTreeNode* const res = cavlSearch(&que->root, &tqi->base.base, &txAVLPredicate, &avlTrivialFactory); - (void) res; - UDPARD_ASSERT(res == &tqi->base.base); - que->size++; - UDPARD_ASSERT(que->size <= que->capacity); - out = 1; // One frame enqueued. - } - else - { - out = -UDPARD_ERROR_OUT_OF_MEMORY; - } - UDPARD_ASSERT((out < 0) || (out == 1)); - return out; -} - -// Produces a chain of Tx queue items for later insertion into the Tx queue. The tail is NULL if OOM. -UDPARD_PRIVATE TxChain txGenerateMultiFrameChain(UdpardInstance* const ins, - const size_t presentation_layer_mtu, - const UdpardMicrosecond deadline_usec, - const UdpardSessionSpecifier* const specifier, - const UdpardNodeID src_node_id, - const UdpardNodeID dst_node_id, - const UdpardPortID port_id, - const UdpardTransferKind transfer_kind, - const UdpardPriority priority, - const UdpardTransferID transfer_id, - const size_t payload_size, - const void* const payload) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(presentation_layer_mtu > sizeof(UdpardFrameHeader)); - - // Otherwise, a single-frame transfer should be used. - // CRC_SIZE_BYTES - UDPARD_ASSERT(payload_size + CRC_SIZE_BYTES > presentation_layer_mtu - sizeof(UdpardFrameHeader)); - UDPARD_ASSERT(payload != NULL); - - TxChain out = {NULL, NULL, 0}; - const size_t payload_size_with_crc = payload_size + CRC_SIZE_BYTES; - size_t offset = 0U; - TransferCRC crc = crcValue(crcAdd(CRC_INITIAL, payload_size, payload)); - const uint8_t* payload_ptr = (const uint8_t*) payload; - uint32_t frame_index = 0U; - uint8_t crc_as_byte[] = {(uint8_t)(crc & BYTE_MAX & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 1U) & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 2U) & CRC_BYTE_MASK), - (uint8_t)(crc >> (BITS_PER_BYTE * 3U) & CRC_BYTE_MASK)}; - size_t last_crc_index = 0U; - size_t inserted_crc_amount = 0; - while (offset < payload_size_with_crc) - { - out.size++; - size_t frame_payload_size_with_header = 0U; - frame_payload_size_with_header = - ((payload_size_with_crc - offset + sizeof(UdpardFrameHeader)) < presentation_layer_mtu) - ? (payload_size_with_crc - offset + sizeof(UdpardFrameHeader)) - : (presentation_layer_mtu); - - TxItem* const tqi = txAllocateQueueItem(ins, specifier, deadline_usec, frame_payload_size_with_header); - if (NULL == out.head) - { - out.head = tqi; - } - else - { - out.tail->base.next_in_transfer = (UdpardTxQueueItem*) tqi; - } - out.tail = tqi; - // If the queue item is not generated successfully, - // we'll end the while loop early to stop the chain - // generation. - // We'll do the memory clean up after this function is done. - if (NULL == out.tail) - { - break; - } - const size_t frame_payload_size = frame_payload_size_with_header; - size_t frame_offset = 0U; - // Index to start copying payload to the frame. - size_t start_index = offset; - // Size of remaining payload + CRC - // We'll compare the size of remaining payload+crc and available size of current frame - // and set the move_size_with_crc to the smaller one. - size_t move_size_with_crc = payload_size_with_crc - offset - >= frame_payload_size - sizeof(UdpardFrameHeader) - ? frame_payload_size - sizeof(UdpardFrameHeader) - : payload_size_with_crc - offset; - // Index referring the end of copied payload with CRC. - size_t end_index = start_index + move_size_with_crc; - // True if all payload has been process and is ready to insert CRC - bool initial_payload_overrun = (end_index >= payload_size); - // Number of CRC byte that has been inserted. - size_t overrun_amount = ((initial_payload_overrun) ? end_index - payload_size : inserted_crc_amount) - inserted_crc_amount; - inserted_crc_amount += overrun_amount; - // Size of remaining payload without CRC - size_t payload_move_size = move_size_with_crc - overrun_amount; - - txMakeFrameHeader( - &out.tail->base.frame.udp_cyphal_header, - src_node_id, - dst_node_id, - port_id, - transfer_kind, - priority, - transfer_id, - false, - frame_index); - - if(initial_payload_overrun) - { - // Insert payload and header - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&out.tail->payload_buffer[0], &out.tail->base.frame.udp_cyphal_header, sizeof(UdpardFrameHeader)); // NOLINT - (void) memcpy(&out.tail->payload_buffer[sizeof(UdpardFrameHeader)], payload_ptr, payload_move_size); // NOLINT - frame_offset += sizeof(UdpardFrameHeader) + payload_move_size; - // Insert crc into same frame - for(unsigned int i = 0; i < overrun_amount; i++) - { - out.tail->payload_buffer[frame_offset++] = crc_as_byte[i + last_crc_index]; - } - last_crc_index += overrun_amount; - } - else - { - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&out.tail->payload_buffer[0], &out.tail->base.frame.udp_cyphal_header, sizeof(UdpardFrameHeader)); // NOLINT - (void) memcpy(&out.tail->payload_buffer[sizeof(UdpardFrameHeader)], payload_ptr, payload_move_size); // NOLINT - frame_offset += sizeof(UdpardFrameHeader) + payload_move_size; - } - payload_ptr += payload_move_size; - offset += move_size_with_crc; - - // Deal with last frame after inserting all payload and crc - if(end_index == payload_size_with_crc) - { - out.tail->base.frame.udp_cyphal_header.frame_index_eot = (uint32_t) (1U << (uint32_t) UDPARD_END_OF_TRANSFER_OFFSET) + frame_index; - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&out.tail->payload_buffer[0], &out.tail->base.frame.udp_cyphal_header, sizeof(UdpardFrameHeader)); // NOLINT - UDPARD_ASSERT((frame_offset) == out.tail->base.frame.payload_size); - } - ++frame_index; - } - return out; -} - -/// Returns the number of frames enqueued or error. -UDPARD_PRIVATE int32_t txPushMultiFrame(UdpardTxQueue* const que, - UdpardInstance* const ins, - const size_t presentation_layer_mtu, - const UdpardMicrosecond deadline_usec, - const UdpardSessionSpecifier* const specifier, - const UdpardNodeID src_node_id, - const UdpardNodeID dst_node_id, - const UdpardPortID port_id, - const UdpardTransferKind transfer_kind, - const UdpardPriority priority, - const UdpardTransferID transfer_id, - const size_t payload_size, - const void* const payload) -{ - - UDPARD_ASSERT((ins != NULL) && (que != NULL)); - UDPARD_ASSERT(presentation_layer_mtu > sizeof(UdpardFrameHeader)); - UDPARD_ASSERT(payload_size + CRC_SIZE_BYTES > presentation_layer_mtu - sizeof(UdpardFrameHeader)); - const size_t payload_size_with_crc = payload_size + CRC_SIZE_BYTES; - const size_t num_frames = (payload_size_with_crc - + presentation_layer_mtu - - sizeof(UdpardFrameHeader) - 1) - / (presentation_layer_mtu - - sizeof(UdpardFrameHeader)); - UDPARD_ASSERT(num_frames >= 2); - - int32_t out = 0; - if ((que->size + num_frames) <= que->capacity) - { - const TxChain sq = txGenerateMultiFrameChain(ins, - presentation_layer_mtu, - deadline_usec, - specifier, - src_node_id, - dst_node_id, - port_id, - transfer_kind, - priority, - transfer_id, - payload_size, - payload); - if (sq.tail != NULL) - { - UdpardTxQueueItem* next = &sq.head->base; - do - { - const UdpardTreeNode* const res = cavlSearch(&que->root, &next->base, &txAVLPredicate, &avlTrivialFactory); - (void) res; - UDPARD_ASSERT(res == &next->base); - UDPARD_ASSERT(que->root != NULL); - next = next->next_in_transfer; - } while (next != NULL); - UDPARD_ASSERT(num_frames == sq.size); - que->size += sq.size; - UDPARD_ASSERT(que->size <= que->capacity); - UDPARD_ASSERT((sq.size + 0ULL) <= INT32_MAX); - out = (int32_t) sq.size; - } - else - { - out = -UDPARD_ERROR_OUT_OF_MEMORY; - UdpardTxQueueItem* head = &sq.head->base; - // We'll clean up the memory after txGenerateMultiFrameChain is done - // with an UDPARD_ERROR_OUT_OF_MEMORY error raised. - while (head != NULL) - { - UdpardTxQueueItem* const next = head->next_in_transfer; - ins->memory_free(ins, head); - head = next; - } - } - - } - else // We predict that we're going to run out of queue, don't bother serializing the transfer. - { - out = -UDPARD_ERROR_OUT_OF_MEMORY; - } - UDPARD_ASSERT((out < 0) || (out >= 2)); - return out; -} - -// --------------------------------------------- RECEPTION --------------------------------------------- - -#define RX_SESSIONS_PER_SUBSCRIPTION (UDPARD_NODE_ID_MAX + 1U) - -/// The memory requirement model provided in the documentation assumes that the maximum size of this structure never -/// exceeds 48 bytes on any conventional platform. -/// A user that needs a detailed analysis of the worst-case memory consumption may compute the size of this -/// structure for the particular platform at hand manually or by evaluating its sizeof(). The fields are ordered to -/// minimize the amount of padding on all conventional platforms. -typedef struct UdpardInternalRxSession -{ - UdpardMicrosecond transfer_timestamp_usec; ///< Timestamp of the last received start-of-transfer. - size_t total_payload_size; ///< The payload size before the implicit truncation, including the CRC. - size_t payload_size; ///< How many bytes received so far. - uint8_t* payload; ///< Dynamically allocated and handed off to the application when done. - TransferCRC calculated_crc; ///< Updated with the received payload in real time. - UdpardTransferID transfer_id; - uint8_t redundant_transport_index; ///< Arbitrary value in [0, 255]. - uint32_t last_udp_header_index; -} UdpardInternalRxSession; - -/// High-level transport frame model. -typedef struct -{ - UdpardMicrosecond timestamp_usec; - UdpardHeaderVersion version; - UdpardPriority priority; - UdpardTransferKind transfer_kind; - UdpardPortID port_id; - UdpardNodeID source_node_id; - UdpardNodeID destination_node_id; - UdpardTransferID transfer_id; - bool start_of_transfer; - bool end_of_transfer; - size_t payload_size; - const void* payload; - uint32_t frame_index; -} RxFrameModel; - -UDPARD_PRIVATE UdpardPortID getPortIdFromDataSpecifiers(UdpardUdpPortID data_specifier) -{ - if ((uint16_t)(data_specifier >> UDPARD_SERVICE_NOT_MESSAGE_DATA_SPECIFIER_OFFSET) & 1U) - { - return (UdpardPortID) (data_specifier & UDPARD_SERVICE_ID_MASK); - } - return (UdpardPortID) (data_specifier & UDPARD_SUBJECT_ID_MASK); -} - -UDPARD_PRIVATE UdpardTransferKind getTransferKindFromDataSpecifier(UdpardUdpPortID data_specifier) -{ - if ((uint16_t)(data_specifier >> UDPARD_SERVICE_NOT_MESSAGE_DATA_SPECIFIER_OFFSET) & 1U) - { - return ((uint16_t)(data_specifier >> UDPARD_IRNR_DATA_SPECIFIER_OFFSET) & 1U) ? UdpardTransferKindRequest : UdpardTransferKindResponse; - } - return UdpardTransferKindMessage; -} - -/// Returns truth if the frame is valid and parsed successfully. False if the frame is not a valid Cyphal/UDP frame. -UDPARD_PRIVATE bool rxTryParseFrame(const UdpardMicrosecond timestamp_usec, - UdpardFrame* const frame, - RxFrameModel* const out) -{ - - UDPARD_ASSERT(frame != NULL); - UDPARD_ASSERT(out != NULL); - if (frame->payload_size < sizeof(frame->udp_cyphal_header)) - { - return false; - } - bool valid = true; - - // Get the Header out of the frame - UDPARD_ASSERT(frame->payload != NULL); - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&frame->udp_cyphal_header, frame->payload, sizeof(frame->udp_cyphal_header)); // NOLINT - out->timestamp_usec = timestamp_usec; - - out->version = frame->udp_cyphal_header.version; - out->priority = (UdpardPriority) frame->udp_cyphal_header.priority; - out->source_node_id = frame->udp_cyphal_header.source_node_id; - out->transfer_kind = getTransferKindFromDataSpecifier(frame->udp_cyphal_header.data_specifier); - out->port_id = getPortIdFromDataSpecifiers(frame->udp_cyphal_header.data_specifier); - out->destination_node_id = frame->udp_cyphal_header.destination_node_id; - // Payload parsing. - out->payload_size = frame->payload_size - sizeof(frame->udp_cyphal_header); // Cut off the header size. - out->payload = (void*) ((uint8_t*) frame->payload + sizeof(frame->udp_cyphal_header)); - - out->transfer_id = frame->udp_cyphal_header.transfer_id; - out->start_of_transfer = (((frame->udp_cyphal_header.frame_index_eot) & (UDPARD_MAX_FRAME_INDEX)) == 0); - out->end_of_transfer = ((frame->udp_cyphal_header.frame_index_eot >> UDPARD_END_OF_TRANSFER_OFFSET) == 1); - out->frame_index = frame->udp_cyphal_header.frame_index_eot; - // Make sure header version is supported - valid = valid && (out->version >= UDPARD_CYPHAL_HEADER_VERSION); - if (out->transfer_kind != UdpardTransferKindMessage) - { - valid = valid && (out->source_node_id != out->destination_node_id); - } - // Anonymous transfers can be only single-frame transfers. - valid = - valid && ((out->start_of_transfer && out->end_of_transfer) || (UDPARD_NODE_ID_UNSET != out->source_node_id)); - // A frame that is a part of a multi-frame transfer cannot be empty (tail byte not included). - valid = valid && ((out->payload_size > 0) || (out->start_of_transfer && out->end_of_transfer)); - return valid; -} - -UDPARD_PRIVATE void rxInitTransferMetadataFromFrame(const RxFrameModel* const frame, - UdpardTransferMetadata* const out_transfer) -{ - UDPARD_ASSERT(frame != NULL); - UDPARD_ASSERT(frame->payload != NULL); - UDPARD_ASSERT(out_transfer != NULL); - out_transfer->priority = frame->priority; - out_transfer->transfer_kind = frame->transfer_kind; - out_transfer->port_id = frame->port_id; - out_transfer->remote_node_id = frame->source_node_id; - out_transfer->transfer_id = frame->transfer_id; -} - -// Assume we will never roll over a transfer id with 64bits -UDPARD_PRIVATE uint64_t rxComputeTransferIDDifference(const uint64_t a, const uint64_t b) -{ - UDPARD_ASSERT(a <= UDPARD_TRANSFER_ID_MAX); - UDPARD_ASSERT(b <= UDPARD_TRANSFER_ID_MAX); - return a - b; -} - -UDPARD_PRIVATE int8_t rxSessionWritePayload(UdpardInstance* const ins, - UdpardInternalRxSession* const rxs, - const size_t extent, - const size_t payload_size, - const void* const payload) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(rxs != NULL); - UDPARD_ASSERT((payload != NULL) || (payload_size == 0U)); - UDPARD_ASSERT(rxs->payload_size <= extent); // This invariant is enforced by the subscription logic. - UDPARD_ASSERT(rxs->payload_size <= rxs->total_payload_size); - - rxs->total_payload_size += payload_size; - - // Allocate the payload lazily, as late as possible. - if ((NULL == rxs->payload) && (extent > 0U)) - { - UDPARD_ASSERT(rxs->payload_size == 0); - rxs->payload = ins->memory_allocate(ins, extent); - } - - int8_t out = 0; - if (rxs->payload != NULL) - { - // Copy the payload into the contiguous buffer. Apply the implicit truncation rule if necessary. - size_t bytes_to_copy = payload_size; - if ((rxs->payload_size + bytes_to_copy) > extent) - { - UDPARD_ASSERT(rxs->payload_size <= extent); - bytes_to_copy = extent - rxs->payload_size; - UDPARD_ASSERT((rxs->payload_size + bytes_to_copy) == extent); - UDPARD_ASSERT(bytes_to_copy < payload_size); - } - // This memcpy() call here is one of the two variable-complexity operations in the RX pipeline; - // the other one is the search of the matching subscription state. - // Excepting these two cases, the entire RX pipeline contains neither loops nor recursion. - // Intentional violation of MISRA: indexing on a pointer. This is done to avoid pointer arithmetics. - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. - (void) memcpy(&rxs->payload[rxs->payload_size], payload, bytes_to_copy); // NOLINT NOSONAR - rxs->payload_size += bytes_to_copy; - UDPARD_ASSERT(rxs->payload_size <= extent); - } - else - { - UDPARD_ASSERT(rxs->payload_size == 0); - out = (extent > 0U) ? -UDPARD_ERROR_OUT_OF_MEMORY : 0; - } - UDPARD_ASSERT(out <= 0); - return out; -} - -UDPARD_PRIVATE void rxSessionRestart(UdpardInstance* const ins, UdpardInternalRxSession* const rxs) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(rxs != NULL); - ins->memory_free(ins, rxs->payload); // May be NULL, which is OK. - rxs->total_payload_size = 0U; - rxs->payload_size = 0U; - rxs->payload = NULL; - rxs->calculated_crc = CRC_INITIAL; - rxs->transfer_id = (UdpardTransferID) (rxs->transfer_id + 1U) & UDPARD_TRANSFER_ID_MAX; -} - -UDPARD_PRIVATE int8_t rxSessionAcceptFrame(UdpardInstance* const ins, - UdpardInternalRxSession* const rxs, - const RxFrameModel* const frame, - const size_t extent, - UdpardRxTransfer* const out_transfer) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(rxs != NULL); - UDPARD_ASSERT(frame != NULL); - UDPARD_ASSERT(frame->payload != NULL); - UDPARD_ASSERT(frame->transfer_id <= UDPARD_TRANSFER_ID_MAX); - UDPARD_ASSERT(out_transfer != NULL); - - if (frame->start_of_transfer) // The transfer timestamp is the timestamp of its first frame. - { - rxs->transfer_timestamp_usec = frame->timestamp_usec; - } - - rxs->calculated_crc = crcAdd(rxs->calculated_crc, frame->payload_size, frame->payload); - - int8_t out = rxSessionWritePayload(ins, rxs, extent, frame->payload_size, frame->payload); - if (out < 0) - { - UDPARD_ASSERT(-UDPARD_ERROR_OUT_OF_MEMORY == out); - rxSessionRestart(ins, rxs); // Out-of-memory. - } - else if (frame->end_of_transfer) - { - UDPARD_ASSERT(0 == out); - if (CRC_RESIDUE == rxs->calculated_crc) - { - - out = 1; // One transfer received, notify the application. - rxInitTransferMetadataFromFrame(frame, &out_transfer->metadata); - out_transfer->timestamp_usec = rxs->transfer_timestamp_usec; - out_transfer->payload_size = rxs->payload_size; - out_transfer->payload = rxs->payload; - - // Cut off the CRC from the payload if it's there -- we don't want to expose it to the user. - UDPARD_ASSERT(rxs->total_payload_size >= rxs->payload_size); - // For single frame transfers, the truncated amount will be 0 (total_payload_size == payload_size) - const size_t truncated_amount = rxs->total_payload_size - rxs->payload_size; - if (CRC_SIZE_BYTES > truncated_amount) - { - UDPARD_ASSERT(out_transfer->payload_size >= (CRC_SIZE_BYTES - truncated_amount)); - out_transfer->payload_size -= CRC_SIZE_BYTES - truncated_amount; - } - - rxs->payload = NULL; // Ownership passed over to the application, nullify to prevent freeing. - } - rxSessionRestart(ins, rxs); // Successful completion. - } - return out; -} - -/// RX session state machine update is the most intricate part of any Cyphal transport implementation. -/// The state model used here is derived from the reference pseudocode given in the original UAVCAN v0 -/// specification. The Cyphal/UDP v1 specification, which this library is an implementation of, does not provide any -/// reference pseudocode. Instead, it takes a higher-level, more abstract approach, where only the high-level -/// requirements are given and the particular algorithms are left to be implementation-defined. Such abstract -/// approach is much advantageous because it allows implementers to choose whatever solution works best for the -/// specific application at hand, while the wire compatibility is still guaranteed by the high-level requirements -/// given in the specification. -UDPARD_PRIVATE int8_t rxSessionUpdate(UdpardInstance* const ins, - UdpardInternalRxSession* const rxs, - const RxFrameModel* const frame, - const uint8_t redundant_transport_index, - const UdpardMicrosecond transfer_id_timeout_usec, - const size_t extent, - UdpardRxTransfer* const out_transfer) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(rxs != NULL); - UDPARD_ASSERT(frame != NULL); - UDPARD_ASSERT(out_transfer != NULL); - UDPARD_ASSERT(rxs->transfer_id <= UDPARD_TRANSFER_ID_MAX); - UDPARD_ASSERT(frame->transfer_id <= UDPARD_TRANSFER_ID_MAX); - - const bool tid_timed_out = (frame->timestamp_usec > rxs->transfer_timestamp_usec) && - ((frame->timestamp_usec - rxs->transfer_timestamp_usec) > transfer_id_timeout_usec); - - const bool not_previous_tid = rxComputeTransferIDDifference(rxs->transfer_id, frame->transfer_id) > 1; - - const bool need_restart = tid_timed_out || ((rxs->redundant_transport_index == redundant_transport_index) && - frame->start_of_transfer && not_previous_tid); - - if (need_restart) - { - rxs->total_payload_size = 0U; - rxs->payload_size = 0U; - rxs->calculated_crc = CRC_INITIAL; - rxs->transfer_id = frame->transfer_id; - rxs->redundant_transport_index = redundant_transport_index; - rxs->last_udp_header_index = 0U; - } - - int8_t out = 0; - if (need_restart && (!frame->start_of_transfer)) - { - rxSessionRestart(ins, rxs); // SOT-miss, no point going further. - } - else - { - // multi-frame transfer - if (!(frame->start_of_transfer && frame->end_of_transfer)) - { - if (frame->end_of_transfer) - { - uint32_t next_expected_frame_index = (1U << UDPARD_END_OF_TRANSFER_OFFSET) + rxs->last_udp_header_index + 1; - if (frame->frame_index != next_expected_frame_index) - { - // Out of order multiframe packet received - out = -UDPARD_ERROR_OUT_OF_ORDER; - // Reset previous frame index to 0 - rxs->last_udp_header_index = 0U; - rxSessionRestart(ins, rxs); - return out; - } - rxs->last_udp_header_index = 0U; - } - else - { - if ((!frame->start_of_transfer && frame->frame_index != rxs->last_udp_header_index + 1) - || (frame->start_of_transfer && frame->frame_index != 0U)) - { - // Out of order multiframe packet received - out = -UDPARD_ERROR_OUT_OF_ORDER; - rxSessionRestart(ins, rxs); - return out; - } - rxs->last_udp_header_index = frame->frame_index; - } - } - const bool correct_transport = (rxs->redundant_transport_index == redundant_transport_index); - const bool correct_tid = (frame->transfer_id == rxs->transfer_id); - if (correct_transport && correct_tid) - { - out = rxSessionAcceptFrame(ins, rxs, frame, extent, out_transfer); - } - } - return out; -} - -UDPARD_PRIVATE int8_t rxAcceptFrame(UdpardInstance* const ins, - UdpardRxSubscription* const subscription, - const RxFrameModel* const frame, - const uint8_t redundant_transport_index, - UdpardRxTransfer* const out_transfer) -{ - UDPARD_ASSERT(ins != NULL); - UDPARD_ASSERT(subscription != NULL); - UDPARD_ASSERT(subscription->port_id == frame->port_id); - UDPARD_ASSERT(frame != NULL); - UDPARD_ASSERT(frame->payload != NULL); - UDPARD_ASSERT(frame->transfer_id <= UDPARD_TRANSFER_ID_MAX); - UDPARD_ASSERT((UDPARD_NODE_ID_UNSET == frame->destination_node_id) || (ins->node_id == frame->destination_node_id)); - UDPARD_ASSERT(out_transfer != NULL); - - int8_t out = 0; - if ((frame->source_node_id <= UDPARD_NODE_ID_MAX) && (frame->source_node_id != UDPARD_NODE_ID_UNSET)) - { - // If such session does not exist, create it. This only makes sense if this is the first frame of a - // transfer, otherwise, we won't be able to receive the transfer anyway so we don't bother. - if ((NULL == subscription->sessions[frame->source_node_id]) && frame->start_of_transfer) - { - UdpardInternalRxSession* const rxs = - (UdpardInternalRxSession*) ins->memory_allocate(ins, sizeof(UdpardInternalRxSession)); - subscription->sessions[frame->source_node_id] = rxs; - if (rxs != NULL) - { - rxs->transfer_timestamp_usec = frame->timestamp_usec; - rxs->total_payload_size = 0U; - rxs->payload_size = 0U; - rxs->payload = NULL; - rxs->calculated_crc = CRC_INITIAL; - rxs->transfer_id = frame->transfer_id; - rxs->redundant_transport_index = redundant_transport_index; - } - else - { - out = -UDPARD_ERROR_OUT_OF_MEMORY; - } - } - // There are two possible reasons why the session may not exist: 1. OOM; 2. SOT-miss. - if (subscription->sessions[frame->source_node_id] != NULL) - { - UDPARD_ASSERT(out == 0); - out = rxSessionUpdate(ins, - subscription->sessions[frame->source_node_id], - frame, - redundant_transport_index, - subscription->transfer_id_timeout_usec, - subscription->extent, - out_transfer); - } - } - else - { - UDPARD_ASSERT(frame->source_node_id == UDPARD_NODE_ID_UNSET); - // Anonymous transfers are stateless. No need to update the state machine, just blindly accept it. - // We have to copy the data into an allocated storage because the API expects it: the lifetime shall be - // independent of the input data and the memory shall be free-able. - const size_t payload_size = - (subscription->extent < frame->payload_size) ? subscription->extent : frame->payload_size; - void* const payload = ins->memory_allocate(ins, payload_size); - if (payload != NULL) - { - rxInitTransferMetadataFromFrame(frame, &out_transfer->metadata); - out_transfer->timestamp_usec = frame->timestamp_usec; - out_transfer->payload_size = payload_size; - out_transfer->payload = payload; - // Clang-Tidy raises an error recommending the use of memcpy_s() instead. - // We ignore it because the safe functions are poorly supported; reliance on them may limit the - // portability. - (void) memcpy(payload, frame->payload, payload_size); // NOLINT - out = 1; - } - else - { - out = -UDPARD_ERROR_OUT_OF_MEMORY; - } - } - return out; -} - -UDPARD_PRIVATE int8_t -rxSubscriptionPredicateOnPortID(void* const user_reference, // NOSONAR Cavl API requires pointer to non-const. - const UdpardTreeNode* const node) -{ - const UdpardPortID sought = *((const UdpardPortID*) user_reference); - const UdpardPortID other = ((const UdpardRxSubscription*) node)->port_id; - static const int8_t NegPos[2] = {-1, +1}; - // Clang-Tidy mistakenly identifies a narrowing cast to int8_t here, which is incorrect. - return (sought == other) ? 0 : NegPos[sought > other]; // NOLINT no narrowing conversion is taking place here -} - -UDPARD_PRIVATE int8_t -rxSubscriptionPredicateOnStruct(void* const user_reference, // NOSONAR Cavl API requires pointer to non-const. - const UdpardTreeNode* const node) -{ - return rxSubscriptionPredicateOnPortID(&((UdpardRxSubscription*) user_reference)->port_id, node); -} - -// --------------------------------------------- PUBLIC API --------------------------------------------- - -UdpardInstance udpardInit(const UdpardMemoryAllocate memory_allocate, const UdpardMemoryFree memory_free) -{ - UDPARD_ASSERT(memory_allocate != NULL); - UDPARD_ASSERT(memory_free != NULL); - const UdpardInstance out = { - .user_reference = NULL, - .node_id = UDPARD_NODE_ID_UNSET, - .memory_allocate = memory_allocate, - .memory_free = memory_free, - .rx_subscriptions = {NULL, NULL, NULL}, - }; - return out; -} - -UdpardTxQueue udpardTxInit(const size_t capacity, const size_t mtu_bytes) -{ - UdpardTxQueue out = { - .capacity = capacity, - .mtu_bytes = mtu_bytes, - .size = 0, - .root = NULL, - .user_reference = NULL, - }; - return out; -} - -int32_t udpardTxPush(UdpardTxQueue* const que, - UdpardInstance* const ins, - const UdpardMicrosecond tx_deadline_usec, - const UdpardTransferMetadata* const metadata, - const size_t payload_size, - const void* const payload) -{ - int32_t out = -UDPARD_ERROR_INVALID_ARGUMENT; - if ((ins != NULL) && (que != NULL) && (metadata != NULL) && ((payload != NULL) || (0U == payload_size))) - { - const size_t pl_mtu = adjustPresentationLayerMTU(que->mtu_bytes); - UdpardSessionSpecifier specifier; - const int32_t specifier_result = txMakeSessionSpecifier(metadata, ins->node_id, ins->local_ip_addr, &specifier); - if (specifier_result >= 0) - { - if (payload_size + CRC_SIZE_BYTES <= pl_mtu - sizeof(UdpardFrameHeader)) - { - out = txPushSingleFrame(que, - ins, - tx_deadline_usec, - &specifier, - ins->node_id, - metadata->remote_node_id, - metadata->port_id, - metadata->transfer_kind, - metadata->priority, - metadata->transfer_id, - payload_size, - payload); - UDPARD_ASSERT((out < 0) || (out == 1)); - } - else - { - out = txPushMultiFrame(que, - ins, - pl_mtu, - tx_deadline_usec, - &specifier, - ins->node_id, - metadata->remote_node_id, - metadata->port_id, - metadata->transfer_kind, - metadata->priority, - metadata->transfer_id, - payload_size, - payload); - UDPARD_ASSERT((out < 0) || (out >= 2)); - } - } - else - { - out = specifier_result; - } - } - UDPARD_ASSERT(out != 0); - return out; -} - -const UdpardTxQueueItem* udpardTxPeek(const UdpardTxQueue* const que) -{ - const UdpardTxQueueItem* out = NULL; - if (que != NULL) - { - // Paragraph 6.7.2.1.15 of the C standard says: - // A pointer to a structure object, suitably converted, points to its initial member, and vice versa. - out = (const UdpardTxQueueItem*) cavlFindExtremum(que->root, false); - } - return out; -} - -UdpardTxQueueItem* udpardTxPop(UdpardTxQueue* const que, const UdpardTxQueueItem* const item) -{ - UdpardTxQueueItem* out = NULL; - if ((que != NULL) && (item != NULL)) - { - // Intentional violation of MISRA: casting away const qualifier. This is considered safe because the API - // contract dictates that the pointer shall point to a mutable entity in RAM previously allocated by the - // memory manager. It is difficult to avoid this cast in this context. - out = (UdpardTxQueueItem*) item; // NOSONAR casting away const qualifier. - // Paragraph 6.7.2.1.15 of the C standard says: - // A pointer to a structure object, suitably converted, points to its initial member, and vice versa. - // Note that the highest-priority frame is always a leaf node in the AVL tree, which means that it is very - // cheap to remove. - cavlRemove(&que->root, &item->base); - que->size--; - } - return out; -} - -int8_t udpardRxAccept(UdpardInstance* const ins, - const UdpardMicrosecond timestamp_usec, - UdpardFrame* const frame, - const uint8_t redundant_transport_index, - UdpardRxTransfer* const out_transfer, - UdpardRxSubscription** const out_subscription) -{ - int8_t out = -UDPARD_ERROR_INVALID_ARGUMENT; - if ((ins != NULL) && (out_transfer != NULL) && (frame != NULL) && - ((frame->payload != NULL) || (sizeof(frame->udp_cyphal_header) == frame->payload_size))) - { - RxFrameModel model = {0}; - if (rxTryParseFrame(timestamp_usec, frame, &model)) - { - if ((UDPARD_NODE_ID_UNSET == model.destination_node_id) || (ins->node_id == model.destination_node_id)) - { - // This is the reason the function has a logarithmic time complexity of the number of subscriptions. - // Note also that this one of the two variable-complexity operations in the RX pipeline; the other - // one is memcpy(). Excepting these two cases, the entire RX pipeline contains neither loops nor - // recursion. - UdpardRxSubscription* const sub = - (UdpardRxSubscription*) cavlSearch(&ins->rx_subscriptions[(size_t) model.transfer_kind], - &model.port_id, - &rxSubscriptionPredicateOnPortID, - NULL); - if (out_subscription != NULL) - { - *out_subscription = sub; // Expose selected instance to the caller. - } - if (sub != NULL) - { - UDPARD_ASSERT(sub->port_id == model.port_id); - out = rxAcceptFrame(ins, sub, &model, redundant_transport_index, out_transfer); - } - else - { - out = 0; // No matching subscription. - } - } - else - { - out = 0; // Mis-addressed frame (normally it should be filtered out by the hardware). - } - } - else - { - out = 0; // A non-Cyphal/UDP input frame. - } - } - UDPARD_ASSERT(out <= 1); - return out; -} - -/// DONE -> This shouldn't change from canard to udpard -int8_t udpardRxSubscribe(UdpardInstance* const ins, - const UdpardTransferKind transfer_kind, - const UdpardPortID port_id, - const size_t extent, - const UdpardMicrosecond transfer_id_timeout_usec, - UdpardRxSubscription* const out_subscription) -{ - int8_t out = -UDPARD_ERROR_INVALID_ARGUMENT; - const size_t tk = (size_t) transfer_kind; - if ((ins != NULL) && (out_subscription != NULL) && (tk < UDPARD_NUM_TRANSFER_KINDS)) - { - // Reset to the initial state. This is absolutely critical because the new payload size limit may be larger - // than the old value; if there are any payload buffers allocated, we may overrun them because they are - // shorter than the new payload limit. So we clear the subscription and thus ensure that no overrun may - // occur. - out = udpardRxUnsubscribe(ins, transfer_kind, port_id); - if (out >= 0) - { - out_subscription->transfer_id_timeout_usec = transfer_id_timeout_usec; - out_subscription->extent = extent; - out_subscription->port_id = port_id; - for (size_t i = 0; i < RX_SESSIONS_PER_SUBSCRIPTION; i++) - { - // The sessions will be created ad-hoc. Normally, for a low-jitter deterministic system, - // we could have pre-allocated sessions here, but that requires too much memory to be feasible. - // We could accept an extra argument that would instruct us to pre-allocate sessions here? - out_subscription->sessions[i] = NULL; - } - const UdpardTreeNode* const res = cavlSearch(&ins->rx_subscriptions[tk], - out_subscription, - &rxSubscriptionPredicateOnStruct, - &avlTrivialFactory); - (void) res; - UDPARD_ASSERT(res == &out_subscription->base); - out = (out > 0) ? 0 : 1; - } - } - return out; -} - -/// DONE -> This shouldn't change from canard to udpard -int8_t udpardRxUnsubscribe(UdpardInstance* const ins, - const UdpardTransferKind transfer_kind, - const UdpardPortID port_id) +const UdpardTxItem* udpardTxPeek(const UdpardTx* const self) { - int8_t out = -UDPARD_ERROR_INVALID_ARGUMENT; - const size_t tk = (size_t) transfer_kind; - if ((ins != NULL) && (tk < UDPARD_NUM_TRANSFER_KINDS)) - { - UdpardPortID port_id_mutable = port_id; - UdpardRxSubscription* const sub = (UdpardRxSubscription*) - cavlSearch(&ins->rx_subscriptions[tk], &port_id_mutable, &rxSubscriptionPredicateOnPortID, NULL); - if (sub != NULL) - { - cavlRemove(&ins->rx_subscriptions[tk], &sub->base); - UDPARD_ASSERT(sub->port_id == port_id); - out = 1; - for (size_t i = 0; i < RX_SESSIONS_PER_SUBSCRIPTION; i++) - { - ins->memory_free(ins, (sub->sessions[i] != NULL) ? sub->sessions[i]->payload : NULL); - ins->memory_free(ins, sub->sessions[i]); - sub->sessions[i] = NULL; - } - } - else - { - out = 0; - } - } - return out; + (void) self; + return NULL; } diff --git a/libudpard/udpard.h b/libudpard/udpard.h index 0ed06ce..6f749fd 100644 --- a/libudpard/udpard.h +++ b/libudpard/udpard.h @@ -5,14 +5,165 @@ /// `____/ .___/`___/_/ /_/`____/`__, / .___/_/ /_/`__,_/_/ /// /_/ /____/_/ /// -/// description tbd -/// This library is based heavily on libcanard -// -------------------------------------------------------------------------------------------------------------------- +/// LibUDPard is a compact implementation of the Cyphal/UDP protocol for high-integrity real-time embedded systems. +/// It is designed for use in robust deterministic embedded systems equipped with at least 64K ROM and RAM. +/// The codebase is compliant with a large subset of MISRA C, has full test coverage, and is validated by at least +/// two static analyzers. The library is designed to be compatible with any conventional target platform and +/// instruction set architecture, from 8 to 64 bit, little- and big-endian, RTOS-based or baremetal, +/// as long as there is a standards-compliant ISO C99 compiler available. +/// +/// The library offers a very low-level API that may be cumbersome to use in many applications. +/// Users seeking a higher-level API are encouraged to use LibCyphal instead, which builds on top of LibUDPard et al. +/// +/// +/// INTEGRATION +/// +/// The library is intended to be integrated into the end application by simply copying its source files into the +/// source tree of the project; it does not require any special compilation options and should work out of the box. +/// There are build-time configuration parameters defined near the top of udpard.c, but they are optional to use. +/// +/// As explained in this documentation, the library requires a deterministic constant-time dynamic memory allocator. +/// If your target platform does not provide a deterministic memory manager, it is recommended to use O1Heap +/// (MIT licensed): https://github.com/pavel-kirienko/o1heap. +/// +/// To use the library, the application needs to provide an implementation of the UDP/IP stack with IGMP support. +/// POSIX-based systems may use the standard Berkeley sockets API, while more constrained embedded systems may choose +/// to rely either on a third-party solution like LwIP or a custom UDP/IP stack implementation. +/// +/// Cyphal/UDP leverages only multicast IP addresses; the underlying UDP/IP stack does not need to support ARP. +/// +/// +/// ARCHITECTURE +/// +/// In the following description, the terms "local Cyphal node" and "application" are used interchangeably. +/// Some applications may require more than one logical Cyphal node to operate side-by-side. +/// +/// This library implements the Cyphal/UDP transport protocol. Resembling the conventional OSI model, the Cyphal +/// protocol stack --- when implemented with the help of this library --- consists of the following layers: +/// +/// LAYER | DESCRIPTION +/// ----------------|--------------------------------------------------------------------------------------- +/// APPLICATION | User-defined and Cyphal-standard application logic +/// PRESENTATION | Autogenerated code for DSDL serialization/deserialization (see Nunavut) +/// +-> TRANSPORT | THIS LIBRARY +/// | NETWORK | The UDP/IP stack provided by the application (LwIP, custom, Berkeley sockets, etc). +/// | +/// +------ you are here +/// +/// The library consists of three independent parts: +/// +/// - The transmission pipeline (TX pipeline) for publishing messages and sending RPC-service requests & responses. +/// - The reception pipeline (RX pipeline), which in turn is built from two sub-pipelines: +/// - subscriptions -- for subscribing to subjects (aka topics); +/// - service dispatcher -- for receiving service requests and responses; both clients and servers need this. +/// +/// As these components share no resources within the library, they can be used in different threads, +/// provided that there are no thread-unsafe resources shared between them in the application (such as heaps). +/// +/// The library supports at most UDPARD_NETWORK_INTERFACE_COUNT_MAX redundant network interfaces. +/// Transfers received from each interface are reassembled independently and the first interface to complete a +/// transfer is always chosen to deliver the transfer to the application, while the transfers from the slower +/// interface are discarded as duplicates. The application must assign each of the redundant interface a +/// unique integer ID in the range [0, UDPARD_NETWORK_INTERFACE_COUNT_MAX) to allow the library to distinguish +/// between them. +/// +/// As will be shown below, a typical application with R redundant network interfaces and S topic subscriptions needs +/// R*(S+2) sockets (or equivalent abstractions provided by the underlying UDP/IP stack). +/// +/// Transmission pipeline +/// +/// The transmission pipeline is used to publish messages and send RPC-service requests and responses to the network +/// through a particular redundant interface. A Cyphal node with R redundant network interfaces needs to instantiate +/// R transmission pipelines, one per interface, unless the application is not interested in sending data at all. +/// The transmission pipeline contains a prioritized queue of UDP datagrams scheduled for transmission via its +/// network interface. The typical usage pattern is to enqueue Cyphal transfers using dedicated functions (see +/// udpardTxPublish, udpardTxRequest, udpardTxRespond) into all instances of transmission pipelines +/// (i.e., once per redundant interface) and periodically check the network interface for readiness to accept writes; +/// once the interface is ready, pop the next datagram scheduled for transmission from the queue and send it. +/// +/// Each transmission pipeline instance requires one socket (or a similar abstraction provided by the underlying +/// UDP/IP stack) that is not connected to any specific remote endpoint (i.e., usable with sendto(), +/// speaking in terms of Berkeley sockets). In the case of redundant interfaces, each socket may need to be configured +/// to emit data through its specific interface. +/// +/// Graphically, the transmission pipeline is arranged as follows: +/// +/// +---> TX PIPELINE ---> UDP SOCKET ---> REDUNDANT INTERFACE A +/// | +/// SERIALIZED TRANSFERS ---+---> TX PIPELINE ---> UDP SOCKET ---> REDUNDANT INTERFACE B +/// | +/// +---> ... +/// +/// The library supports configurable DSCP marking of the outgoing UDP datagrams as a function of Cyphal transfer +/// priority level. This is configured separately per TX pipeline instance (i.e., per network interface). +/// +/// The maximum transmission unit (MTU) can also be configured separately per TX pipeline instance. +/// Applications that are interested in maximizing their wire compatibility should not change the default MTU setting. +/// +/// +/// Reception pipeline +/// +/// The reception pipelines are used to subscribe to subjects (aka topics) and to receive RPC-service requests and +/// responses. The former are handled by "subscriptions" and the latter two are managed by a "service dispatcher". +/// Said pipelines are entirely independent from each other and can be operated from different threads, +/// as they share no resources. +/// +/// The reception pipeline is able to accept datagrams with arbitrary MTU. +/// +/// The application should instantiate one subscription instance per subject it needs to receive messages from, +/// irrespective of the number of redundant interfaces. There needs to be one socket (or a similar abstraction +/// provided by the underlying UDP/IP stack) per subscription instance per redundant interface, +/// each socket bound to the same UDP/IP endpoint (IP address and UDP port) which is selected by the library when +/// the subscription is created. +/// The application needs to listen to all these sockets simultaneously and pass the received UDP datagrams to the +/// corresponding subscription instance as they arrive, thus unifying the datagrams received from all redundant +/// interface sockets into a single stream. +/// At the output, subscription instances provide reassembled and deduplicated stream of Cyphal transfers ready for +/// deserialization. +/// +/// Graphically, the subscription pipeline is arranged as shown below. +/// Remember that the application with S topic subscriptions would have S such pipelines, one per subscription. +/// +/// REDUNDANT INTERFACE A ---> UDP SOCKET ---+ +/// | +/// REDUNDANT INTERFACE B ---> UDP SOCKET ---+---> SUBSCRIPTION ---> SERIALIZED TRANSFERS +/// | +/// ... ---+ +/// +/// The application should instantiate a single service dispatcher instance irrespective of the number of redundant +/// interfaces or the set of RPC-services it is interested in (unless it is not interested in RPC-services at all). +/// The service dispatcher instance requires a single socket (or a similar abstraction provided by the underlying +/// UDP/IP stack) per redundant interface, each socket bound to the same UDP/IP endpoint (IP address and UDP port) +/// which is selected by the library when the service dispatcher is created. +/// The application needs to listen to all these sockets simultaneously and pass the received UDP datagrams to +/// the service dispatcher instance as they arrive, thus unifying the datagrams received from all redundant +/// interface sockets into a single stream. +/// +/// The service dispatcher by itself is not useful; it needs to be configured with the set of RPC-services +/// that the application is interested in. This is done by creating RPC-service RX ports and registering them +/// with the service dispatcher. The service dispatcher will then forward the received requests and responses +/// to the corresponding RPC-service RX ports; the application can then deserialize and process them. +/// +/// Graphically, the service dispatcher pipeline is arranged as shown below. +/// +/// REDUNDANT INTERFACE A ---> UDP SOCKET ---+ +---> RPC-SERVICE X ---> SERIALIZED TRANSFERS +/// | | +/// REDUNDANT INTERFACE B ---> UDP SOCKET ---+---> SERVICE DISPATCHER ---+---> RPC-SERVICE Y ---> SERIALIZED TRANSFERS +/// | | +/// ... ---+ +---> ... +/// +/// In summary, to make a service request, the application needs a TX pipeline to transmit the request and +/// a service dispatcher with a registered RPC-service RX port to receive the response. Same holds if the +/// application needs to handle a service request, except that the RX port will be used to accept the request +/// and the TX pipeline will be used to transmit the response. +/// +/// -------------------------------------------------------------------------------------------------------------------- /// /// This software is distributed under the terms of the MIT License. -/// Copyright (c) 2016 OpenCyphal. +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT /// Author: Pavel Kirienko -/// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. #ifndef UDPARD_H_INCLUDED #define UDPARD_H_INCLUDED @@ -27,68 +178,62 @@ extern "C" { /// Semantic version of this library (not the Cyphal specification). /// API will be backward compatible within the same major version. -#define UDPARD_VERSION_MAJOR 0 +#define UDPARD_VERSION_MAJOR 1 #define UDPARD_VERSION_MINOR 0 /// The version number of the Cyphal specification implemented by this library. #define UDPARD_CYPHAL_SPECIFICATION_VERSION_MAJOR 1 #define UDPARD_CYPHAL_SPECIFICATION_VERSION_MINOR 0 -/// The version number of the Cyphal Header supported by this library -#define UDPARD_CYPHAL_HEADER_VERSION 1 - /// These error codes may be returned from the library API calls whose return type is a signed integer in the negated /// form (e.g., error code 2 returned as -2). A non-negative return value represents success. /// API calls whose return type is not a signed integer cannot fail by contract. /// No other error states may occur in the library. -/// By contract, a well-characterized application with a properly sized memory pool will never encounter errors. +/// By contract, a well-characterized application with properly sized memory pools will never encounter errors. /// The error code 1 is not used because -1 is often used as a generic error code in 3rd-party code. #define UDPARD_ERROR_INVALID_ARGUMENT 2 #define UDPARD_ERROR_OUT_OF_MEMORY 3 -#define UDPARD_ERROR_OUT_OF_ORDER 4 - -/// In the case that we still need error codes but need to mutate an input we will default to a success code -#define UDPARD_SUCCESS 0 /// MTU values for the supported protocols. /// RFC 791 states that hosts must be prepared to accept datagrams of up to 576 octets and it is expected that this -/// library will receive non ip-fragmented datagrams thus the minimum MTU should be larger than 576. -/// That being said, the MTU here is set to 1408 which is derived from -/// A 1500B Ethernet MTU RFC 894 - 60B IPv4 max header - 8B UDP Header - 24B Cyphal header which is equal to 1408B -#define UDPARD_MTU_MAX 1408U // Note that to guarantee a single frame transfer your max payload size shall be 1404 - // This value is to accomodate for a 4B CRC which is appended to the transfer. -#define UDPARD_MTU_UDP_IPV4 UDPARD_MTU_MAX -#define UDPARD_MTU_UDP_IPV6 UDPARD_MTU_MAX +/// library will receive non IP-fragmented datagrams thus the minimum MTU should be larger than 576. +/// That being said, the MTU here is set to 1408 which is derived as: +/// 1500B Ethernet MTU (RFC 894) - 60B IPv4 max header - 8B UDP Header - 24B Cyphal header +#define UDPARD_MTU_MAX 1408U +/// To guarantee a single frame transfer, the maximum payload size shall be 4 bytes less to accommodate for the CRC. +#define UDPARD_MTU_MAX_SINGLE_FRAME (UDPARD_MTU_MAX - 4U) + +/// The port number is defined in the Cyphal/UDP Specification. The same port number is used for all transfer kinds. +#define UDPARD_UDP_PORT 9382U /// Parameter ranges are inclusive; the lower bound is zero for all. See Cyphal/UDP Specification for background. -#define UDPARD_SUBJECT_ID_MAX 32767U /// 15 bits subject ID -#define UDPARD_SERVICE_ID_MAX 65535U /// The hard limit for ports -#define UDPARD_NODE_SUBNET_MAX 31U /// 5 bits for subnet -#define UDPARD_NODE_ID_MAX 65534U /// 16 bits - 1 is the hard limit. But this may change pending implementations +#define UDPARD_SUBJECT_ID_MAX 8191U +#define UDPARD_SERVICE_ID_MAX 511U +#define UDPARD_NODE_ID_MAX 0xFFFEU /// 2**16-1 is reserved for the anonymous/broadcast ID. #define UDPARD_PRIORITY_MAX 7U -#define UDPARD_TRANSFER_ID_BIT_LENGTH 63ULL -#define UDPARD_TRANSFER_ID_MAX ((1ULL << UDPARD_TRANSFER_ID_BIT_LENGTH) - 1ULL) -#define UDPARD_NODE_ID_UNSET 65535U /// For UDP 0xFFFF is the anonymous ID +/// This value represents an undefined node-ID: broadcast destination or anonymous source. +#define UDPARD_NODE_ID_UNSET 0xFFFFU /// This is the recommended transfer-ID timeout value given in the Cyphal Specification. The application may choose /// different values per subscription (i.e., per data specifier) depending on its timing requirements. -#define UDPARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC 2000000UL /// 2 seconds +#define UDPARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC 2000000UL + +/// The library supports at most this many redundant network interfaces per Cyphal node. +#define UDPARD_NETWORK_INTERFACE_COUNT_MAX 3U // Forward declarations. -typedef struct UdpardInstance UdpardInstance; -typedef struct UdpardTreeNode UdpardTreeNode; -typedef struct UdpardTxQueueItem UdpardTxQueueItem; -typedef uint64_t UdpardMicrosecond; -typedef uint32_t UdpardNodeAddress; /// Full Node IP Address for multi or uni cast -typedef uint32_t UdpardIPv4Addr; /// Full Node IP Address for multi or uni cast -typedef uint16_t UdpardPortID; -typedef uint16_t UdpardUdpPortID; -typedef uint16_t UdpardNodeID; -typedef uint64_t UdpardTransferID; /// TODO - This may need to be broken up for 32bit systems? -typedef uint8_t UdpardHeaderVersion; +typedef struct UdpardInstance UdpardInstance; +typedef struct UdpardTreeNode UdpardTreeNode; +typedef struct UdpardTxItem UdpardTxItem; +typedef struct UdpardMemoryResource UdpardMemoryResource; +typedef uint64_t UdpardMicrosecond; +typedef uint16_t UdpardPortID; +typedef uint16_t UdpardNodeID; +typedef uint64_t UdpardTransferID; /// Transfer priority level mnemonics per the recommendations given in the Cyphal Specification. +/// For outgoing transfers they are mapped to DSCP values as configured per redundant interface (per UdpardTx instance). typedef enum { UdpardPriorityExceptional = 0, @@ -101,15 +246,6 @@ typedef enum UdpardPriorityOptional = 7, } UdpardPriority; -/// Transfer kinds as defined by the Cyphal Specification. -typedef enum -{ - UdpardTransferKindMessage = 0, ///< Multicast, from publisher to all subscribers. - UdpardTransferKindResponse = 1, ///< Point-to-point, from server to client. - UdpardTransferKindRequest = 2, ///< Point-to-point, from client to server. -} UdpardTransferKind; -#define UDPARD_NUM_TRANSFER_KINDS 3 - /// The AVL tree node structure is exposed here to avoid pointer casting/arithmetics inside the library. /// The user code is not expected to interact with this type except if advanced introspection is required. struct UdpardTreeNode @@ -121,110 +257,136 @@ struct UdpardTreeNode typedef struct { - uint8_t version; - uint8_t priority; - uint16_t source_node_id; - uint16_t destination_node_id; - uint16_t data_specifier; - uint64_t transfer_id; - uint32_t frame_index_eot; - uint16_t _opaque; - uint16_t cyphal_header_checksum; -} UdpardFrameHeader; + size_t size; + void* data; +} UdpardMutablePayload; typedef struct { - UdpardFrameHeader udp_cyphal_header; - size_t payload_size; - const void* payload; -} UdpardFrame; + size_t size; + const void* data; +} UdpardConstPayload; +/// Cyphal/UDP uses only multicast traffic. +/// Unicast support is not required; one consequence is that ARP tables are not needed. typedef struct { - /// The route specifier is defined by the 16 LSB of the IP address - UdpardIPv4Addr source_route_specifier; - UdpardIPv4Addr destination_route_specifier; - /// For message transfers: the data specifier is the 16 LSB of the multicast group - /// For service transfers: the data specifier is the UDP destination port number - /// The service UDP port number for a request is determined by (SERVICE_BASE_PORT + service_id * 2) - /// The service UDP port number for a response is determined by (SERVICE_BASE_PORT + service_id * 2 + 1) - /// Note that message transfers will always use the same UDP port number (MESSAGE_BASE_PORT) - UdpardUdpPortID data_specifier; -} UdpardSessionSpecifier; - -/// A Cyphal transfer metadata (everything except the payload). -/// Per Specification, a transfer is represented on the wire as a non-empty set of transport frames (i.e., UDP frames). -/// The library is responsible for serializing transfers into transport frames when transmitting, and reassembling -/// transfers from an incoming stream of frames (possibly duplicated if redundant interfaces are used) during reception. -typedef struct -{ - /// Per the Specification, all frames belonging to a given transfer shall share the same priority level. - /// If this is not the case, then this field contains the priority level of the last frame to arrive. - UdpardPriority priority; + uint32_t ip_address; + uint16_t udp_port; +} UdpardUDPIPEndpoint; - UdpardTransferKind transfer_kind; +// ============================================================================================================= +// ============================================= MEMORY RESOURCE ============================================= +// ============================================================================================================= - /// Subject-ID for message publications; service-ID for service requests/responses. - UdpardPortID port_id; +/// A pointer to the memory allocation function. The semantics are similar to malloc(): +/// - The returned pointer shall point to an uninitialized block of memory that is at least "size" bytes large. +/// - If there is not enough memory, the returned pointer shall be NULL. +/// - The memory shall be aligned at least at max_align_t. +/// - The execution time should be constant (O(1)). +/// - The worst-case memory consumption (worst fragmentation) should be understood by the developer. +/// If the standard dynamic memory manager of the target platform does not satisfy the above requirements, +/// consider using O1Heap: https://github.com/pavel-kirienko/o1heap. +typedef void* (*UdpardMemoryAllocate)(UdpardMemoryResource* const self, const size_t size); - /// For outgoing message transfers the value shall be UDPARD_NODE_ID_UNSET (otherwise the state is invalid). - /// For outgoing service transfers this is the destination address (invalid if unset). - /// For incoming non-anonymous transfers this is the node-ID of the origin. - /// For incoming anonymous transfers the value is reported as UDPARD_NODE_ID_UNSET. - UdpardNodeID remote_node_id; +/// The counterpart of the above -- this function is invoked to return previously allocated memory to the allocator. +/// The size argument contains the amount of memory that was originally requested via the allocation function. +/// The semantics are similar to free(): +/// - The pointer was previously returned by the allocation function. +/// - The pointer may be NULL, in which case the function shall have no effect. +/// - The execution time should be constant (O(1)). +typedef void (*UdpardMemoryFree)(UdpardMemoryResource* const self, const size_t size, void* const pointer); - /// When responding to a service request, the response transfer SHALL have the same transfer-ID value as the - /// request because the client will match the response with the request based on that. - /// - /// When publishing a message transfer, the value SHALL be one greater than the previous transfer under the same - /// subject-ID; the initial value should be zero. - /// - /// When publishing a service request transfer, the value SHALL be one greater than the previous transfer under - /// the same service-ID addressed to the same server node-ID; the initial value should be zero. - /// - /// The transfer-ID shall not overflow - /// - /// A simple and robust way of managing transfer-ID counters is to keep a separate static variable per subject-ID - /// and per (service-ID, server-node-ID) pair. - UdpardTransferID transfer_id; -} UdpardTransferMetadata; - -/// Prioritized transmission queue that keeps UDP frames destined for transmission via one UDP interface. -/// Applications with redundant interfaces are expected to have one instance of this type per interface. -/// Applications that are not interested in transmission may have zero queues. -/// All operations (push, peek, pop) are O(log n); there is exactly one heap allocation per element. -/// API functions that work with this type are named "udpardTx*()", find them below. -typedef struct UdpardTxQueue +/// A memory resource encapsulates the dynamic memory allocation and deallocation facilities. +/// The time complexity models given in the API documentation are made on the assumption that the memory management +/// functions have constant complexity O(1). +struct UdpardMemoryResource { - /// The maximum number of frames this queue is allowed to contain. An attempt to push more will fail with an - /// out-of-memory error even if the memory is not exhausted. This value can be changed by the user at any moment. - /// The purpose of this limitation is to ensure that a blocked queue does not exhaust the heap memory. - size_t capacity; + /// The function pointers shall be valid at all times. + UdpardMemoryAllocate allocate; + UdpardMemoryFree free; + /// This is an opaque pointer that can be freely utilized by the user for arbitrary needs. + void* user_reference; +}; - /// The transport-layer maximum transmission unit (MTU). The value can be changed arbitrarily at any time between - /// pushes. It defines the maximum number of data bytes per UDP data frame in outgoing transfers via this queue. - /// - /// Only the standard values should be used as recommended by the specification; - /// otherwise, networking interoperability issues may arise. See recommended values UDPARD_MTU_*. - /// - /// Valid values are any valid UDP frame data length value not smaller than 8. - /// Invalid values are treated as the nearest valid value. The default is the maximum valid value. - size_t mtu_bytes; +// ============================================================================================================= +// ============================================= TX PIPELINE ============================================= +// ============================================================================================================= - /// The number of frames that are currently contained in the queue, initially zero. - /// Do not modify this field! - size_t size; +/// The transmission pipeline is a prioritized transmission queue that keeps UDP datagrams (aka transport frames) +/// destined for transmission via one network interface. +/// Applications with redundant network interfaces are expected to have one instance of this type per interface. +/// Applications that are not interested in transmission may have zero such instances. +/// +/// All operations are logarithmic in complexity on the number of enqueued items. +/// There is exactly one memory allocation per element; +/// the size of each allocation is sizeof(UdpardTxItem) plus the size of the datagram. +/// +/// Once initialized, instances cannot be copied. +/// +/// API functions that work with this type are named "udpardTx*()", find them below. +/// +/// FUTURE: Eventually we might consider adding another way of arranging the transmission pipeline where the UDP +/// datagrams ready for transmission are not enqueued into the local prioritized queue but instead are sent directly +/// to the network interface driver using a dedicated callback. The callback would accept not just a single +/// chunk of data but a list of three chunks to avoid copying the source transfer payload: the datagram header, +/// the payload, and (only for the last frame) the CRC. The driver would then use some form of vectorized IO to +/// transmit the data; the advantage of this approach is that up to two data copy operations are eliminated from the +/// stack and the memory allocator is not used at all. The disadvantage is that if the driver callback is blocking, +/// the application thread will be blocked as well; plus the driver will be responsible for the correct prioritization +/// of the outgoing datagrams according to the DSCP value. +typedef struct +{ + /// Pointer to the node-ID of the local node, which is used to populate the source node-ID field of outgoing + /// transfers. + /// This is made a pointer to allow the user to easily change the node-ID after a plug-and-play node-ID allocation + /// across multiple instances (remember there is a separate instance per redundant interface). + /// The node-ID value should be set to UDPARD_NODE_ID_UNSET if the local node is anonymous + /// (e.g., during PnP allocation or if no transmission is needed). + const UdpardNodeID* local_node_id; + + /// The maximum number of UDP datagrams this instance is allowed to enqueue. + /// An attempt to push more will fail with an out-of-memory error even if the memory is not exhausted. + /// The purpose of this limitation is to ensure that a blocked queue does not exhaust the memory. + size_t queue_capacity; + + /// The transport-layer maximum transmission unit (MTU). + /// It defines the maximum number of data bytes per UDP data frame in outgoing transfers via this queue. + /// See UDPARD_MTU_*. + /// The value can be changed arbitrarily at any time between enqueue operations. + size_t mtu_bytes; - /// The root of the priority queue is NULL if the queue is empty. Do not modify this field! - UdpardTreeNode* root; + /// The mapping from the Cyphal priority level in [0,7], where the highest priority is at index 0 + /// and the lowest priority is at the last element of the array, to the IP DSCP field value. + /// See UdpardPriority. + /// By default, the mapping is initialized per the recommendations given in the Cyphal/UDP specification. + /// The value can be changed arbitrarily at any time between enqueue operations. + uint8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U]; /// This field can be arbitrarily mutated by the user. It is never accessed by the library. /// Its purpose is to simplify integration with OOP interfaces. void* user_reference; -} UdpardTxQueue; -/// One frame stored in the transmission queue along with its metadata. -struct UdpardTxQueueItem + /// The memory resource used by this queue for allocating the enqueued items (UDP datagrams). + /// There is exactly one allocation per enqueued item, each allocation contains both the UdpardTxItem + /// and its payload. + /// In a simple application there would be just one memory resource shared by all parts of the library. + UdpardMemoryResource memory; + + /// The number of frames that are currently contained in the queue, initially zero. + /// READ-ONLY + size_t size; + + /// Internal use only. + /// READ-ONLY + UdpardTreeNode* root; +} UdpardTx; + +/// One transport frame (UDP datagram) stored in the UdpardTx transmission queue along with its metadata. +/// The datagram should be sent to the indicated UDP/IP endpoint with the specified DSCP value. +/// The datagram should be discarded (transmission aborted) if the deadline has expired. +/// All fields are READ-ONLY. +struct UdpardTxItem { /// Internal use only; do not access this field. UdpardTreeNode base; @@ -234,327 +396,293 @@ struct UdpardTxQueueItem /// It can be useful though for pulling pending frames from the TX queue if at least one frame of their transfer /// failed to transmit; the idea is that if at least one frame is missing, the transfer will not be received by /// remote nodes anyway, so all its remaining frames can be dropped from the queue at once using udpardTxPop(). - UdpardTxQueueItem* next_in_transfer; - - /// This is the same value that is passed to udpardTxPush(). - /// Frames whose transmission deadline is in the past shall be dropped. - UdpardMicrosecond tx_deadline_usec; + UdpardTxItem* next_in_transfer; - /// We need some way to distinguish data and destination routing for each TxQueueItem. The SessionSepcifier gives us - /// all of the information we need to do that. - UdpardSessionSpecifier specifier; - - /// The actual UDP frame data. - UdpardFrame frame; -}; - -/// Transfer subscription state. The application can register its interest in a particular kind of data exchanged -/// over the bus by creating such subscription objects. Frames that carry data for which there is no active -/// subscription will be silently dropped by the library. The entire RX pipeline is invariant to the number of -/// redundant UDP interfaces used. -/// -/// SUBSCRIPTION INSTANCES SHALL NOT BE MOVED WHILE IN USE. -/// -/// The memory footprint of a subscription is large. On a 32-bit platform it slightly exceeds half a KiB. -/// This is an intentional time-memory trade-off: use a large look-up table to ensure predictable temporal properties. -typedef struct UdpardRxSubscription -{ - UdpardTreeNode base; ///< Read-only DO NOT MODIFY THIS - - UdpardMicrosecond transfer_id_timeout_usec; - size_t extent; ///< Read-only DO NOT MODIFY THIS - UdpardPortID port_id; ///< Read-only DO NOT MODIFY THIS - - /// This field can be arbitrarily mutated by the user. It is never accessed by the library. - /// Its purpose is to simplify integration with OOP interfaces. - void* user_reference; - - /// TODO - We may need to limit UDPARD_NODE_ID_MAX so that we aren't allocating 65 KiB for empty sessions... - /// TODO - or we need to determine some other method of creating sessions while retaining O(1) - - /// The current architecture is an acceptable middle ground between worst-case execution time and memory - /// consumption. Instead of statically pre-allocating a dedicated RX session for each remote node-ID here in - /// this table, we only keep pointers, which are NULL by default, populating a new RX session dynamically - /// on an ad-hoc basis when we first receive a transfer from that node. This is O(1) because our memory - /// allocation routines are assumed to be O(1) and we make at most one allocation per remote node. - /// - /// A more predictable and simpler approach is to pre-allocate states here statically instead of keeping - /// just pointers, but it would push the size of this instance from about 0.5 KiB to ~3 KiB for a typical 32-bit - /// system. Since this is a general-purpose library, we have to pick a middle ground so we use the more complex - /// but more memory-efficient approach. - struct UdpardInternalRxSession* sessions[UDPARD_NODE_ID_MAX + 1U]; ///< Read-only DO NOT MODIFY THIS -} UdpardRxSubscription; - -/// Reassembled incoming transfer returned by udpardRxAccept(). -typedef struct UdpardRxTransfer -{ - UdpardTransferMetadata metadata; - - /// The timestamp of the first received UDP frame of this transfer. - /// The time system may be arbitrary as long as the clock is monotonic (steady). - UdpardMicrosecond timestamp_usec; - - /// If the payload is empty (payload_size = 0), the payload pointer may be NULL. - /// The application is required to deallocate the payload buffer after the transfer is processed. - size_t payload_size; - void* payload; -} UdpardRxTransfer; - -/// A pointer to the memory allocation function. The semantics are similar to malloc(): -/// - The returned pointer shall point to an uninitialized block of memory that is at least "amount" bytes large. -/// - If there is not enough memory, the returned pointer shall be NULL. -/// - The memory shall be aligned at least at max_align_t. -/// - The execution time should be constant (O(1)). -/// - The worst-case memory fragmentation should be bounded and easily predictable. -/// If the standard dynamic memory manager of the target platform does not satisfy the above requirements, -/// consider using O1Heap: https://github.com/pavel-kirienko/o1heap. -typedef void* (*UdpardMemoryAllocate)(UdpardInstance* ins, size_t amount); + /// This is the same value that is passed to udpardTxPublish/Request/Respond. + /// Frames whose transmission deadline is in the past should be dropped (transmission aborted). + UdpardMicrosecond deadline_usec; -/// The counterpart of the above -- this function is invoked to return previously allocated memory to the allocator. -/// The semantics are similar to free(): -/// - The pointer was previously returned by the allocation function. -/// - The pointer may be NULL, in which case the function shall have no effect. -/// - The execution time should be constant (O(1)). -typedef void (*UdpardMemoryFree)(UdpardInstance* ins, void* pointer); + /// The IP differentiated services code point (DSCP) is used to prioritize UDP frames on the network. + /// LibUDPard selects the DSCP value based on the transfer priority level and the configured DSCP mapping. + uint8_t dscp; -/// This is the core structure that keeps all of the states and allocated resources of the library instance. -struct UdpardInstance -{ - /// User pointer that can link this instance with other objects. - /// This field can be changed arbitrarily, the library does not access it after initialization. - /// The default value is NULL. - void* user_reference; + /// This UDP/IP datagram compiled by libudpard should be sent to this endpoint. + /// The endpoint is always at a multicast address. + UdpardUDPIPEndpoint destination; - /// The node-ID of the local node. - /// Per the Cyphal Specification, the node-ID should not be assigned more than once. - /// Invalid values are treated as UDPARD_NODE_ID_UNSET. The default value is UDPARD_NODE_ID_UNSET. - UdpardNodeID node_id; - UdpardIPv4Addr local_ip_addr; + /// The completed UDP/IP datagram payload. This includes the Cyphal header as well as all required CRCs. + /// It should be sent through the socket (or equivalent abstraction) verbatim. + UdpardMutablePayload datagram_payload; - /// Dynamic memory management callbacks. See their type documentation for details. - /// They SHALL be valid function pointers at all times. - /// The time complexity models given in the API documentation are made on the assumption that the memory management - /// functions have constant complexity O(1). - /// - /// The following API functions may allocate memory: udpardRxAccept(), udpardTxPush(). - /// The following API functions may deallocate memory: udpardRxAccept(), udpardRxSubscribe(), udpardRxUnsubscribe(). - /// The exact memory requirement and usage model is specified for each function in its documentation. - UdpardMemoryAllocate memory_allocate; - UdpardMemoryFree memory_free; - - /// Read-only DO NOT MODIFY THIS - UdpardTreeNode* rx_subscriptions[UDPARD_NUM_TRANSFER_KINDS]; + /// This opaque pointer is assigned the value that is passed to udpardTxPublish/Request/Respond. + /// The library itself does not make use of it but the application can use it to provide continuity between + /// its high-level transfer objects and datagrams that originate from it. + /// If not needed, the application can set it to NULL. + void* user_transfer_reference; }; -/// TODO - Make a UDP acceptance filter based on the UdpardFrameHeader - -/// Construct a new library instance. -/// The default values will be assigned as specified in the structure field documentation. -/// If any of the pointers are NULL, the behavior is undefined. -/// -/// The instance does not hold any resources itself except for the allocated memory. -/// To safely discard it, simply remove all existing subscriptions, and don't forget about the TX queues. -/// -/// The time complexity is constant. This function does not invoke the dynamic memory manager. -UdpardInstance udpardInit(const UdpardMemoryAllocate memory_allocate, const UdpardMemoryFree memory_free); - -/// Construct a new transmission queue instance with the specified values for capacity and mtu_bytes. -/// No memory allocation is going to take place until the queue is actually pushed to. -/// Applications are expected to have one instance of this type per redundant interface. +/// Construct a new transmission pipeline with the specified queue capacity and memory resource. +/// Refer to the documentation for UdpardTx for more information. +/// The other parameters will be initialized to the recommended defaults automatically, +/// which can be changed later by modifying the struct fields directly. +/// No memory allocation is going to take place until the pipeline is actually written to. /// /// The instance does not hold any resources itself except for the allocated memory. -/// To safely discard it, simply pop all items from the queue. +/// To safely discard it, simply pop all enqueued frames from it. /// /// The time complexity is constant. This function does not invoke the dynamic memory manager. -UdpardTxQueue udpardTxInit(const size_t capacity, const size_t mtu_bytes); +int8_t udpardTxInit(UdpardTx* const self, + const UdpardNodeID* const local_node_id, + const size_t queue_capacity, + const UdpardMemoryResource memory); -/// This function serializes a transfer into a sequence of transport frames and inserts them into the prioritized +/// This function serializes a message transfer into a sequence of UDP datagrams and inserts them into the prioritized /// transmission queue at the appropriate position. Afterwards, the application is supposed to take the enqueued frames -/// from the transmission queue using the function udpardTxPeek() and transmit them. Each transmitted (or otherwise -/// discarded, e.g., due to timeout) frame should be removed from the queue using udpardTxPop(). The queue is -/// prioritized following the normal UDP frame arbitration rules to avoid the inner priority inversion. The transfer -/// payload will be copied into the transmission queue so that the lifetime of the frames is not related to the +/// from the transmission queue using the function udpardTxPeek and transmit them one by one. Each transmitted +/// (or discarded, e.g., due to timeout) frame should be removed from the queue using udpardTxPop. The enqueued items +/// are prioritized according to their Cyphal transfer priority to avoid the inner priority inversion. The transfer +/// payload will be copied into the transmission queue so that the lifetime of the datagrams is not related to the /// lifetime of the input payload buffer. /// -/// The MTU of the generated frames is dependent on the value of the MTU setting at the time when this function +/// The MTU of the generated datagrams is dependent on the value of the MTU setting at the time when this function /// is invoked. The MTU setting can be changed arbitrarily between invocations. /// -/// The tx_deadline_usec will be used to populate the timestamp values of the resulting transport -/// frames (so all frames will have the same timestamp value). This feature is intended to facilitate transmission -/// deadline tracking, i.e., aborting frames that could not be transmitted before the specified deadline. -/// Therefore, normally, the timestamp value should be in the future. -/// The library itself, however, does not use or check this value in any way, so it can be zero if not needed. +/// The pointer to the transfer_id will be used to populate the transfer_id field of the generated datagrams and +/// then to increment the pointed-to value to prepare it for the next publication. +/// There shall be a separate transfer-ID counter per subject (topic). +/// The lifetime of the pointed-to transfer-ID counter must exceed the lifetime of the intent to publish on this +/// subject (topic); one common approach is to use a static variable. /// -/// The function returns the number of frames enqueued into the prioritized TX queue (which is always a positive -/// number) in case of success (so that the application can track the number of items in the TX queue if necessary). +/// The user_transfer_reference is an opaque pointer that will be assigned to the user_transfer_reference field of +/// each enqueued item. The library itself does not use or check this value in any way, so it can be NULL if not needed. +/// +/// The deadline_usec value will be used to populate the eponymous field of the generated datagrams +/// (all will share the same deadline value). +/// This feature is intended to allow aborting frames that could not be transmitted before the specified deadline; +/// therefore, normally, the timestamp value should be in the future. +/// The library itself, however, does not use or check this value in any way, so it can be zero if not needed +/// (this is not recommended for real-time systems). +/// +/// The function returns the number of UDP datagrams enqueued, which is always a positive number, in case of success. /// In case of failure, the function returns a negated error code: either invalid argument or out-of-memory. /// /// An invalid argument error may be returned in the following cases: -/// - Any of the input arguments are NULL. -/// - The remote node-ID is not UDPARD_NODE_ID_UNSET and the transfer is a message transfer. -/// - The remote node-ID is above UDPARD_NODE_ID_MAX and the transfer is a service transfer. -/// - The priority, subject-ID, or service-ID exceed their respective maximums. -/// - The transfer kind is invalid. +/// - Any of the input arguments except user_transfer_reference are NULL. +/// - The priority or the port-ID exceed their respective maximums. /// - The payload pointer is NULL while the payload size is nonzero. -/// - The local node is anonymous and a message transfer is requested that requires a multi-frame transfer. -/// - The local node is anonymous and a service transfer is requested. -/// The following cases are handled without raising an invalid argument error: -/// - If the transfer-ID is above the maximum, the excessive bits are silently masked away -/// (i.e., the modulo is computed automatically, so the caller doesn't have to bother). +/// - The local node is anonymous (the local node-ID is unset) and the transfer payload cannot fit into +/// a single datagram (a multi-frame transfer is required). /// /// An out-of-memory error is returned if a TX frame could not be allocated due to the memory being exhausted, /// or if the capacity of the queue would be exhausted by this operation. In such cases, all frames allocated for /// this transfer (if any) will be deallocated automatically. In other words, either all frames of the transfer are /// enqueued successfully, or none are. /// +/// The memory allocation requirement is one allocation per datagram: +/// a single-frame transfer takes one allocation; a multi-frame transfer of N frames takes N allocations. +/// The size of each allocation is (sizeof(UdpardTxItem) + MTU) except for the last datagram where the payload may be +/// smaller than the MTU. +/// /// The time complexity is O(p + log e), where p is the amount of payload in the transfer, and e is the number of /// frames already enqueued in the transmission queue. -/// -/// The memory allocation requirement is one allocation per transport frame. A single-frame transfer takes one -/// allocation; a multi-frame transfer of N frames takes N allocations. The size of each allocation is -/// (sizeof(udpardTxQueueItem) + MTU). -int32_t udpardTxPush(UdpardTxQueue* const que, - UdpardInstance* const ins, - const UdpardMicrosecond tx_deadline_usec, - const UdpardTransferMetadata* const metadata, - const size_t payload_size, - const void* const payload); - -/// This function accesses the top element of the prioritized transmission queue. The queue itself is not modified -/// (i.e., the accessed element is not removed). The application should invoke this function to collect the transport -/// frames of serialized transfers pushed into the prioritized transmission queue by udpardTxPush(). -/// -/// The timestamp values of returned frames are initialized with tx_deadline_usec from udpardTxPush(). -/// Timestamps are used to specify the transmission deadline. It is up to the application and/or the media layer -/// to implement the discardment of timed-out transport frames. The library does not check it, so a frame that is +int32_t udpardTxPublish(UdpardTx* const self, + void* const user_transfer_reference, + const UdpardMicrosecond deadline_usec, + const UdpardPriority priority, + const UdpardPortID subject_id, + UdpardTransferID* const transfer_id, + const UdpardConstPayload payload); + +/// This is similar to udpardTxPublish except that it is intended for service request transfers. +/// It takes the node-ID of the server that is intended to receive the request. +/// +/// The pointer to the transfer_id will be used to populate the transfer_id field of the generated datagrams and +/// then to increment the pointed-to value to prepare it for the next request. +/// There shall be a separate transfer-ID counter per pair of (service-ID, server node-ID). +/// The lifetime of the pointed-to transfer-ID counter must exceed the lifetime of the intent to invoke this service +/// on this server node; one common approach is to use a static array indexed by the server node-ID per service-ID +/// (memory-constrained applications may choose a more compact container). +/// +/// Aside from the errors defined for udpardTxPublish, this function may also return an invalid argument error +/// in the following cases: +/// - The server node-ID value exceeds UDPARD_NODE_ID_MAX. +/// - The local node is anonymous (the local node-ID is unset). +/// +/// Other considerations are the same as for udpardTxPublish. +int32_t udpardTxRequest(UdpardTx* const self, + void* const user_transfer_reference, + const UdpardMicrosecond deadline_usec, + const UdpardPriority priority, + const UdpardPortID service_id, + const UdpardNodeID server_node_id, + UdpardTransferID* const transfer_id, + const UdpardConstPayload payload); + +/// This is similar to udpardTxRequest except that it takes the node-ID of the client instead of server, +/// and the transfer-ID is passed by value rather than by pointer. +/// The transfer-ID is passed by value because when responding to an RPC-service request, the server must +/// reuse the transfer-ID value of the request (this is to allow the client to match responses with their requests). +int32_t udpardTxRespond(UdpardTx* const self, + void* const user_transfer_reference, + const UdpardMicrosecond deadline_usec, + const UdpardPriority priority, + const UdpardPortID service_id, + const UdpardNodeID client_node_id, + const UdpardTransferID transfer_id, + const UdpardConstPayload payload); + +/// This function accesses the enqueued UDP datagram scheduled for transmission next. The queue itself is not modified +/// (i.e., the accessed element is not removed). The application should invoke this function to collect the datagrams +/// enqueued by udpardTxPublish/Request/Respond whenever the socket (or equivalent abstraction) becomes writable. +/// +/// The timestamp values of the enqueued items are initialized with deadline_usec from udpardTxPublish/Request/Respond. +/// The timestamps are used to specify the transmission deadline. It is up to the application and/or the socket layer +/// to implement the discardment of timed-out datagrams. The library does not check it, so a frame that is /// already timed out may be returned here. /// /// If the queue is empty or if the argument is NULL, the returned value is NULL. /// -/// If the queue is non-empty, the returned value is a pointer to its top element (i.e., the next frame to transmit). +/// If the queue is non-empty, the returned value is a pointer to its top element (i.e., the next item to transmit). /// The returned pointer points to an object allocated in the dynamic storage; it should be eventually freed by the -/// application by calling udpardInstance::memory_free(). The memory shall not be freed before the entry is removed -/// from the queue by calling udpardTxPop(); this is because until udpardTxPop() is executed, the library retains -/// ownership of the object. The pointer retains validity until explicitly freed by the application; in other words, -/// calling udpardTxPop() does not invalidate the object. +/// application by calling UdpardTx::memory.free. The memory shall not be freed before the item is removed +/// from the queue by calling udpardTxPop; this is because until udpardTxPop is executed, the library retains +/// ownership of the item. The pointer retains validity until explicitly freed by the application; in other words, +/// calling udpardTxPop does not invalidate the object. /// /// The payload buffer is located shortly after the object itself, in the same memory fragment. The application shall /// not attempt to free it. /// +/// Calling functions that modify the queue may cause the next invocation to return a different pointer. +/// /// The time complexity is logarithmic of the queue size. This function does not invoke the dynamic memory manager. -const UdpardTxQueueItem* udpardTxPeek(const UdpardTxQueue* const que); +const UdpardTxItem* udpardTxPeek(const UdpardTx* const self); -/// This function transfers the ownership of the specified element of the prioritized transmission queue from the queue -/// to the application. The element does not necessarily need to be the top one -- it is safe to dequeue any element. -/// The element is dequeued but not invalidated; it is the responsibility of the application to deallocate the -/// memory used by the object later. The memory SHALL NOT be deallocated UNTIL this function is invoked. +/// This function transfers the ownership of the specified item of the prioritized transmission queue from the queue +/// to the application. The item does not necessarily need to be the top one -- it is safe to dequeue any item. +/// The item is dequeued but not invalidated; it is the responsibility of the application to deallocate its memory +/// later. The memory SHALL NOT be deallocated UNTIL this function is invoked. /// The function returns the same pointer that it is given except that it becomes mutable. /// /// If any of the arguments are NULL, the function has no effect and returns NULL. /// /// The time complexity is logarithmic of the queue size. This function does not invoke the dynamic memory manager. -UdpardTxQueueItem* udpardTxPop(UdpardTxQueue* const que, const UdpardTxQueueItem* const item); +UdpardTxItem* udpardTxPop(UdpardTx* const self, const UdpardTxItem* const item); + +// ============================================================================================================= +// ============================================= RX PIPELINE ============================================= +// ============================================================================================================= -/// This function implements the transfer reassembly logic. It accepts a transport frame from any of the redundant -/// interfaces, locates the appropriate subscription state, and, if found, updates it. If the frame completed a -/// transfer, the return value is 1 (one) and the out_transfer pointer is populated with the parameters of the -/// newly reassembled transfer. The transfer reassembly logic is defined in the Cyphal specification. +/// This type represents an open input port, such as a subscription to a subject (topic), a service server port +/// that accepts RPC-service requests, or a service client port that accepts RPC-service responses. /// -/// The MTU of the accepted frame can be arbitrary; that is, any MTU is accepted. The DLC validity is irrelevant. +/// The library performs transfer reassembly, deduplication, and integrity checks, along with the management of +/// redundant network interfaces. +typedef struct +{ + /// For subject ports this is the subject-ID. For RPC-service ports this is the service-ID. + /// READ-ONLY + UdpardPortID port_id; + + /// The maximum payload size that can be accepted at this port. + /// The rest will be truncated away following the implicit truncation rule defined in the Cyphal specification. + /// READ-ONLY + size_t extent; + + /// Refer to the Cyphal specification for the description of the transfer-ID timeout. + /// By default, this is set to UDPARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC and it can be changed by the user. + /// This field can be adjusted at runtime arbitrarily; e.g., this is useful to implement adaptive timeouts. + UdpardMicrosecond transfer_id_timeout_usec; + + /// A new session instance is created per remote node-ID that emits transfers matching this port. + /// For example, if the local node is subscribed to a certain subject and there are X nodes publishing + /// transfers on that subject, then there will be X sessions created for that subject. + /// Same applies to RPC-services as well. + /// + /// Once a session is created, it is never freed again until the port that owns it (this structure) is destroyed. + /// This is in line with the assumption that the network configuration is usually mostly static, and that + /// once a node has started emitting data on a certain port, it is likely to continue doing so. + /// Applications where this is not the case may consider cycling their ports periodically + /// by destroying and re-creating them immediately. + /// + /// Each session instance takes sizeof(UdpardInternalRxSession) bytes of dynamic memory, + /// which is at most 128 bytes on wide-word platforms (on small word size platforms it is usually much smaller). + /// On top of that, each session instance may have one transfer payload buffer allocated per redundant interface; + /// the size of each buffer equals the extent (see above). + /// The payload buffer is only allocated while reassembly is in progress; when the session is idle, + /// no additional memory is held by the session instance. + /// + /// To summarize, a node with R redundant network interfaces subscribed to a subject with extent E bytes + /// requires at most (sizeof(UdpardInternalRxSession) + R*E) bytes of dynamic memory per session instance, + /// and there is a session instance per remote node that publishes on this subject. Same holds for services. + /// + /// The worst situation from the memory management standpoint is when there is a large number of nodes emitting + /// data such that each node begins a multi-frame transfer while never completing it. + /// + /// If the dynamic memory pool is sized correctly, the application is guaranteed to never encounter an + /// out-of-memory (OOM) error at runtime. The actual size of the dynamic memory pool is typically larger; + /// for a detailed review of this matter please refer to the documentation of O1Heap. + /// + /// READ-ONLY + UdpardTreeNode* sessions; +} UdpardRxPort; + +/// Represents a received Cyphal transfer. +/// The payload buffer is owned by this instance, so the application is required to free it after use +/// by calling UdpardMemoryResource::free(payload.data). +typedef struct +{ + UdpardMicrosecond timestamp_usec; + UdpardPriority priority; + UdpardNodeID source_node_id; + UdpardTransferID transfer_id; + UdpardMutablePayload payload; +} UdpardRxTransfer; + +// --------------------------------------------- SUBJECTS --------------------------------------------- + +/// This is a specialization of a port for subject (topic) subscriptions. /// -/// Any value of redundant_transport_index is accepted; that is, up to 256 redundant transports are supported. -/// The index of the transport from which the transfer is accepted is always the same as redundant_transport_index -/// of the current invocation, so the application can always determine which transport has delivered the transfer. +/// In Cyphal/UDP, each subject (topic) has a specific IP multicast group address associated with it. +/// This address is contained in the field named "udp_ip_endpoint". +/// The application is expected to open a separate socket bound to that endpoint per redundant interface, +/// and then feed the UDP datagrams received from these sockets into udpardRxSubscriptionReceive, +/// collecting UdpardRxTransfer instances at the output. /// -/// Upon return, the out_subscription pointer will point to the instance of udpardRxSubscription that accepted this -/// frame; if no matching subscription exists (i.e., frame discarded), the pointer will be NULL. -/// If this information is not relevant, set out_subscription to NULL. -/// The purpose of this argument is to allow integration with OOP adapters built on top of libudpard; see also the -/// user_reference provided in udpardRxSubscription. +/// Observe that the subscription pipeline is entirely independent of the node-ID of the local node. +/// This is by design, allowing nodes to listen to subjects without having to be present online. +typedef struct +{ + /// See UdpardRxPort. + /// Use this to change the transfer-ID timeout value for this subscription. + UdpardRxPort port; + + /// The IP multicast group address and the UDP port number where UDP/IP datagrams matching this Cyphal + /// subject will be sent by the publishers (remote nodes). + /// READ-ONLY + UdpardUDPIPEndpoint udp_ip_endpoint; + + /// The application may choose to use dedicated memory resources for sessions (fixed-size allocations) + /// and for the payload buffers (extent-sized allocations). + /// Simpler applications may choose to use the same memory resource for both. + /// Special snowflakes may put a limit on the maximum number of sessions in use through a constrained + /// memory resource. + UdpardMemoryResource memory_for_sessions; + UdpardMemoryResource memory_for_payloads; +} UdpardRxSubscription; + +/// To subscribe to a subject, the application should do this: /// -/// The function invokes the dynamic memory manager in the following cases only: +/// 1. Create a new UdpardRxSubscription instance. /// -/// 1. New memory for a session state object is allocated when a new session is initiated. -/// This event occurs when a transport frame that matches a known subscription is received from a node that -/// did not emit matching frames since the subscription was created. -/// Once a new session is created, it is not destroyed until the subscription is terminated by invoking -/// udpardRxUnsubscribe(). The number of sessions is bounded and the bound is low (at most the number of nodes -/// in the network minus one), also the size of a session instance is very small, so the removal is unnecessary. -/// Real-time networks typically do not change their configuration at runtime, so it is possible to reduce -/// the time complexity by never deallocating sessions. -/// The size of a session instance is at most 48 bytes on any conventional platform (typically much smaller). -/// -/// 2. New memory for the transfer payload buffer is allocated when a new transfer is initiated, unless the buffer -/// was already allocated at the time. -/// This event occurs when a transport frame that matches a known subscription is received and it begins a -/// new transfer (that is, the start-of-frame flag is set and it is not a duplicate). -/// The amount of the allocated memory equals the extent as configured via udpardRxSubscribe(); please read -/// its documentation for further information about the extent and related edge cases. -/// The worst case occurs when every node on the bus initiates a multi-frame transfer for which there is a -/// matching subscription: in this case, the library will allocate number_of_nodes allocations, where each -/// allocation is the same size as the configured extent. -/// -/// 3. Memory allocated for the transfer payload buffer may be deallocated at the discretion of the library. -/// This operation does not increase the worst case execution time and does not improve the worst case memory -/// consumption, so a deterministic application need not consider this behavior in the resource analysis. -/// This behavior is implemented for the benefit of applications where rigorous characterization is unnecessary. +/// 2. Initialize it by calling udpardRxSubscriptionInit. The subject-ID and port-ID are synonymous here. /// -/// The worst case dynamic memory consumption per subscription is: -/// -/// (sizeof(session instance) + extent) * number_of_nodes -/// -/// Where sizeof(session instance) and extent are defined above, and number_of_nodes is the number of remote -/// nodes emitting transfers that match the subscription (which cannot exceed (UDPARD_NODE_ID_MAX-1) by design). -/// If the dynamic memory pool is sized correctly, the application is guaranteed to never encounter an -/// out-of-memory (OOM) error at runtime. The actual size of the dynamic memory pool is typically larger; -/// for a detailed treatment of the problem and the related theory please refer to the documentation of O1Heap -- -/// a deterministic memory allocator for hard real-time embedded systems. -/// -/// The time complexity is O(p + log n) where n is the number of subject-IDs or service-IDs subscribed to by the -/// application, depending on the transfer kind of the supplied frame, and p is the amount of payload in the received -/// frame (because it will be copied into an internal contiguous buffer). Observe that the time complexity is -/// invariant to the network configuration (such as the number of online nodes) -- this is a very important -/// design guarantee for real-time applications because the execution time is dependent only on the number of -/// active subscriptions for a given transfer kind, and the MTU, both of which are easy to predict and account for. -/// Excepting the subscription search and the payload data copying, the entire RX pipeline contains neither loops -/// nor recursion. -/// Misaddressed and malformed frames are discarded in constant time. -/// -/// The function returns 1 (one) if the new frame completed a transfer. In this case, the details of the transfer -/// are stored into out_transfer, and the transfer payload buffer ownership is passed to that object. The lifetime -/// of the resulting transfer object is not related to the lifetime of the input transport frame (that is, even if -/// it is a single-frame transfer, its payload is copied out into a new dynamically allocated buffer storage). -/// If the extent is zero, the payload pointer may be NULL, since there is no data to store and so a -/// buffer is not needed. The application is responsible for deallocating the payload buffer when the processing -/// is done by invoking memory_free on the transfer payload pointer. -/// -/// The function returns a negated out-of-memory error if it was unable to allocate dynamic memory. -/// -/// The function does nothing and returns a negated invalid argument error immediately if any condition is true: -/// - Any of the input arguments that are pointers are NULL. -/// - The payload pointer of the input frame is NULL while its size is non-zero. -/// - The UDP ID of the input frame is not less than 2**29=0x20000000. -/// -/// The function returns zero if any of the following conditions are true (the general policy is that protocol -/// errors are not escalated because they do not construe a node-local error): -/// - The received frame is not a valid Cyphal/UDP transport frame. -/// - The received frame is a valid Cyphal/UDP transport frame, but there is no matching subscription, -/// the frame did not complete a transfer, the frame forms an invalid frame sequence, the frame is a duplicate, -/// the frame is unicast to a different node (address mismatch). -int8_t udpardRxAccept(UdpardInstance* const ins, - const UdpardMicrosecond timestamp_usec, - UdpardFrame* const frame, - const uint8_t redundant_transport_index, - UdpardRxTransfer* const out_transfer, - UdpardRxSubscription** const out_subscription); - -/// This function creates a new subscription, allowing the application to register its interest in a particular -/// category of transfers. The library will reject all transport frames for which there is no active subscription. -/// The reference out_subscription shall retain validity until the subscription is terminated (the referred object -/// cannot be moved or destroyed). -/// -/// If such subscription already exists, it will be removed first as if udpardRxUnsubscribe() was -/// invoked by the application, and then re-created anew with the new parameters. +/// 3. Per redundant network interface: +/// - Create a new socket bound to the IP multicast group address and UDP port number specified in the +/// udp_ip_endpoint field of the initialized subscription instance. The library will determine the +/// endpoint to use based on the subject-ID. +/// +/// 4. Read data from the sockets continuously and forward each received UDP datagram to +/// udpardRxSubscriptionReceive, along with the index of the redundant interface the datagram was received on. /// /// The extent defines the size of the transfer payload memory buffer; or, in other words, the maximum possible size /// of received objects, considering also possible future versions with new fields. It is safe to pick larger values. @@ -563,42 +691,217 @@ int8_t udpardRxAccept(UdpardInstance* const ins, /// will be truncated away, as mandated by the Specification. The transfer CRC is always validated regardless of /// whether its payload is truncated. /// -/// The default transfer-ID timeout value is defined as UDPARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC; use it if not sure. -/// The redundant transport fail-over timeout (if redundant transports are used) is the same as the transfer-ID timeout. -/// It may be reduced in a future release of the library, but it will not affect the backward compatibility. +/// By default, the transfer-ID timeout value is set to UDPARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC. +/// It can be changed by the user at any time by modifying the corresponding field in the subscription instance. +/// +/// The return value is 0 on success. +/// The return value is a negated invalid argument error if any of the input arguments are invalid. +/// +/// The time complexity is constant. This function does not invoke the dynamic memory manager. +int8_t udpardRxSubscriptionInit(UdpardRxSubscription* const self, + const UdpardPortID subject_id, + const size_t extent, + const UdpardMemoryResource memory_for_sessions, + const UdpardMemoryResource memory_for_payloads); + +/// Frees all memory held by the subscription instance. +/// After invoking this function, the instance is no longer usable. +/// Do not forget to close the sockets that were opened for this subscription. +void udpardRxSubscriptionDestroy(UdpardRxSubscription* const self); + +/// Datagrams received from the sockets of this subscription are fed into this function. +/// +/// The timestamp value indicates the arrival time of the datagram; the arrival time of the first datagram of +/// a transfer becomes the transfer timestamp upon successful reassembly. +/// This value is also used for the transfer-ID timeout management. +/// Usually, naive software timestamping is adequate for these purposes, but some applications may require +/// a greater accuracy (e.g., for time synchronization). +/// +/// The redundant interface index shall not exceed UDPARD_NETWORK_INTERFACE_COUNT_MAX. +/// +/// The accepted datagram may either be invalid, carry a non-final part of a multi-frame transfer, +/// carry a final part of a valid multi-frame transfer, or carry a valid single-frame transfer. +/// The last two cases are said to complete a transfer. +/// +/// If the datagram completes a transfer, the received_transfer argument is filled with the transfer details +/// and the return value is one. +/// The caller is assigned ownership of the transfer payload buffer memory; it has to be freed after use by calling +/// UdpardRxSubscription::memory_for_payloads.free(received_transfer->payload.data). +/// The lifetime of the resulting transfer object is not related to the lifetime of the input datagram (that is, +/// even if it is a single-frame transfer, its payload is copied out into a new dynamically allocated buffer). +/// If the extent is zero, the payload pointer may be NULL, since there is no data to store and so no buffer is needed. +/// +/// If the datagram does not complete a transfer or is malformed, the function returns zero and the received_transfer +/// is not modified. Observe that malformed frames are not treated as errors, as the local application is not +/// responsible for the behavior of external agents producing the datagrams. +/// +/// The function invokes the dynamic memory manager in the following cases only: +/// +/// 1. A new session state instance is allocated when a new session is initiated. +/// Please refer to UdpardRxPort for further information about the session state management. +/// +/// 2. A new transfer payload buffer is allocated when a new transfer is initiated, unless the buffer +/// was already allocated at the time. This event occurs when a valid transport frame is received and it +/// begins a new transfer on its interface (that is, the frame index is zero and it is not a duplicate). +/// The amount of the allocated memory equals the extent configured during initialization of the subscription. +/// +/// 3. Transfer payload buffers may occasionally be deallocated at the discretion of the library. +/// This operation does not increase the worst case execution time and does not improve the worst case memory +/// consumption, so a deterministic application need not consider this behavior in its resource analysis. +/// This behavior is implemented for the benefit of applications where rigorous characterization is unnecessary. +/// +/// The time complexity is O(p + log n) where n is the number of remote notes publishing on this subject (topic), +/// and p is the amount of payload in the received frame (because it will be copied into an internal contiguous buffer). +/// Malformed frames are discarded in constant time. +/// +/// A negated out-of-memory error is returned if the function fails to allocate memory. +/// A negated invalid argument error is returned if any of the input arguments are invalid. +/// No other errors are possible. +int8_t udpardRxSubscriptionReceive(UdpardRxSubscription* const self, + const UdpardMicrosecond timestamp_usec, + const UdpardConstPayload datagram_payload, + const uint8_t redundant_iface_index, + UdpardRxTransfer* const received_transfer); + +// --------------------------------------------- RPC-SERVICES --------------------------------------------- + +/// An RPC-service RX port models the interest of the application in receiving RPC-service transfers of +/// a particular kind (request or response) and a particular service-ID. +typedef struct +{ + /// READ-ONLY + UdpardTreeNode base; + + /// See UdpardRxPort. + /// Use this to change the transfer-ID timeout value for this RPC-service port. + UdpardRxPort port; + + /// This field can be arbitrarily mutated by the user. It is never accessed by the library. + /// Its purpose is to simplify integration with OOP interfaces. + void* user_reference; +} UdpardRxService; + +/// A service dispatcher is a collection of RPC-service RX ports. +/// +/// In Cyphal/UDP, each node has a specific IP multicast group address where RPC-service transfers destined to that +/// node are sent to. This is similar to subject (topic) multicast group addressed except that the node-ID takes +/// the place of the subject-ID. The IP multicast group address is derived from the local node-ID. +/// This address is available in the field named "udp_ip_endpoint". +/// The application is expected to open a separate socket bound to that endpoint per redundant interface, +/// and then feed the UDP datagrams received from these sockets into udpardRxServiceDispatcherReceive, +/// collecting UdpardRxServiceTransfer instances at the output. +/// +/// Anonymous nodes (nodes without a node-ID of their own) cannot use RPC-services. +typedef struct +{ + /// The IP address and UDP port number where UDP/IP datagrams carrying RPC-service transfers destined to this node + /// will be sent. + /// READ-ONLY + UdpardUDPIPEndpoint udp_ip_endpoint; + + /// The application may choose to use dedicated memory resources for sessions (fixed-size allocations) + /// and for the payload buffers (extent-sized allocations). + UdpardMemoryResource memory_for_sessions; + UdpardMemoryResource memory_for_payloads; + + /// READ-ONLY + UdpardRxService* request_ports; + UdpardRxService* response_ports; +} UdpardRxServiceDispatcher; + +/// Represents a received Cyphal RPC-service transfer -- either request or response. +typedef struct +{ + UdpardRxTransfer base; + UdpardPortID service_id; + bool is_request; +} UdpardRxServiceTransfer; + +/// To begin receiving RPC-service requests and/or responses, the application should do this: +/// +/// 1. Create a new UdpardRxServiceDispatcher instance. +/// +/// 2. Initialize it by calling udpardRxServiceDispatcherInit. Observe that a valid node-ID is required here. +/// If the application has to perform a plug-and-play node-ID allocation, it has to complete that beforehand. +/// The dispatcher is not needed to perform PnP node-ID allocation. +/// +/// 3. Per redundant network interface: +/// - Create a new socket bound to the IP multicast group address and UDP port number specified in the +/// udp_ip_endpoint field of the initialized service dispatcher instance. The library will determine the +/// endpoint to use based on the node-ID. +/// +/// 4. Announce its interest in specific RPC-services (requests and/or responses) by calling +/// udpardRxServiceDispatcherListen per each. This can be done at any later point as well. /// -/// The return value is 1 if a new subscription has been created as requested. -/// The return value is 0 if such subscription existed at the time the function was invoked. In this case, -/// the existing subscription is terminated and then a new one is created in its place. Pending transfers may be lost. +/// 5. Read data from the sockets continuously and forward each received UDP datagram to +/// udpardRxServiceDispatcherReceive, along with the index of the redundant interface +/// the datagram was received on. Only those services that were announced in step 4 will be processed. +/// +/// The return value is 0 on success. +/// The return value is a negated invalid argument error if any of the input arguments are invalid. +/// +/// The time complexity is constant. This function does not invoke the dynamic memory manager. +int8_t udpardRxServiceDispatcherInit(UdpardRxServiceDispatcher* const self, + const UdpardNodeID local_node_id, + const UdpardMemoryResource memory_for_sessions, + const UdpardMemoryResource memory_for_payloads); + +/// Frees all memory held by the RPC-service dispatcher instance. +/// After invoking this function, the instance is no longer usable. +/// Do not forget to close the sockets that were opened for this instance. +void udpardRxServiceDispatcherDestroy(UdpardRxServiceDispatcher* const self); + +/// This function lets the application register its interest in a particular service-ID and kind (request/response) +/// by creating an RPC-service RX port. The service pointer shall retain validity until its unregistration or until +/// the dispatcher is destroyed. The service instance shall not be moved or destroyed. +/// +/// If such registration already exists, it will be unregistered first as if udpardRxServiceDispatcherCancel was +/// invoked by the application, and then re-created anew with the new parameters. +/// +/// For the meaning of extent, please refer to the documentation of the subscription pipeline. +/// +/// By default, the transfer-ID timeout value is set to UDPARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC. +/// It can be changed by the user at any time by modifying the corresponding field in the registration instance. +/// +/// The return value is 1 if a new registration has been created as requested. +/// The return value is 0 if such registration existed at the time the function was invoked. In this case, +/// the existing registration is terminated and then a new one is created in its place. Pending transfers may be lost. /// The return value is a negated invalid argument error if any of the input arguments are invalid. /// -/// The time complexity is logarithmic from the number of current subscriptions under the specified transfer kind. -/// This function does not allocate new memory. The function may deallocate memory if such subscription already -/// existed; the deallocation behavior is specified in the documentation for udpardRxUnsubscribe(). -/// -/// Subscription instances have large look-up tables to ensure that the temporal properties of the algorithms are -/// invariant to the network configuration (i.e., a node that is validated on a network containing one other node -/// will provably perform identically on a network that contains X nodes). This is a conscious time-memory trade-off. -int8_t udpardRxSubscribe(UdpardInstance* const ins, - const UdpardTransferKind transfer_kind, - const UdpardPortID port_id, - const size_t extent, - const UdpardMicrosecond transfer_id_timeout_usec, - UdpardRxSubscription* const out_subscription); - -/// This function reverses the effect of udpardRxSubscribe(). -/// If the subscription is found, all its memory is de-allocated (session states and payload buffers); to determine -/// the amount of memory freed, please refer to the memory allocation requirement model of udpardRxAccept(). -/// -/// The return value is 1 if such subscription existed (and, therefore, it was removed). -/// The return value is 0 if such subscription does not exist. In this case, the function has no effect. +/// The time complexity is logarithmic from the number of current registrations under the specified transfer kind +/// (request or response). +/// This function does not allocate new memory. The function may deallocate memory if such registration already +/// existed; the deallocation behavior is specified in the documentation for udpardRxServiceDispatcherCancel. +int8_t udpardRxServiceDispatcherListen(UdpardRxServiceDispatcher* const self, + UdpardRxService* const service, + const UdpardPortID service_id, + const bool is_request, + const size_t extent); + +/// This function reverses the effect of udpardRxServiceDispatcherListen. +/// If the registration is found, all its memory is de-allocated (session states and payload buffers). +/// Please refer to the UdpardRxPort session description for detailed information on the amount of memory freed. +/// +/// The return value is 1 if such registration existed (and, therefore, it was removed). +/// The return value is 0 if such registration does not exist. In this case, the function has no effect. /// The return value is a negated invalid argument error if any of the input arguments are invalid. /// -/// The time complexity is logarithmic from the number of current subscriptions under the specified transfer kind. +/// The time complexity is logarithmic from the number of current registration under the specified transfer kind. /// This function does not allocate new memory. -int8_t udpardRxUnsubscribe(UdpardInstance* const ins, - const UdpardTransferKind transfer_kind, - const UdpardPortID port_id); +int8_t udpardRxServiceDispatcherCancel(UdpardRxServiceDispatcher* const self, + const UdpardPortID service_id, + const bool is_request); + +/// Datagrams received from the sockets of this service dispatcher are fed into this function. +/// It is the analog of udpardRxSubscriptionReceive for RPC-service transfers. +/// Please refer to the documentation of udpardRxSubscriptionReceive for the usage information. +int8_t udpardRxServiceDispatcherReceive(UdpardRxServiceDispatcher* const self, + UdpardRxService** const service, + const UdpardMicrosecond timestamp_usec, + const UdpardConstPayload datagram_payload, + const uint8_t redundant_iface_index, + UdpardRxServiceTransfer* const received_transfer); + #ifdef __cplusplus } #endif diff --git a/tests/.clang-tidy b/tests/.clang-tidy index 78a09ef..d00a940 100644 --- a/tests/.clang-tidy +++ b/tests/.clang-tidy @@ -31,11 +31,14 @@ Checks: >- -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-avoid-const-or-ref-data-members, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-reinterpret-cast, -*-no-malloc, -cert-msc30-c, -cert-msc50-cpp, + -modernize-macro-to-enum, + -cppcoreguidelines-owning-memory, WarningsAsErrors: '*' HeaderFilterRegex: '.*\.hpp' AnalyzeTemporaryDtors: false diff --git a/tests/.idea/dictionaries/pavel.xml b/tests/.idea/dictionaries/pavel.xml new file mode 100644 index 0000000..d796a59 --- /dev/null +++ b/tests/.idea/dictionaries/pavel.xml @@ -0,0 +1,33 @@ + + + + baremetal + cfamily + deallocation + discardment + dscp + dudpard + ghcr + intravehicular + libcanard + libgtest + libudpard + misoptimizes + mmcu + nosonar + opencyphal + profraw + stringmakers + udpard + udpip + unicast + unlisten + unregistration + usec + wconversion + wextra + wtype + zzzzzz + + + \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0321aa8..896e87b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,15 +1,20 @@ # This software is distributed under the terms of the MIT License. -# Copyright (c) 2016 OpenCyphal. +# Copyright (c) OpenCyphal. # Author: Pavel Kirienko -# Contributors: https://github.com/OpenCyphal/libudpard/contributors. cmake_minimum_required(VERSION 3.12) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_EXTENSIONS OFF) + project(udpard_tests C CXX) enable_testing() set(CTEST_OUTPUT_ON_FAILURE ON) set(library_dir "${CMAKE_SOURCE_DIR}/../libudpard") +set(NO_STATIC_ANALYSIS OFF CACHE BOOL "disable udpard static analysis") + # Use -DNO_STATIC_ANALYSIS=1 to suppress static analysis. # If not suppressed, the tools used here shall be available, otherwise the build will fail. if (NOT NO_STATIC_ANALYSIS) @@ -33,57 +38,48 @@ else () add_custom_target(format COMMAND ${clang_format} -i -fallback-style=none -style=file --verbose ${format_files}) endif () -set(CMAKE_CXX_STANDARD 17) -set(CXX_EXTENSIONS OFF) add_compile_options( - -Wall -Wextra -pedantic -fstrict-aliasing -Wdouble-promotion -Wswitch-enum -Wfloat-equal -Wundef - -Werror + -Wall -Wextra -Werror -pedantic -Wdouble-promotion -Wswitch-enum -Wfloat-equal -Wundef -Wconversion -Wtype-limits -Wsign-conversion -Wcast-align ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual -Wnon-virtual-dtor -Wsign-promo") -include_directories(catch ${library_dir}) -add_definitions(-DCATCH_CONFIG_FAST_COMPILE=1 -DCATCH_CONFIG_ENABLE_ALL_STRINGMAKERS=1) - -set(common_sources catch/main.cpp ${library_dir}/udpard.c) - function(gen_test name files compile_definitions compile_flags link_flags c_standard) - add_executable(${name} ${common_sources} ${files}) + add_executable(${name} ${library_dir}/udpard.c ${files}) + target_include_directories(${name} PUBLIC ${library_dir}) target_compile_definitions(${name} PUBLIC ${compile_definitions}) - target_link_libraries(${name} pthread) + # We're not using find_package(GTest) because this method can only find the package for one specific architecture, + # which is not what we want. We build tests for AMD64 and x86 separately, which requires linking against different + # GTest libraries located in different directories. Instead of doing it the hard way via find_package separately + # per architecture, we just pass the library names directly and let the linker figure out where to find them. + target_link_libraries(${name} pthread gtest gtest_main) set_target_properties( ${name} PROPERTIES COMPILE_FLAGS "${compile_flags}" LINK_FLAGS "${link_flags}" C_STANDARD "${c_standard}" + C_EXTENSIONS OFF ) - add_test("run_${name}" "${name}" --rng-seed time) + add_test("run_${name}" "${name}" --gtest_random_seed=0 --gtest_color=yes) endfunction() -function(gen_test_matrix name files compile_definitions compile_flags) - gen_test("${name}_x64_c99" "${files}" "${compile_definitions}" "${compile_flags} -m64" "-m64" "99") - # gen_test("${name}_x32_c99" "${files}" "${compile_definitions}" "${compile_flags} -m32" "-m32" "99") - gen_test("${name}_x64_c11" "${files}" "${compile_definitions}" "${compile_flags} -m64" "-m64" "11") - # gen_test("${name}_x32_c11" "${files}" "${compile_definitions}" "${compile_flags} -m32" "-m32" "11") - # Coverage is only available for GCC builds. - if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_BUILD_TYPE STREQUAL "Debug")) - gen_test("${name}_cov" - "${files}" - "${compile_definitions}" - "${compile_flags} -g -O0 --coverage" - "--coverage" - "11") - endif () +function(gen_test_matrix name files compile_definitions compile_flags link_flags) + gen_test("${name}_x64_c99" "${files}" "${compile_definitions}" "${compile_flags} -m64" "-m64 ${link_flags}" "99") + gen_test("${name}_x32_c99" "${files}" "${compile_definitions}" "${compile_flags} -m32" "-m32 ${link_flags}" "99") + gen_test("${name}_x64_c11" "${files}" "${compile_definitions}" "${compile_flags} -m64" "-m64 ${link_flags}" "11") + gen_test("${name}_x32_c11" "${files}" "${compile_definitions}" "${compile_flags} -m32" "-m32 ${link_flags}" "11") endfunction() # Disable missing declaration warning to allow exposure of private definitions. gen_test_matrix(test_private - "test_private_crc.cpp;test_private_rx.cpp;test_private_tx.cpp;test_private_cavl.cpp;" + "test_private_cavl.cpp;" "-DUDPARD_CONFIG_HEADER=\"${CMAKE_CURRENT_SOURCE_DIR}/udpard_config_private.h\"" - "-Wno-missing-declarations") + "-Wno-missing-declarations" + "") gen_test_matrix(test_public - "test_public_tx.cpp;test_public_rx.cpp;test_public_roundtrip.cpp;test_self.cpp;" + "test_self.cpp" "" - "-Wmissing-declarations") + "-Wmissing-declarations" + "") diff --git a/tests/catch/catch.hpp b/tests/catch/catch.hpp deleted file mode 100644 index b852ddb..0000000 --- a/tests/catch/catch.hpp +++ /dev/null @@ -1,12011 +0,0 @@ -/* - * Catch v2.0.1 - * Generated: 2017-11-03 11:53:39.642003 - * ---------------------------------------------------------- - * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 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) - */ -#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -// start catch.hpp - - -#ifdef __clang__ -# pragma clang system_header -#elif defined __GNUC__ -# pragma GCC system_header -#endif - -// 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 "-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" - -# pragma GCC diagnostic push -# 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_EXTERNAL_INTERFACES -# if defined(CATCH_CONFIG_DISABLE_MATCHERS) -# undef CATCH_CONFIG_DISABLE_MATCHERS -# endif -#endif - -// start catch_platform.h - -#ifdef __APPLE__ -# include -# if TARGET_OS_MAC == 1 -# define CATCH_PLATFORM_MAC -# elif 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) -# define CATCH_PLATFORM_WINDOWS -#endif - -// end catch_platform.h -#ifdef CATCH_IMPL -# ifndef CLARA_CONFIG_MAIN -# define CLARA_CONFIG_MAIN_NOT_DEFINED -# define CLARA_CONFIG_MAIN -# endif -#endif - -// start catch_tag_alias_autoregistrar.h - -// start catch_common.h - -// start catch_compiler_capabilities.h - -// Detect a number of compiler features - by compiler -// The following features are defined: -// -// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? -// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? -// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? -// **************** -// Note to maintainers: if new toggles are added please document them -// in configuration.md, too -// **************** - -// In general each macro has a _NO_ form -// (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. - -#ifdef __cplusplus - -# if __cplusplus >= 201402L -# define CATCH_CPP14_OR_GREATER -# endif - -#endif - -#ifdef __clang__ - -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_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 // __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 - -#endif - -#ifdef __OS400__ -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# define CATCH_CONFIG_COLOUR_NONE -#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__ - -//////////////////////////////////////////////////////////////////////////////// -// Visual C++ -#ifdef _MSC_VER - -// 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 - -#endif // _MSC_VER - -//////////////////////////////////////////////////////////////////////////////// - -// 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_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) -# 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_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_CONFIG_POSIX_SIGNALS -#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_GLOBALS_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS -#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 -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) -#else -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) -#endif - -#include -#include -#include - -namespace Catch { - - struct CaseSensitive { enum Choice { - Yes, - No - }; }; - - class NonCopyable { - NonCopyable( NonCopyable const& ) = delete; - NonCopyable( NonCopyable && ) = delete; - NonCopyable& operator = ( NonCopyable const& ) = delete; - NonCopyable& operator = ( NonCopyable && ) = delete; - - protected: - NonCopyable(); - virtual ~NonCopyable(); - }; - - struct SourceLineInfo { - - SourceLineInfo() = delete; - SourceLineInfo( char const* _file, std::size_t _line ) noexcept; - - SourceLineInfo( SourceLineInfo const& other ) = default; - SourceLineInfo( SourceLineInfo && ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo& operator = ( SourceLineInfo && ) = default; - - bool empty() const noexcept; - bool operator == ( SourceLineInfo const& other ) const noexcept; - bool operator < ( SourceLineInfo const& other ) const noexcept; - - char const* file; - std::size_t line; - }; - - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); - - // This is just here to avoid compiler warnings with macro constants and boolean literals - bool isTrue( bool value ); - bool alwaysTrue(); - bool alwaysFalse(); - - // Use this in variadic streaming macros to allow - // >> +StreamEndStop - // as well as - // >> stuff +StreamEndStop - struct StreamEndStop { - std::string operator+() const; - }; - template - T const& operator + ( T const& value, StreamEndStop ) { - return value; - } -} - -#define CATCH_INTERNAL_LINEINFO \ - ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) - -// end catch_common.h -namespace Catch { - - struct RegistrarForTagAliases { - RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - }; - -} // end namespace Catch - -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } - -// end catch_tag_alias_autoregistrar.h -// start catch_test_registry.h - -// start catch_interfaces_testcase.h - -#include -#include - -namespace Catch { - - class TestSpec; - - struct ITestInvoker { - virtual void invoke () const = 0; - virtual ~ITestInvoker(); - }; - - using ITestCasePtr = std::shared_ptr; - - class TestCase; - struct IConfig; - - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); - virtual std::vector const& getAllTests() const = 0; - virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; - }; - - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector const& getAllTestCasesSorted( IConfig const& config ); - -} - -// end catch_interfaces_testcase.h -// start catch_stringref.h - -#include -#include -#include - -namespace Catch { - - class StringData; - - /// 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. c_str() must return a null terminated - /// string, however, and so the StringRef will internally take ownership - /// (taking a copy), if necessary. In theory this ownership is not externally - /// visible - but it does mean (substring) StringRefs should not be shared between - /// threads. - class StringRef { - friend struct StringRefTestAccess; - - using size_type = std::size_t; - - char const* m_start; - size_type m_size; - - char* m_data = nullptr; - - void takeOwnership(); - - public: // construction/ assignment - StringRef() noexcept; - StringRef( StringRef const& other ) noexcept; - StringRef( StringRef&& other ) noexcept; - StringRef( char const* rawChars ) noexcept; - StringRef( char const* rawChars, size_type size ) noexcept; - StringRef( std::string const& stdString ) noexcept; - ~StringRef() noexcept; - - auto operator = ( StringRef other ) noexcept -> StringRef&; - operator std::string() const; - - void swap( StringRef& other ) noexcept; - - public: // operators - auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != ( StringRef const& other ) const noexcept -> bool; - - auto operator[] ( size_type index ) const noexcept -> char; - - public: // named queries - auto empty() const noexcept -> bool; - auto size() const noexcept -> size_type; - auto numberOfCharacters() const noexcept -> size_type; - auto c_str() const -> char const*; - - public: // substrings and searches - auto substr( size_type start, size_type size ) const noexcept -> StringRef; - - private: // ownership queries - may not be consistent between calls - auto isOwned() const noexcept -> bool; - auto isSubstring() const noexcept -> bool; - auto data() const noexcept -> char const*; - }; - - auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; - auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; - auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; - - auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; - -} // namespace Catch - -// end catch_stringref.h -namespace Catch { - -template -class TestInvokerAsMethod : public ITestInvoker { - void (C::*m_testAsMethod)(); -public: - TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} - - void invoke() const override { - C obj; - (obj.*m_testAsMethod)(); - } -}; - -auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*; - -template -auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { - return new(std::nothrow) TestInvokerAsMethod( testAsMethod ); -} - -struct NameAndTags { - NameAndTags( StringRef name_ = "", StringRef tags_ = "" ) noexcept; - StringRef name; - StringRef tags; -}; - -struct AutoReg : NonCopyable { - AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept; - ~AutoReg(); -}; - -} // end namespace Catch - -#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 : ClassName { \ - void test(); \ - }; \ - } \ - void TestName::test() - -#endif - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ - static void TestName(); \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - 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_GLOBALS_WARNINGS \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ \ - struct TestName : ClassName{ \ - void test(); \ - }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ - } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - 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_GLOBALS_WARNINGS \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS - -// end catch_test_registry.h -// start catch_capture.hpp - -// start catch_assertionhandler.h - -// start catch_decomposer.h - -// start catch_tostring.h - -#include -#include -#include -#include -#include - -#ifdef __OBJC__ -// start catch_objc_arc.hpp - -#import - -#ifdef __has_feature -#define CATCH_ARC_ENABLED __has_feature(objc_arc) -#else -#define CATCH_ARC_ENABLED 0 -#endif - -void arcSafeRelease( NSObject* obj ); -id performOptionalSelector( id obj, SEL sel ); - -#if !CATCH_ARC_ENABLED -inline void arcSafeRelease( NSObject* obj ) { - [obj release]; -} -inline id performOptionalSelector( id obj, SEL sel ) { - if( [obj respondsToSelector: sel] ) - return [obj performSelector: sel]; - return nil; -} -#define CATCH_UNSAFE_UNRETAINED -#define CATCH_ARC_STRONG -#else -inline void arcSafeRelease( NSObject* ){} -inline id performOptionalSelector( id obj, SEL sel ) { -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" -#endif - if( [obj respondsToSelector: sel] ) - return [obj performSelector: sel]; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - return nil; -} -#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained -#define CATCH_ARC_STRONG __strong -#endif - -// end catch_objc_arc.hpp -#endif - -#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 - -// 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 { - // Bring in operator<< from global namespace into Catch namespace - using ::operator<<; - - namespace Detail { - - extern const std::string unprintableString; - - std::string rawMemoryToString( const void *object, std::size_t size ); - - template - std::string rawMemoryToString( const T& object ) { - return rawMemoryToString( &object, sizeof(object) ); - } - - template - class IsStreamInsertable { - template - static auto test(int) - -> decltype(std::declval() << std::declval(), std::true_type()); - - template - static auto test(...)->std::false_type; - - public: - static const bool value = decltype(test(0))::value; - }; - - } // namespace Detail - - // If we decide for C++14, change these to enable_if_ts - template - struct StringMaker { - template - static - typename std::enable_if<::Catch::Detail::IsStreamInsertable::value, std::string>::type - convert(const Fake& t) { - std::ostringstream sstr; - sstr << t; - return sstr.str(); - } - - template - static - typename std::enable_if::value, std::string>::type - convert(const Fake&) { - return Detail::unprintableString; - } - }; - - namespace Detail { - - // This function dispatches all stringification requests inside of Catch. - // Should be preferably called fully qualified, like ::Catch::Detail::stringify - template - std::string stringify(const T& e) { - return ::Catch::StringMaker::type>::type>::convert(e); - } - - } // namespace Detail - - // Some predefined specializations - - template<> - struct StringMaker { - static std::string convert(const std::string& str); - }; - template<> - struct StringMaker { - static std::string convert(const std::wstring& wstr); - }; - - template<> - struct StringMaker { - static std::string convert(char const * str); - }; - template<> - struct StringMaker { - static std::string convert(char * str); - }; - template<> - struct StringMaker { - static std::string convert(wchar_t const * str); - }; - template<> - struct StringMaker { - static std::string convert(wchar_t * str); - }; - - template - struct StringMaker { - static std::string convert(const char* str) { - return ::Catch::Detail::stringify(std::string{ str }); - } - }; - template - struct StringMaker { - static std::string convert(const char* str) { - return ::Catch::Detail::stringify(std::string{ str }); - } - }; - template - struct StringMaker { - static std::string convert(const char* str) { - return ::Catch::Detail::stringify(std::string{ str }); - } - }; - - template<> - struct StringMaker { - static std::string convert(int value); - }; - template<> - struct StringMaker { - static std::string convert(long value); - }; - template<> - struct StringMaker { - static std::string convert(long long value); - }; - template<> - struct StringMaker { - static std::string convert(unsigned int value); - }; - template<> - struct StringMaker { - static std::string convert(unsigned long value); - }; - template<> - struct StringMaker { - static std::string convert(unsigned long long value); - }; - - template<> - struct StringMaker { - static std::string convert(bool b); - }; - - template<> - struct StringMaker { - static std::string convert(char c); - }; - template<> - struct StringMaker { - static std::string convert(signed char c); - }; - template<> - struct StringMaker { - static std::string convert(unsigned char c); - }; - - template<> - struct StringMaker { - static std::string convert(std::nullptr_t); - }; - - template<> - struct StringMaker { - static std::string convert(float value); - }; - template<> - struct StringMaker { - static std::string convert(double value); - }; - - template - struct StringMaker { - template - static std::string convert(U* p) { - if (p) { - return ::Catch::Detail::rawMemoryToString(p); - } else { - return "nullptr"; - } - } - }; - - template - struct StringMaker { - static std::string convert(R C::* p) { - if (p) { - return ::Catch::Detail::rawMemoryToString(p); - } else { - return "nullptr"; - } - } - }; - - namespace Detail { - template - std::string rangeToString(InputIterator first, InputIterator last) { - std::ostringstream oss; - oss << "{ "; - if (first != last) { - oss << ::Catch::Detail::stringify(*first); - for (++first; first != last; ++first) - oss << ", " << ::Catch::Detail::stringify(*first); - } - oss << " }"; - return oss.str(); - } - } - - template - struct StringMaker > { - static std::string convert( std::vector const& v ) { - return ::Catch::Detail::rangeToString( v.begin(), v.end() ); - } - }; - - template - struct EnumStringMaker { - static std::string convert(const T& t) { - return ::Catch::Detail::stringify(static_cast::type>(t)); - } - }; - -#ifdef __OBJC__ - template<> - struct StringMaker { - static std::string convert(NSString * nsstring) { - if (!nsstring) - return "nil"; - return std::string("@") + [nsstring UTF8String]; - } - }; - template<> - struct StringMaker { - static std::string convert(NSObject* nsObject) { - return ::Catch::Detail::stringify([nsObject description]); - } - - }; - namespace Detail { - inline std::string stringify( NSString* nsstring ) { - return StringMaker::convert( nsstring ); - } - - } // namespace Detail -#endif // __OBJC__ - -} // namespace Catch - -////////////////////////////////////////////////////// -// Separate std-lib types stringification, so it can be selectively enabled -// This means that we do not bring in - -#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) -# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER -# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER -# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -#endif - -// Separate std::pair specialization -#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) -#include -namespace Catch { - template - struct StringMaker > { - static std::string convert(const std::pair& pair) { - std::ostringstream oss; - oss << "{ " - << ::Catch::Detail::stringify(pair.first) - << ", " - << ::Catch::Detail::stringify(pair.second) - << " }"; - return oss.str(); - } - }; -} -#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER - -// Separate std::tuple specialization -#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) -#include -namespace Catch { - namespace Detail { - template< - typename Tuple, - std::size_t N = 0, - bool = (N < std::tuple_size::value) - > - struct TupleElementPrinter { - static void print(const Tuple& tuple, std::ostream& os) { - os << (N ? ", " : " ") - << ::Catch::Detail::stringify(std::get(tuple)); - TupleElementPrinter::print(tuple, os); - } - }; - - template< - typename Tuple, - std::size_t N - > - struct TupleElementPrinter { - static void print(const Tuple&, std::ostream&) {} - }; - - } - - template - struct StringMaker> { - static std::string convert(const std::tuple& tuple) { - std::ostringstream os; - os << '{'; - Detail::TupleElementPrinter>::print(tuple, os); - os << " }"; - return os.str(); - } - }; -} -#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER - -// Separate std::chrono::duration specialization -#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -#include -#include -#include - -template -struct ratio_string { - static std::string symbol(); -}; - -template -std::string ratio_string::symbol() { - std::ostringstream oss; - oss << '[' << Ratio::num << '/' - << Ratio::den << ']'; - return oss.str(); -} -template <> -struct ratio_string { - static std::string symbol() { return "a"; } -}; -template <> -struct ratio_string { - static std::string symbol() { return "f"; } -}; -template <> -struct ratio_string { - static std::string symbol() { return "p"; } -}; -template <> -struct ratio_string { - static std::string symbol() { return "n"; } -}; -template <> -struct ratio_string { - static std::string symbol() { return "u"; } -}; -template <> -struct ratio_string { - static std::string symbol() { return "m"; } -}; - -namespace Catch { - //////////// - // std::chrono::duration specializations - template - struct StringMaker> { - static std::string convert(std::chrono::duration const& duration) { - std::ostringstream oss; - oss << duration.count() << ' ' << ratio_string::symbol() << 's'; - return oss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const& duration) { - std::ostringstream oss; - oss << duration.count() << " s"; - return oss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const& duration) { - std::ostringstream oss; - oss << duration.count() << " m"; - return oss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const& duration) { - std::ostringstream oss; - oss << duration.count() << " h"; - return oss.str(); - } - }; - - //////////// - // std::chrono::time_point specialization - // Generic time_point cannot be specialized, only std::chrono::time_point - template - struct StringMaker> { - static std::string convert(std::chrono::time_point const& time_point) { - return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; - } - }; - // std::chrono::time_point specialization - template - struct StringMaker> { - static std::string convert(std::chrono::time_point const& time_point) { - auto converted = std::chrono::system_clock::to_time_t(time_point); - -#ifdef _MSC_VER - std::tm timeInfo = {}; - gmtime_s(&timeInfo, &converted); -#else - std::tm* timeInfo = std::gmtime(&converted); -#endif - - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); - char timeStamp[timeStampSize]; - const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; - -#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 - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -// end catch_tostring.h -#include - -#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 -#endif - -namespace Catch { - - struct ITransientExpression { - virtual auto isBinaryExpression() const -> bool = 0; - virtual auto getResult() const -> bool = 0; - virtual void streamReconstructedExpression( std::ostream &os ) const = 0; - - // We don't actually need a virtual destructore, but many static analysers - // complain if it's not here :-( - virtual ~ITransientExpression(); - }; - - void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); - - template - class BinaryExpr : public ITransientExpression { - bool m_result; - LhsT m_lhs; - StringRef m_op; - RhsT m_rhs; - - auto isBinaryExpression() const -> bool override { return true; } - auto getResult() const -> bool override { return m_result; } - - void streamReconstructedExpression( std::ostream &os ) const override { - formatReconstructedExpression - ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); - } - - public: - BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) - : m_result( comparisonResult ), - m_lhs( lhs ), - m_op( op ), - m_rhs( rhs ) - {} - }; - - template - class UnaryExpr : public ITransientExpression { - LhsT m_lhs; - - auto isBinaryExpression() const -> bool override { return false; } - auto getResult() const -> bool override { return m_lhs ? true : false; } - - void streamReconstructedExpression( std::ostream &os ) const override { - os << Catch::Detail::stringify( m_lhs ); - } - - public: - UnaryExpr( LhsT lhs ) : m_lhs( lhs ) {} - }; - - // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) - template - auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return lhs == rhs; }; - template - auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } - template - auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } - template - auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } - template - auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } - - template - auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return lhs != rhs; }; - template - auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } - template - auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } - template - auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } - template - auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } - - template - class ExprLhs { - LhsT m_lhs; - public: - ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} - - template - auto operator == ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( compareEqual( m_lhs, rhs ), m_lhs, "==", rhs ); - } - auto operator == ( bool rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs == rhs, m_lhs, "==", rhs ); - } - - template - auto operator != ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs ); - } - auto operator != ( bool rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs != rhs, m_lhs, "!=", rhs ); - } - - template - auto operator > ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs > rhs, m_lhs, ">", rhs ); - } - template - auto operator < ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs < rhs, m_lhs, "<", rhs ); - } - template - auto operator >= ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs >= rhs, m_lhs, ">=", rhs ); - } - template - auto operator <= ( RhsT const& rhs ) -> BinaryExpr const { - return BinaryExpr( m_lhs <= rhs, m_lhs, "<=", rhs ); - } - - auto makeUnaryExpr() const -> UnaryExpr { - return UnaryExpr( m_lhs ); - } - }; - - void handleExpression( ITransientExpression const& expr ); - - template - void handleExpression( ExprLhs const& expr ) { - handleExpression( expr.makeUnaryExpr() ); - } - - struct Decomposer { - template - auto operator <= ( T const& lhs ) -> ExprLhs { - return ExprLhs( lhs ); - } - auto operator <=( bool value ) -> ExprLhs { - return ExprLhs( value ); - } - }; - -} // end namespace Catch - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -// end catch_decomposer.h -// start catch_assertioninfo.h - -// start catch_result_type.h - -namespace Catch { - - // ResultWas::OfType enum - struct ResultWas { enum OfType { - Unknown = -1, - Ok = 0, - Info = 1, - Warning = 2, - - FailureBit = 0x10, - - ExpressionFailed = FailureBit | 1, - ExplicitFailure = FailureBit | 2, - - Exception = 0x100 | FailureBit, - - ThrewException = Exception | 1, - DidntThrowException = Exception | 2, - - FatalErrorCondition = 0x200 | FailureBit - - }; }; - - bool isOk( ResultWas::OfType resultType ); - bool isJustInfo( int flags ); - - // ResultDisposition::Flags enum - struct ResultDisposition { enum Flags { - Normal = 0x01, - - ContinueOnFailure = 0x02, // Failures fail test, but execution continues - FalseTest = 0x04, // Prefix expression with ! - SuppressFail = 0x08 // Failures are reported but do not fail the test - }; }; - - ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); - - bool shouldContinueOnFailure( int flags ); - bool isFalseTest( int flags ); - bool shouldSuppressFailure( int flags ); - -} // end namespace Catch - -// end catch_result_type.h -namespace Catch { - - struct AssertionInfo - { - StringRef macroName; - SourceLineInfo lineInfo; - StringRef capturedExpression; - ResultDisposition::Flags resultDisposition; - - // 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 - -// end catch_assertioninfo.h -namespace Catch { - - struct TestFailureException{}; - struct AssertionResultData; - - class LazyExpression { - friend class AssertionHandler; - friend struct AssertionStats; - - ITransientExpression const* m_transientExpression = nullptr; - bool m_isNegated; - public: - LazyExpression( bool isNegated ); - LazyExpression( LazyExpression const& other ); - LazyExpression& operator = ( LazyExpression const& ) = delete; - - explicit operator bool() const; - - friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; - }; - - class AssertionHandler { - AssertionInfo m_assertionInfo; - bool m_shouldDebugBreak = false; - bool m_shouldThrow = false; - bool m_inExceptionGuard = false; - - public: - AssertionHandler - ( StringRef macroName, - SourceLineInfo const& lineInfo, - StringRef capturedExpression, - ResultDisposition::Flags resultDisposition ); - ~AssertionHandler(); - - void handle( ITransientExpression const& expr ); - - template - void handle( ExprLhs const& expr ) { - handle( expr.makeUnaryExpr() ); - } - void handle( ResultWas::OfType resultType ); - void handle( ResultWas::OfType resultType, StringRef const& message ); - void handle( ResultWas::OfType resultType, ITransientExpression const* expr, bool negated ); - void handle( AssertionResultData const& resultData, ITransientExpression const* expr ); - - auto shouldDebugBreak() const -> bool; - auto allowThrows() const -> bool; - void reactWithDebugBreak() const; - void reactWithoutDebugBreak() const; - void useActiveException(); - void setExceptionGuard(); - void unsetExceptionGuard(); - }; - - void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ); - -} // namespace Catch - -// end catch_assertionhandler.h -// start catch_message.h - -#include -#include - -namespace Catch { - - struct MessageInfo { - MessageInfo( std::string const& _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ); - - std::string 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 - MessageStream& operator << ( T const& value ) { - m_stream << value; - return *this; - } - - // !TBD reuse a global/ thread-local stream - std::ostringstream m_stream; - }; - - struct MessageBuilder : MessageStream { - MessageBuilder( std::string const& macroName, - SourceLineInfo const& lineInfo, - ResultWas::OfType type ); - - template - MessageBuilder& operator << ( T const& value ) { - m_stream << value; - return *this; - } - - MessageInfo m_info; - }; - - class ScopedMessage { - public: - ScopedMessage( MessageBuilder const& builder ); - ~ScopedMessage(); - - MessageInfo m_info; - }; - -} // end namespace Catch - -// end catch_message.h -// start catch_interfaces_capture.h - -#include - -namespace Catch { - - class AssertionResult; - struct AssertionInfo; - struct SectionInfo; - struct SectionEndInfo; - struct MessageInfo; - struct Counts; - struct BenchmarkInfo; - struct BenchmarkStats; - - struct IResultCapture { - - virtual ~IResultCapture(); - - virtual void assertionStarting( AssertionInfo const& info ) = 0; - 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 benchmarkStarting( BenchmarkInfo const& info ) = 0; - virtual void benchmarkEnded( BenchmarkStats const& stats ) = 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 void exceptionEarlyReported() = 0; - - virtual void handleFatalErrorCondition( StringRef message ) = 0; - - virtual bool lastAssertionPassed() = 0; - virtual void assertionPassed() = 0; - virtual void assertionRun() = 0; - }; - - IResultCapture& getResultCapture(); -} - -// end catch_interfaces_capture.h -// start catch_debugger.h - -namespace Catch { - bool isDebuggerActive(); -} - -#ifdef CATCH_PLATFORM_MAC - - #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ - -#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 - - #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 - -#ifdef CATCH_TRAP - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } -#else - #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); -#endif - -// end catch_debugger.h -#if !defined(CATCH_CONFIG_DISABLE) - -#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) - #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ -#else - #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( handler ) \ - handler.reactWithDebugBreak(); - -/////////////////////////////////////////////////////////////////////////////// -// 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_TRY( capturer ) capturer.setExceptionGuard(); -#define INTERNAL_CATCH_CATCH( capturer ) capturer.unsetExceptionGuard(); - -#else // CATCH_CONFIG_FAST_COMPILE - -/////////////////////////////////////////////////////////////////////////////// -// 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( handler ) \ - if( handler.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ - handler.reactWithoutDebugBreak(); - -#define INTERNAL_CATCH_TRY( capturer ) try -#define INTERNAL_CATCH_CATCH( capturer ) catch(...) { capturer.useActiveException(); } - -#endif - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ - INTERNAL_CATCH_TRY( catchAssertionHandler ) { \ - CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - catchAssertionHandler.handle( Catch::Decomposer() <= __VA_ARGS__ ); \ - CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ - } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::isTrue( false && static_cast( !!(__VA_ARGS__) ) ) ) // the expression 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_CATCH_IF( macroName, resultDisposition, ... ) \ - INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ - if( Catch::getResultCapture().lastAssertionPassed() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ - INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ - if( !Catch::getResultCapture().lastAssertionPassed() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ - try { \ - static_cast(__VA_ARGS__); \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - } \ - catch( ... ) { \ - catchAssertionHandler.useActiveException(); \ - } \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast(__VA_ARGS__); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ - } \ - catch( ... ) { \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - } \ - else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast(expr); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ - } \ - catch( exceptionType const& ) { \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - } \ - catch( ... ) { \ - catchAssertionHandler.useActiveException(); \ - } \ - else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ - catchAssertionHandler.handle( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_INFO( macroName, log ) \ - Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, 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::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast(__VA_ARGS__); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ - } \ - catch( ... ) { \ - handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher ); \ - } \ - else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -#endif // CATCH_CONFIG_DISABLE - -// end catch_capture.hpp -// start catch_section.h - -// start catch_section_info.h - -// start catch_totals.h - -#include - -namespace Catch { - - struct Counts { - Counts operator - ( Counts const& other ) const; - Counts& operator += ( Counts const& other ); - - std::size_t total() const; - bool allPassed() const; - bool allOk() const; - - std::size_t passed = 0; - std::size_t failed = 0; - std::size_t failedButOk = 0; - }; - - struct Totals { - - Totals operator - ( Totals const& other ) const; - Totals& operator += ( Totals const& other ); - - Totals delta( Totals const& prevTotals ) const; - - Counts assertions; - Counts testCases; - }; -} - -// end catch_totals.h -#include - -namespace Catch { - - struct SectionInfo { - SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description = std::string() ); - - std::string name; - std::string description; - SourceLineInfo lineInfo; - }; - - struct SectionEndInfo { - SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ); - - SectionInfo sectionInfo; - Counts prevAssertions; - double durationInSeconds; - }; - -} // end namespace Catch - -// end catch_section_info.h -// start catch_timer.h - -#include - -namespace Catch { - - auto getCurrentNanosecondsSinceEpoch() -> uint64_t; - auto getEstimatedClockResolution() -> uint64_t; - - class Timer { - uint64_t m_nanoseconds = 0; - public: - void start(); - auto getElapsedNanoseconds() const -> unsigned int; - auto getElapsedMicroseconds() const -> unsigned int; - auto getElapsedMilliseconds() const -> unsigned int; - auto getElapsedSeconds() const -> double; - }; - -} // namespace Catch - -// end catch_timer.h -#include - -namespace Catch { - - class Section : NonCopyable { - public: - Section( SectionInfo const& info ); - ~Section(); - - // This indicates whether the section should be executed or not - explicit operator bool() const; - - private: - SectionInfo m_info; - - std::string m_name; - Counts m_assertions; - bool m_sectionIncluded; - Timer m_timer; - }; - -} // end namespace Catch - - #define INTERNAL_CATCH_SECTION( ... ) \ - if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) - -// end catch_section.h -// start catch_benchmark.h - -#include -#include - -namespace Catch { - - class BenchmarkLooper { - - std::string m_name; - std::size_t m_count = 0; - std::size_t m_iterationsToRun = 1; - uint64_t m_resolution; - Timer m_timer; - - static auto getResolution() -> uint64_t; - public: - // Keep most of this inline as it's on the code path that is being timed - BenchmarkLooper( StringRef name ) - : m_name( name ), - m_resolution( getResolution() ) - { - reportStart(); - m_timer.start(); - } - - explicit operator bool() { - if( m_count < m_iterationsToRun ) - return true; - return needsMoreIterations(); - } - - void increment() { - ++m_count; - } - - void reportStart(); - auto needsMoreIterations() -> bool; - }; - -} // end namespace Catch - -#define BENCHMARK( name ) \ - for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() ) - -// end catch_benchmark.h -// start catch_interfaces_exception.h - -// start catch_interfaces_registry_hub.h - -#include -#include - -namespace Catch { - - class TestCase; - struct ITestCaseRegistry; - struct IExceptionTranslatorRegistry; - struct IExceptionTranslator; - struct IReporterRegistry; - struct IReporterFactory; - struct ITagAliasRegistry; - class StartupExceptionRegistry; - - using IReporterFactoryPtr = std::shared_ptr; - - struct IRegistryHub { - virtual ~IRegistryHub(); - - virtual IReporterRegistry const& getReporterRegistry() const = 0; - virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; - virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; - - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; - - virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; - }; - - struct IMutableRegistryHub { - virtual ~IMutableRegistryHub(); - 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; - }; - - IRegistryHub& getRegistryHub(); - IMutableRegistryHub& getMutableRegistryHub(); - void cleanUp(); - std::string translateActiveException(); - -} - -// 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 - -#include -#include -#include - -namespace Catch { - using exceptionTranslateFunction = std::string(*)(); - - struct IExceptionTranslator; - using ExceptionTranslators = std::vector>; - - struct IExceptionTranslator { - virtual ~IExceptionTranslator(); - virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; - }; - - struct IExceptionTranslatorRegistry { - virtual ~IExceptionTranslatorRegistry(); - - virtual std::string translateActiveException() const = 0; - }; - - class ExceptionTranslatorRegistrar { - template - class ExceptionTranslator : public IExceptionTranslator { - public: - - ExceptionTranslator( std::string(*translateFunction)( T& ) ) - : m_translateFunction( translateFunction ) - {} - - std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { - try { - if( it == itEnd ) - std::rethrow_exception(std::current_exception()); - else - return (*it)->translate( it+1, itEnd ); - } - catch( T& ex ) { - return m_translateFunction( ex ); - } - } - - protected: - std::string(*m_translateFunction)( T& ); - }; - - public: - template - ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { - getMutableRegistryHub().registerTranslator - ( new ExceptionTranslator( translateFunction ) ); - } - }; -} - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ - static std::string translatorName( signature ); \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ - static std::string translatorName( signature ) - -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) - -// end catch_interfaces_exception.h -// start catch_approx.h - -// start catch_enforce.h - -#include -#include - -#define CATCH_PREPARE_EXCEPTION( type, msg ) \ - type( static_cast( std::ostringstream() << msg ).str() ) -#define CATCH_INTERNAL_ERROR( msg ) \ - throw CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg); -#define CATCH_ERROR( msg ) \ - throw CATCH_PREPARE_EXCEPTION( std::domain_error, msg ) -#define CATCH_ENFORCE( condition, msg ) \ - do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) - -// end catch_enforce.h -#include - -namespace Catch { -namespace Detail { - - class Approx { - private: - bool equalityComparisonImpl(double other) const; - - public: - explicit Approx ( double value ); - - static Approx custom(); - - template ::value>::type> - Approx operator()( T const& value ) { - Approx approx( static_cast(value) ); - approx.epsilon( m_epsilon ); - approx.margin( m_margin ); - approx.scale( m_scale ); - return approx; - } - - template ::value>::type> - explicit Approx( T const& value ): Approx(static_cast(value)) - {} - - template ::value>::type> - friend bool operator == ( const T& lhs, Approx const& rhs ) { - auto lhs_v = static_cast(lhs); - return rhs.equalityComparisonImpl(lhs_v); - } - - template ::value>::type> - friend bool operator == ( Approx const& lhs, const T& rhs ) { - return operator==( rhs, lhs ); - } - - template ::value>::type> - friend bool operator != ( T const& lhs, Approx const& rhs ) { - return !operator==( lhs, rhs ); - } - - template ::value>::type> - friend bool operator != ( Approx const& lhs, T const& rhs ) { - return !operator==( rhs, lhs ); - } - - template ::value>::type> - friend bool operator <= ( T const& lhs, Approx const& rhs ) { - return static_cast(lhs) < rhs.m_value || lhs == rhs; - } - - template ::value>::type> - friend bool operator <= ( Approx const& lhs, T const& rhs ) { - return lhs.m_value < static_cast(rhs) || lhs == rhs; - } - - template ::value>::type> - friend bool operator >= ( T const& lhs, Approx const& rhs ) { - return static_cast(lhs) > rhs.m_value || lhs == rhs; - } - - template ::value>::type> - friend bool operator >= ( Approx const& lhs, T const& rhs ) { - return lhs.m_value > static_cast(rhs) || lhs == rhs; - } - - template ::value>::type> - Approx& epsilon( T const& newEpsilon ) { - double epsilonAsDouble = static_cast(newEpsilon); - CATCH_ENFORCE(epsilonAsDouble >= 0 && epsilonAsDouble <= 1.0, - "Invalid Approx::epsilon: " << epsilonAsDouble - << ", Approx::epsilon has to be between 0 and 1"); - m_epsilon = epsilonAsDouble; - return *this; - } - - template ::value>::type> - Approx& margin( T const& newMargin ) { - double marginAsDouble = static_cast(newMargin); - CATCH_ENFORCE(marginAsDouble >= 0, - "Invalid Approx::margin: " << marginAsDouble - << ", Approx::Margin has to be non-negative."); - m_margin = marginAsDouble; - return *this; - } - - template ::value>::type> - Approx& scale( T const& newScale ) { - m_scale = static_cast(newScale); - return *this; - } - - std::string toString() const; - - private: - double m_epsilon; - double m_margin; - double m_scale; - double m_value; - }; -} - -template<> -struct StringMaker { - static std::string convert(Catch::Detail::Approx const& value); -}; - -} // end namespace Catch - -// end catch_approx.h -// start catch_string_manip.h - -#include -#include - -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 ); - 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 ); - - 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 -#include - -namespace Catch { -namespace Matchers { - namespace Impl { - - template struct MatchAllOf; - template struct MatchAnyOf; - template 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; - }; - - template - struct MatcherMethod { - virtual bool match( ObjectT const& arg ) const = 0; - }; - template - struct MatcherMethod { - virtual bool match( PtrT* arg ) const = 0; - }; - - template - struct MatcherBase : MatcherUntypedBase, MatcherMethod { - - MatchAllOf operator && ( MatcherBase const& other ) const; - MatchAnyOf operator || ( MatcherBase const& other ) const; - MatchNotOf operator ! () const; - }; - - template - struct MatchAllOf : MatcherBase { - bool match( ArgT const& arg ) const override { - for( auto matcher : m_matchers ) { - if (!matcher->match(arg)) - return false; - } - return true; - } - 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; - } - - MatchAllOf& operator && ( MatcherBase const& other ) { - m_matchers.push_back( &other ); - return *this; - } - - std::vector const*> m_matchers; - }; - template - struct MatchAnyOf : MatcherBase { - - 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; - } - - MatchAnyOf& operator || ( MatcherBase const& other ) { - m_matchers.push_back( &other ); - return *this; - } - - std::vector const*> m_matchers; - }; - - template - struct MatchNotOf : MatcherBase { - - MatchNotOf( MatcherBase const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} - - bool match( ArgT const& arg ) const override { - return !m_underlyingMatcher.match( arg ); - } - - std::string describe() const override { - return "not " + m_underlyingMatcher.toString(); - } - MatcherBase const& m_underlyingMatcher; - }; - - template - MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { - return MatchAllOf() && *this && other; - } - template - MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { - return MatchAnyOf() || *this || other; - } - template - MatchNotOf MatcherBase::operator ! () const { - return MatchNotOf( *this ); - } - - } // namespace Impl - -} // namespace Matchers - -using namespace Matchers; -using Matchers::Impl::MatcherBase; - -} // namespace Catch - -// end catch_matchers.h -// start catch_matchers_string.h - -#include - -namespace Catch { -namespace Matchers { - - namespace StdString { - - struct CasedString - { - CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); - std::string adjustString( std::string const& str ) const; - std::string caseSensitivitySuffix() const; - - CaseSensitive::Choice m_caseSensitivity; - std::string m_str; - }; - - struct StringMatcherBase : MatcherBase { - StringMatcherBase( std::string const& operation, CasedString const& comparator ); - std::string describe() const override; - - CasedString m_comparator; - std::string m_operation; - }; - - struct EqualsMatcher : StringMatcherBase { - EqualsMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - struct ContainsMatcher : StringMatcherBase { - ContainsMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - struct StartsWithMatcher : StringMatcherBase { - StartsWithMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - struct EndsWithMatcher : StringMatcherBase { - EndsWithMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - - } // namespace StdString - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - - StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); - 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 ); - -} // namespace Matchers -} // namespace Catch - -// end catch_matchers_string.h -// start catch_matchers_vector.h - -namespace Catch { -namespace Matchers { - - namespace Vector { - - template - struct ContainsElementMatcher : MatcherBase, T> { - - ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} - - bool match(std::vector const &v) const override { - for (auto const& el : v) { - if (el == m_comparator) { - return true; - } - } - return false; - } - - std::string describe() const override { - return "Contains: " + ::Catch::Detail::stringify( m_comparator ); - } - - T const& m_comparator; - }; - - template - struct ContainsMatcher : MatcherBase, std::vector > { - - ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} - - bool match(std::vector const &v) const override { - // !TBD: see note in EqualsMatcher - if (m_comparator.size() > v.size()) - return false; - 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; - } - std::string describe() const override { - return "Contains: " + ::Catch::Detail::stringify( m_comparator ); - } - - std::vector const& m_comparator; - }; - - template - struct EqualsMatcher : MatcherBase, std::vector > { - - EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} - - bool match(std::vector 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 etc - // - then just call that directly - if (m_comparator.size() != v.size()) - return false; - for (std::size_t i = 0; i < v.size(); ++i) - if (m_comparator[i] != v[i]) - return false; - return true; - } - std::string describe() const override { - return "Equals: " + ::Catch::Detail::stringify( m_comparator ); - } - std::vector const& m_comparator; - }; - - } // namespace Vector - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - - template - Vector::ContainsMatcher Contains( std::vector const& comparator ) { - return Vector::ContainsMatcher( comparator ); - } - - template - Vector::ContainsElementMatcher VectorContains( T const& comparator ) { - return Vector::ContainsElementMatcher( comparator ); - } - - template - Vector::EqualsMatcher Equals( std::vector const& comparator ) { - return Vector::EqualsMatcher( comparator ); - } - -} // namespace Matchers -} // namespace Catch - -// end catch_matchers_vector.h -namespace Catch { - - template - class MatchExpr : public ITransientExpression { - ArgT const& m_arg; - MatcherT m_matcher; - StringRef m_matcherString; - bool m_result; - public: - MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) - : m_arg( arg ), - m_matcher( matcher ), - m_matcherString( matcherString ), - m_result( matcher.match( arg ) ) - {} - - auto isBinaryExpression() const -> bool override { return true; } - auto getResult() const -> bool override { return m_result; } - - 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; - - void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ); - - template - auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) -> MatchExpr { - return MatchExpr( arg, matcher, matcherString ); - } - -} // namespace Catch - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - INTERNAL_CATCH_TRY( catchAssertionHandler ) { \ - catchAssertionHandler.handle( Catch::makeMatchExpr( arg, matcher, #matcher ) ); \ - } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast(__VA_ARGS__ ); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ - } \ - catch( exceptionType const& ex ) { \ - catchAssertionHandler.handle( Catch::makeMatchExpr( ex, matcher, #matcher ) ); \ - } \ - catch( ... ) { \ - catchAssertionHandler.useActiveException(); \ - } \ - else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -// end catch_capture_matchers.h -#endif - -// These files are included here so the single_include script doesn't put them -// in the conditionally compiled sections -// start catch_test_case_info.h - -#include -#include -#include - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -namespace Catch { - - struct ITestInvoker; - - struct TestCaseInfo { - enum SpecialProperties{ - None = 0, - IsHidden = 1 << 1, - ShouldFail = 1 << 2, - MayFail = 1 << 3, - Throws = 1 << 4, - NonPortable = 1 << 5, - Benchmark = 1 << 6 - }; - - TestCaseInfo( std::string const& _name, - std::string const& _className, - std::string const& _description, - std::vector const& _tags, - SourceLineInfo const& _lineInfo ); - - friend void setTags( TestCaseInfo& testCaseInfo, std::vector 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::vector tags; - std::vector lcaseTags; - SourceLineInfo lineInfo; - SpecialProperties properties; - }; - - class TestCase : public TestCaseInfo { - public: - - TestCase( ITestInvoker* testCase, TestCaseInfo const& info ); - - TestCase withName( std::string const& _newName ) const; - - void invoke() const; - - TestCaseInfo const& getTestCaseInfo() const; - - bool operator == ( TestCase const& other ) const; - bool operator < ( TestCase const& other ) const; - - private: - std::shared_ptr test; - }; - - TestCase makeTestCase( ITestInvoker* testCase, - std::string const& className, - std::string const& name, - std::string const& description, - SourceLineInfo const& lineInfo ); -} - -#ifdef __clang__ -#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__ -// start catch_objc.hpp - -#import - -#include - -// NB. Any general catch headers included here must be included -// in catch.hpp first to make sure they are included by the single -// header for non obj-usage - -/////////////////////////////////////////////////////////////////////////////// -// This protocol is really only here for (self) documenting purposes, since -// all its methods are optional. -@protocol OcFixture - -@optional - --(void) setUp; --(void) tearDown; - -@end - -namespace Catch { - - class OcMethod : public ITestInvoker { - - public: - OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} - - virtual void invoke() const { - id obj = [[m_cls alloc] init]; - - performOptionalSelector( obj, @selector(setUp) ); - performOptionalSelector( obj, m_sel ); - performOptionalSelector( obj, @selector(tearDown) ); - - arcSafeRelease( obj ); - } - private: - virtual ~OcMethod() {} - - Class m_cls; - SEL m_sel; - }; - - namespace Detail{ - - inline std::string getAnnotation( Class cls, - std::string const& annotationName, - std::string const& testCaseName ) { - NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; - SEL sel = NSSelectorFromString( selStr ); - arcSafeRelease( selStr ); - id value = performOptionalSelector( cls, sel ); - if( value ) - return [(NSString*)value UTF8String]; - return ""; - } - } - - 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 ); - - for( int c = 0; c < noClasses; c++ ) { - Class cls = classes[c]; - { - u_int count; - Method* methods = class_copyMethodList( cls, &count ); - for( u_int m = 0; m < count ; m++ ) { - SEL selector = method_getName(methods[m]); - std::string methodName = sel_getName(selector); - if( startsWith( methodName, "Catch_TestCase_" ) ) { - std::string testCaseName = methodName.substr( 15 ); - std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); - 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("",0) ) ); - noTestMethods++; - } - } - free(methods); - } - } - return noTestMethods; - } - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) - - namespace Matchers { - namespace Impl { - namespace NSStringMatchers { - - struct StringHolder : MatcherBase{ - StringHolder( NSString* substr ) : m_substr( [substr copy] ){} - StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} - StringHolder() { - arcSafeRelease( m_substr ); - } - - bool match( NSString* arg ) const override { - return false; - } - - NSString* CATCH_ARC_STRONG m_substr; - }; - - struct Equals : StringHolder { - Equals( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const override { - return (str != nil || m_substr == nil ) && - [str isEqualToString:m_substr]; - } - - std::string describe() const override { - return "equals string: " + Catch::Detail::stringify( m_substr ); - } - }; - - struct Contains : StringHolder { - Contains( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const { - return (str != nil || m_substr == nil ) && - [str rangeOfString:m_substr].location != NSNotFound; - } - - std::string describe() const override { - return "contains string: " + Catch::Detail::stringify( m_substr ); - } - }; - - struct StartsWith : StringHolder { - StartsWith( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const override { - return (str != nil || m_substr == nil ) && - [str rangeOfString:m_substr].location == 0; - } - - std::string describe() const override { - return "starts with: " + Catch::Detail::stringify( m_substr ); - } - }; - struct EndsWith : StringHolder { - EndsWith( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const override { - return (str != nil || m_substr == nil ) && - [str rangeOfString:m_substr].location == [str length] - [m_substr length]; - } - - std::string describe() const override { - return "ends with: " + Catch::Detail::stringify( m_substr ); - } - }; - - } // namespace NSStringMatchers - } // namespace Impl - - inline Impl::NSStringMatchers::Equals - Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } - - inline Impl::NSStringMatchers::Contains - Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } - - inline Impl::NSStringMatchers::StartsWith - StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } - - inline Impl::NSStringMatchers::EndsWith - EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } - - } // namespace Matchers - - using namespace Matchers; - -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -} // namespace Catch - -/////////////////////////////////////////////////////////////////////////////// -#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*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \ -{ \ -return @ desc; \ -} \ --(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix ) - -#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ ) - -// end catch_objc.hpp -#endif - -#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES -// start catch_external_interfaces.h - -// start catch_reporter_bases.hpp - -// start catch_interfaces_reporter.h - -// start catch_config.hpp - -// start catch_test_spec_parser.h - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -// start catch_test_spec.h - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -// start catch_wildcard_pattern.h - -namespace Catch -{ - class WildcardPattern { - enum WildcardPosition { - NoWildcard = 0, - WildcardAtStart = 1, - WildcardAtEnd = 2, - WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd - }; - - public: - - WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ); - virtual ~WildcardPattern() = default; - virtual bool matches( std::string const& str ) const; - - private: - std::string adjustCase( std::string const& str ) const; - CaseSensitive::Choice m_caseSensitivity; - WildcardPosition m_wildcard = NoWildcard; - std::string m_pattern; - }; -} - -// end catch_wildcard_pattern.h -#include -#include -#include - -namespace Catch { - - class TestSpec { - struct Pattern { - virtual ~Pattern(); - virtual bool matches( TestCaseInfo const& testCase ) const = 0; - }; - using PatternPtr = std::shared_ptr; - - class NamePattern : public Pattern { - public: - NamePattern( std::string const& name ); - virtual ~NamePattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; - private: - WildcardPattern m_wildcardPattern; - }; - - class TagPattern : public Pattern { - public: - TagPattern( std::string const& tag ); - virtual ~TagPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; - private: - std::string m_tag; - }; - - class ExcludedPattern : public Pattern { - public: - ExcludedPattern( PatternPtr const& underlyingPattern ); - virtual ~ExcludedPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; - private: - PatternPtr m_underlyingPattern; - }; - - struct Filter { - std::vector m_patterns; - - bool matches( TestCaseInfo const& testCase ) const; - }; - - public: - bool hasFilters() const; - bool matches( TestCaseInfo const& testCase ) const; - - private: - std::vector m_filters; - - friend class TestSpecParser; - }; -} - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_test_spec.h -// start catch_interfaces_tag_alias_registry.h - -#include - -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 = None; - bool m_exclusion = false; - std::size_t m_start = std::string::npos, m_pos = 0; - std::string m_arg; - std::vector m_escapeChars; - TestSpec::Filter m_currentFilter; - TestSpec m_testSpec; - ITagAliasRegistry const* m_tagAliases = nullptr; - - public: - TestSpecParser( ITagAliasRegistry const& tagAliases ); - - TestSpecParser& parse( std::string const& arg ); - TestSpec testSpec(); - - private: - void visitChar( char c ); - void startNewMode( Mode mode, std::size_t start ); - void escape(); - std::string subString() const; - - template - void addPattern() { - std::string token = subString(); - for( std::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() ) { - TestSpec::PatternPtr pattern = std::make_shared( token ); - if( m_exclusion ) - pattern = std::make_shared( pattern ); - m_currentFilter.m_patterns.push_back( pattern ); - } - m_exclusion = false; - m_mode = None; - } - - void addFilter(); - }; - TestSpec parseTestSpec( std::string const& arg ); - -} // namespace Catch - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_test_spec_parser.h -// start catch_interfaces_config.h - -#include -#include -#include -#include - -namespace Catch { - - enum class Verbosity { - Quiet = 0, - Normal, - High - }; - - struct WarnAbout { enum What { - Nothing = 0x00, - NoAssertions = 0x01 - }; }; - - 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 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 int benchmarkResolutionMultiple() const = 0; - virtual UseColour::YesOrNo useColour() const = 0; - virtual std::vector const& getSectionsToRun() const = 0; - virtual Verbosity verbosity() const = 0; - }; - - using IConfigPtr = std::shared_ptr; -} - -// end catch_interfaces_config.h -// Libstdc++ doesn't like incomplete classes for unique_ptr -// start catch_stream.h - -// start catch_streambuf.h - -#include - -namespace Catch { - - class StreamBufBase : public std::streambuf { - public: - virtual ~StreamBufBase(); - }; -} - -// end catch_streambuf.h -#include -#include -#include -#include - -namespace Catch { - - std::ostream& cout(); - std::ostream& cerr(); - std::ostream& clog(); - - struct IStream { - virtual ~IStream(); - virtual std::ostream& stream() const = 0; - }; - - class FileStream : public IStream { - mutable std::ofstream m_ofs; - public: - FileStream( std::string const& filename ); - ~FileStream() override = default; - public: // IStream - std::ostream& stream() const override; - }; - - class CoutStream : public IStream { - mutable std::ostream m_os; - public: - CoutStream(); - ~CoutStream() override = default; - - public: // IStream - std::ostream& stream() const override; - }; - - class DebugOutStream : public IStream { - std::unique_ptr m_streamBuf; - mutable std::ostream m_os; - public: - DebugOutStream(); - ~DebugOutStream() override = default; - - public: // IStream - std::ostream& stream() const override; - }; -} - -// end catch_stream.h - -#include -#include -#include - -#ifndef CATCH_CONFIG_CONSOLE_WIDTH -#define CATCH_CONFIG_CONSOLE_WIDTH 80 -#endif - -namespace Catch { - - struct IStream; - - 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; - int benchmarkResolutionMultiple = 100; - - Verbosity verbosity = Verbosity::Normal; - WarnAbout::What warnings = WarnAbout::Nothing; - ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; - RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; - UseColour::YesOrNo useColour = UseColour::Auto; - WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; - - std::string outputFilename; - std::string name; - std::string processName; - - std::vector reporterNames; - std::vector testsOrTags; - std::vector sectionsToRun; - }; - - class Config : public IConfig { - public: - - Config() = default; - Config( ConfigData const& data ); - virtual ~Config() = default; - - std::string const& getFilename() const; - - bool listTests() const; - bool listTestNamesOnly() const; - bool listTags() const; - bool listReporters() const; - - std::string getProcessName() const; - - std::vector const& getReporterNames() const; - std::vector const& getSectionsToRun() const override; - - virtual TestSpec const& testSpec() 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; - ShowDurations::OrNot showDurations() const override; - RunTests::InWhatOrder runOrder() const override; - unsigned int rngSeed() const override; - int benchmarkResolutionMultiple() const override; - UseColour::YesOrNo useColour() const override; - bool shouldDebugBreak() const override; - int abortAfter() const override; - bool showInvisibles() const override; - Verbosity verbosity() const override; - - private: - - IStream const* openStream(); - ConfigData m_data; - - std::unique_ptr m_stream; - TestSpec m_testSpec; - }; - -} // end namespace Catch - -// end catch_config.hpp -// start catch_assertionresult.h - -#include - -namespace Catch { - - 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: - 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; - std::string getTestMacroName() const; - - //protected: - AssertionInfo m_info; - AssertionResultData m_resultData; - }; - -} // end namespace Catch - -// end catch_assertionresult.h -// start catch_option.hpp - -namespace Catch { - - // An optional type - template - class Option { - public: - Option() : nullableValue( nullptr ) {} - Option( T const& _value ) - : nullableValue( new( storage ) T( _value ) ) - {} - Option( Option const& _other ) - : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) - {} - - ~Option() { - reset(); - } - - Option& operator= ( Option const& _other ) { - if( &_other != this ) { - reset(); - if( _other ) - nullableValue = new( storage ) T( *_other ); - } - return *this; - } - Option& operator = ( T const& _value ) { - reset(); - nullableValue = new( storage ) T( _value ); - return *this; - } - - void reset() { - if( nullableValue ) - nullableValue->~T(); - nullableValue = nullptr; - } - - T& operator*() { return *nullableValue; } - T const& operator*() const { return *nullableValue; } - T* operator->() { return nullableValue; } - const T* operator->() const { return nullableValue; } - - T valueOr( T const& defaultValue ) const { - return nullableValue ? *nullableValue : defaultValue; - } - - bool some() const { return nullableValue != nullptr; } - bool none() const { return nullableValue == nullptr; } - - bool operator !() const { return nullableValue == nullptr; } - explicit operator bool() const { - return some(); - } - - private: - T *nullableValue; - alignas(alignof(T)) char storage[sizeof(T)]; - }; - -} // end namespace Catch - -// end catch_option.hpp -#include -#include -#include -#include -#include - -namespace Catch { - - struct ReporterConfig { - explicit ReporterConfig( IConfigPtr const& _fullConfig ); - - ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ); - - std::ostream& stream() const; - IConfigPtr fullConfig() const; - - private: - std::ostream* m_stream; - IConfigPtr m_fullConfig; - }; - - struct ReporterPreferences { - bool shouldRedirectStdOut = false; - }; - - template - struct LazyStat : Option { - LazyStat& operator=( T const& _value ) { - Option::operator=( _value ); - used = false; - return *this; - } - void reset() { - Option::reset(); - used = false; - } - bool used = false; - }; - - 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 ); - - std::string name; - std::size_t groupIndex; - std::size_t groupsCounts; - }; - - struct AssertionStats { - AssertionStats( AssertionResult const& _assertionResult, - std::vector const& _infoMessages, - Totals const& _totals ); - - AssertionStats( AssertionStats const& ) = default; - AssertionStats( AssertionStats && ) = default; - AssertionStats& operator = ( AssertionStats const& ) = default; - AssertionStats& operator = ( AssertionStats && ) = default; - virtual ~AssertionStats(); - - AssertionResult assertionResult; - std::vector infoMessages; - Totals totals; - }; - - 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(); - - SectionInfo sectionInfo; - Counts assertions; - double durationInSeconds; - bool missingAssertions; - }; - - struct TestCaseStats { - TestCaseStats( TestCaseInfo const& _testInfo, - Totals const& _totals, - std::string const& _stdOut, - std::string const& _stdErr, - bool _aborting ); - - TestCaseStats( TestCaseStats const& ) = default; - TestCaseStats( TestCaseStats && ) = default; - TestCaseStats& operator = ( TestCaseStats const& ) = default; - TestCaseStats& operator = ( TestCaseStats && ) = default; - virtual ~TestCaseStats(); - - TestCaseInfo testInfo; - Totals totals; - std::string stdOut; - std::string stdErr; - bool aborting; - }; - - struct TestGroupStats { - TestGroupStats( GroupInfo const& _groupInfo, - Totals const& _totals, - bool _aborting ); - TestGroupStats( GroupInfo const& _groupInfo ); - - 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; - }; - - struct BenchmarkInfo { - std::string name; - }; - struct BenchmarkStats { - BenchmarkInfo info; - std::size_t iterations; - uint64_t elapsedTimeInNanoseconds; - }; - - struct IStreamingReporter { - virtual ~IStreamingReporter() = default; - - // Implementing class must also provide the following static methods: - // static std::string getDescription(); - // static std::set getSupportedVerbosities() - - virtual ReporterPreferences getPreferences() const = 0; - - virtual void noMatchingTestCases( std::string const& spec ) = 0; - - 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; - - // *** experimental *** - virtual void benchmarkStarting( BenchmarkInfo const& ) {} - - 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; - - // *** experimental *** - virtual void benchmarkEnded( BenchmarkStats const& ) {} - - 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; - - struct IReporterFactory { - virtual ~IReporterFactory(); - virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0; - virtual std::string getDescription() const = 0; - }; - using IReporterFactoryPtr = std::shared_ptr; - - struct IReporterRegistry { - using FactoryMap = std::map; - using Listeners = std::vector; - - 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; - }; - - void addReporter( IStreamingReporterPtr& existingReporter, IStreamingReporterPtr&& additionalReporter ); - -} // end namespace Catch - -// end catch_interfaces_reporter.h -#include -#include -#include -#include -#include -#include - -namespace Catch { - void prepareExpandedExpression(AssertionResult& result); - - // Returns double formatted as %.3f (format expected on output) - std::string getFormattedDuration( double duration ); - - template - struct StreamingReporterBase : IStreamingReporter { - - StreamingReporterBase( ReporterConfig const& _config ) - : m_config( _config.fullConfig() ), - stream( _config.stream() ) - { - m_reporterPrefs.shouldRedirectStdOut = false; - CATCH_ENFORCE( DerivedT::getSupportedVerbosities().count( m_config->verbosity() ), "Verbosity level not supported by this reporter" ); - } - - ReporterPreferences getPreferences() const override { - return m_reporterPrefs; - } - - static std::set getSupportedVerbosities() { - return { Verbosity::Normal }; - } - - ~StreamingReporterBase() override = default; - - void noMatchingTestCases(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 currentTestRunInfo; - LazyStat currentGroupInfo; - LazyStat currentTestCaseInfo; - - std::vector m_sectionStack; - ReporterPreferences m_reporterPrefs; - }; - - template - struct CumulativeReporterBase : IStreamingReporter { - template - struct Node { - explicit Node( T const& _value ) : value( _value ) {} - virtual ~Node() {} - - using ChildNodes = std::vector>; - 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 const& other) const { - return operator==(*other); - } - - SectionStats stats; - using ChildSections = std::vector>; - using Assertions = std::vector; - 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 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; - using TestGroupNode = Node; - using TestRunNode = Node; - - CumulativeReporterBase( ReporterConfig const& _config ) - : m_config( _config.fullConfig() ), - stream( _config.stream() ) - { - m_reporterPrefs.shouldRedirectStdOut = false; - CATCH_ENFORCE( DerivedT::getSupportedVerbosities().count( m_config->verbosity() ), "Verbosity level not supported by this reporter" ); - } - ~CumulativeReporterBase() override = default; - - ReporterPreferences getPreferences() const override { - return m_reporterPrefs; - } - - static std::set 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 node; - if( m_sectionStack.empty() ) { - if( !m_rootSection ) - m_rootSection = std::make_shared( 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( incompleteStats ); - parentNode.childSections.push_back( node ); - } - else - node = *it; - } - m_sectionStack.push_back( node ); - m_deepestSection = std::move(node); - } - - void assertionStarting(AssertionInfo const&) override {} - - 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( 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(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 testGroupEnded(TestGroupStats const& testGroupStats) override { - auto node = std::make_shared(testGroupStats); - node->children.swap(m_testCases); - m_testGroups.push_back(node); - } - void testRunEnded(TestRunStats const& testRunStats) override { - auto node = std::make_shared(testRunStats); - node->children.swap(m_testGroups); - m_testRuns.push_back(node); - testRunEndedCumulative(); - } - virtual void testRunEndedCumulative() = 0; - - void skipTest(TestCaseInfo const&) override {} - - IConfigPtr m_config; - std::ostream& stream; - std::vector m_assertions; - std::vector>> m_sections; - std::vector> m_testCases; - std::vector> m_testGroups; - - std::vector> m_testRuns; - - std::shared_ptr m_rootSection; - std::shared_ptr m_deepestSection; - std::vector> m_sectionStack; - ReporterPreferences m_reporterPrefs; - }; - - template - 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( ReporterConfig const& _config ); - - 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, - - // By intention - FileName = LightGrey, - Warning = Yellow, - ResultError = BrightRed, - ResultSuccess = BrightGreen, - ResultExpectedFailure = Warning, - - Error = BrightRed, - Success = Green, - - OriginalExpression = Cyan, - ReconstructedExpression = Yellow, - - 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; - }; - - std::ostream& operator << ( std::ostream& os, Colour const& ); - -} // end namespace Catch - -// end catch_console_colour.h -// start catch_reporter_registrars.hpp - - -namespace Catch { - - template - class ReporterRegistrar { - - class ReporterFactory : public IReporterFactory { - - virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { - return std::unique_ptr( new T( config ) ); - } - - virtual std::string getDescription() const override { - return T::getDescription(); - } - }; - - public: - - ReporterRegistrar( std::string const& name ) { - getMutableRegistryHub().registerReporter( name, std::make_shared() ); - } - }; - - template - class ListenerRegistrar { - - class ListenerFactory : public IReporterFactory { - - virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { - return std::unique_ptr( new T( config ) ); - } - virtual std::string getDescription() const override { - return std::string(); - } - }; - - public: - - ListenerRegistrar() { - getMutableRegistryHub().registerListener( std::make_shared() ); - } - }; -} - -#if !defined(CATCH_CONFIG_DISABLE) - -#define CATCH_REGISTER_REPORTER( name, reporterType ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS - -#define CATCH_REGISTER_LISTENER( listenerType ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -#else // CATCH_CONFIG_DISABLE - -#define CATCH_REGISTER_REPORTER(name, reporterType) -#define CATCH_REGISTER_LISTENER(listenerType) - -#endif // CATCH_CONFIG_DISABLE - -// end catch_reporter_registrars.hpp -// end catch_external_interfaces.h -#endif - -#ifdef CATCH_IMPL -// start catch_impl.hpp - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wweak-vtables" -#endif - -// Keep these here for external reporters -// start catch_test_case_tracker.h - -#include -#include -#include - -namespace Catch { -namespace TestCaseTracking { - - struct NameAndLocation { - std::string name; - SourceLineInfo location; - - NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); - }; - - struct ITracker; - - using ITrackerPtr = std::shared_ptr; - - struct ITracker { - virtual ~ITracker(); - - // static queries - virtual NameAndLocation const& nameAndLocation() const = 0; - - // 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 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 isIndexTracker() const = 0; - }; - - class TrackerContext { - - enum RunState { - NotStarted, - Executing, - CompletedCycle - }; - - ITrackerPtr m_rootTracker; - ITracker* m_currentTracker = nullptr; - RunState m_runState = NotStarted; - - public: - - static TrackerContext& instance(); - - ITracker& startRun(); - void endRun(); - - void startCycle(); - void completeCycle(); - - bool completedCycle() const; - ITracker& currentTracker(); - void setCurrentTracker( ITracker* tracker ); - }; - - class TrackerBase : public ITracker { - protected: - enum CycleState { - NotStarted, - Executing, - ExecutingChildren, - NeedsAnotherRun, - CompletedSuccessfully, - Failed - }; - - class TrackerHasName { - NameAndLocation m_nameAndLocation; - public: - TrackerHasName( NameAndLocation const& nameAndLocation ); - bool operator ()( ITrackerPtr const& tracker ) const; - }; - - using Children = std::vector; - NameAndLocation m_nameAndLocation; - TrackerContext& m_ctx; - ITracker* m_parent; - Children m_children; - CycleState m_runState = NotStarted; - - public: - TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - - NameAndLocation const& nameAndLocation() const override; - bool isComplete() const override; - bool isSuccessfullyCompleted() const override; - bool isOpen() const override; - bool hasChildren() const override; - - void addChild( ITrackerPtr const& child ) override; - - ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override; - ITracker& parent() override; - - void openChild() override; - - bool isSectionTracker() const override; - bool isIndexTracker() const override; - - void open(); - - void close() override; - void fail() override; - void markAsNeedingAnotherRun() override; - - private: - void moveToParent(); - void moveToThis(); - }; - - class SectionTracker : public TrackerBase { - std::vector m_filters; - public: - SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - - bool isSectionTracker() const override; - - static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); - - void tryOpen(); - - void addInitialFilters( std::vector const& filters ); - void addNextFilters( std::vector const& filters ); - }; - - class IndexTracker : public TrackerBase { - int m_size; - int m_index = -1; - public: - IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ); - - bool isIndexTracker() const override; - void close() override; - - static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ); - - int index() const; - - void moveNext(); - }; - -} // namespace TestCaseTracking - -using TestCaseTracking::ITracker; -using TestCaseTracking::TrackerContext; -using TestCaseTracking::SectionTracker; -using TestCaseTracking::IndexTracker; - -} // namespace Catch - -// end catch_test_case_tracker.h - -// start catch_leak_detector.h - -namespace Catch { - - struct LeakDetector { - LeakDetector(); - }; - -} -// end catch_leak_detector.h -// Cpp files will be included in the single-header file here -// start catch_approx.cpp - -#include -#include - -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); -} - -} - -namespace Catch { -namespace Detail { - - Approx::Approx ( double value ) - : m_epsilon( std::numeric_limits::epsilon()*100 ), - m_margin( 0.0 ), - m_scale( 0.0 ), - m_value( value ) - {} - - Approx Approx::custom() { - return Approx( 0 ); - } - - std::string Approx::toString() const { - std::ostringstream oss; - oss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; - return oss.str(); - } - - 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(m_value))); - } - -} // end namespace Detail - -std::string StringMaker::convert(Catch::Detail::Approx const& value) { - return value.toString(); -} - -} // end namespace Catch -// end catch_approx.cpp -// start catch_assertionhandler.cpp - -// start catch_context.h - -#include - -namespace Catch { - - struct IResultCapture; - struct IRunner; - struct IConfig; - - using IConfigPtr = std::shared_ptr; - - struct IContext - { - virtual ~IContext(); - - virtual IResultCapture* getResultCapture() = 0; - virtual IRunner* getRunner() = 0; - virtual IConfigPtr getConfig() const = 0; - }; - - struct IMutableContext : IContext - { - virtual ~IMutableContext(); - virtual void setResultCapture( IResultCapture* resultCapture ) = 0; - virtual void setRunner( IRunner* runner ) = 0; - virtual void setConfig( IConfigPtr const& config ) = 0; - }; - - IContext& getCurrentContext(); - IMutableContext& getCurrentMutableContext(); - void cleanUpContext(); -} - -// end catch_context.h -#include - -namespace Catch { - - auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { - expr.streamReconstructedExpression( os ); - return os; - } - - 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 { - os << "{** error - unchecked empty expression requested **}"; - } - return os; - } - - AssertionHandler::AssertionHandler - ( StringRef macroName, - SourceLineInfo const& lineInfo, - StringRef capturedExpression, - ResultDisposition::Flags resultDisposition ) - : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition } - { - getCurrentContext().getResultCapture()->assertionStarting( m_assertionInfo ); - } - AssertionHandler::~AssertionHandler() { - if ( m_inExceptionGuard ) { - handle( ResultWas::ThrewException, "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE" ); - getCurrentContext().getResultCapture()->exceptionEarlyReported(); - } - } - - void AssertionHandler::handle( ITransientExpression const& expr ) { - - bool negated = isFalseTest( m_assertionInfo.resultDisposition ); - bool result = expr.getResult() != negated; - - handle( result ? ResultWas::Ok : ResultWas::ExpressionFailed, &expr, negated ); - } - void AssertionHandler::handle( ResultWas::OfType resultType ) { - handle( resultType, nullptr, false ); - } - void AssertionHandler::handle( ResultWas::OfType resultType, StringRef const& message ) { - AssertionResultData data( resultType, LazyExpression( false ) ); - data.message = message; - handle( data, nullptr ); - } - void AssertionHandler::handle( ResultWas::OfType resultType, ITransientExpression const* expr, bool negated ) { - AssertionResultData data( resultType, LazyExpression( negated ) ); - handle( data, expr ); - } - void AssertionHandler::handle( AssertionResultData const& resultData, ITransientExpression const* expr ) { - - getResultCapture().assertionRun(); - - AssertionResult assertionResult{ m_assertionInfo, resultData }; - assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; - - getResultCapture().assertionEnded( assertionResult ); - - if( !assertionResult.isOk() ) { - m_shouldDebugBreak = getCurrentContext().getConfig()->shouldDebugBreak(); - m_shouldThrow = - getCurrentContext().getRunner()->aborting() || - (m_assertionInfo.resultDisposition & ResultDisposition::Normal); - } - } - - auto AssertionHandler::allowThrows() const -> bool { - return getCurrentContext().getConfig()->allowThrows(); - } - - auto AssertionHandler::shouldDebugBreak() const -> bool { - return m_shouldDebugBreak; - } - void AssertionHandler::reactWithDebugBreak() const { - 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 reactWithoutDebugBreak() call - /////////////////////////////////////////////////////////////////// - CATCH_BREAK_INTO_DEBUGGER(); - } - reactWithoutDebugBreak(); - } - void AssertionHandler::reactWithoutDebugBreak() const { - if( m_shouldThrow ) - throw Catch::TestFailureException(); - } - - void AssertionHandler::useActiveException() { - handle( ResultWas::ThrewException, Catch::translateActiveException() ); - } - - void AssertionHandler::setExceptionGuard() { - assert( m_inExceptionGuard == false ); - m_inExceptionGuard = true; - } - void AssertionHandler::unsetExceptionGuard() { - assert( m_inExceptionGuard == true ); - m_inExceptionGuard = false; - } - - // 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 matcherString ) { - handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); - } - -} // 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 ) { - // !TBD Use stringstream for now, but rework above to pass stream in - std::ostringstream oss; - oss << lazyExpression; - reconstructedExpression = oss.str(); - } - } - return reconstructedExpression; - } - - 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[0] != 0; - } - - bool AssertionResult::hasMessage() const { - return !m_resultData.message.empty(); - } - - std::string AssertionResult::getExpression() const { - if( isFalseTest( m_info.resultDisposition ) ) - return "!(" + std::string(m_info.capturedExpression) + ")"; - else - return m_info.capturedExpression; - } - - std::string AssertionResult::getExpressionInMacro() const { - std::string expr; - if( m_info.macroName[0] == 0 ) - expr = 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 += " )"; - } - return expr; - } - - 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; - } - - std::string AssertionResult::getTestMacroName() const { - return m_info.macroName; - } - -} // end namespace Catch -// end catch_assertionresult.cpp -// start catch_benchmark.cpp - -namespace Catch { - - auto BenchmarkLooper::getResolution() -> uint64_t { - return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); - } - - void BenchmarkLooper::reportStart() { - getResultCapture().benchmarkStarting( { m_name } ); - } - auto BenchmarkLooper::needsMoreIterations() -> bool { - auto elapsed = m_timer.getElapsedNanoseconds(); - - // Exponentially increasing iterations until we're confident in our timer resolution - if( elapsed < m_resolution ) { - m_iterationsToRun *= 10; - return true; - } - - getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } ); - return false; - } - -} // end namespace Catch -// end catch_benchmark.cpp -// start catch_capture_matchers.cpp - -namespace Catch { - - using StringMatcher = Matchers::Impl::MatcherBase; - - // This is the general overload that takes a any string matcher - // There is another overload, in catch_assertinhandler.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 matcherString ) { - std::string exceptionMessage = Catch::translateActiveException(); - MatchExpr expr( exceptionMessage, matcher, matcherString ); - handler.handle( 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 -// v1.0-develop.2 -// See https://github.com/philsquared/Clara - - -#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 - -// ----------- #included from clara_textflow.hpp ----------- - -// TextFlowCpp -// -// A single-header library for wrapping and laying out basic text, by Phil Nash -// -// This work is licensed under the BSD 2-Clause license. -// See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause -// -// This project is hosted at https://github.com/philsquared/textflowcpp - - -#include -#include -#include -#include - -#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 -#endif - -namespace Catch { namespace clara { namespace TextFlow { - - 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; - } - - class Columns; - - class Column { - std::vector m_strings; - size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; - size_t m_indent = 0; - size_t m_initialIndent = std::string::npos; - - 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; - 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: - 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 ); - if( m_pos + m_column.m_width < m_end ) - return addIndentAndSuffix(line().substr(m_pos, m_len)); - else - return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos)); - } - - 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(); - } - }; - - class Spacer : public Column { - - public: - explicit Spacer( size_t spaceWidth ) : Column( "" ) { - width( spaceWidth ); - } - }; - - class Columns { - std::vector m_columns; - - public: - - class iterator { - friend Columns; - struct EndTag {}; - - std::vector const& m_columns; - std::vector 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: - 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; - } -}}} // namespace Catch::clara::TextFlow - -// ----------- end of #include from clara_textflow.hpp ----------- -// ........... back in clara.hpp - -#include -#include -#include - -#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 - struct UnaryLambdaTraits : UnaryLambdaTraits {}; - - template - struct UnaryLambdaTraits { - static const bool isValid = false; - }; - - template - struct UnaryLambdaTraits { - static const bool isValid = true; - using ArgType = typename std::remove_const::type>::type;; - using ReturnType = ReturnT; - }; - - class TokenStream; - - // 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 m_args; - - public: - Args( int argc, char *argv[] ) { - m_exeName = argv[0]; - for( int i = 1; i < argc; ++i ) - m_args.push_back( argv[i] ); - } - - Args( std::initializer_list args ) - : m_exeName( *args.begin() ), - m_args( args.begin()+1, args.end() ) - {} - - auto exeName() const -> std::string { - return m_exeName; - } - }; - - // 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; - }; - - inline auto isOptPrefix( char c ) -> bool { - return c == '-' -#ifdef CATCH_PLATFORM_WINDOWS - || c == '/' -#endif - ; - } - - // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled - class TokenStream { - using Iterator = std::vector::const_iterator; - Iterator it; - Iterator itEnd; - std::vector 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 } ); - } - } - } - - 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(); - } - - explicit operator bool() const { - return !m_tokenBuffer.empty() || it != itEnd; - } - - auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } - - auto operator*() const -> Token { - assert( !m_tokenBuffer.empty() ); - return m_tokenBuffer.front(); - } - - auto operator->() const -> Token const * { - assert( !m_tokenBuffer.empty() ); - return &m_tokenBuffer.front(); - } - - auto operator++() -> TokenStream & { - if( m_tokenBuffer.size() >= 2 ) { - m_tokenBuffer.erase( m_tokenBuffer.begin() ); - } else { - if( it != itEnd ) - ++it; - loadBuffer(); - } - return *this; - } - }; - - class ResultBase { - public: - enum Type { - Ok, LogicError, RuntimeError - }; - - protected: - ResultBase( Type type ) : m_type( type ) {} - virtual ~ResultBase() = default; - - virtual void enforceOk() const = 0; - - Type m_type; - }; - - template - 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() { - if( m_type == Ok ) - m_value.~T(); - } - - union { - T m_value; - }; - }; - - template<> - class ResultValueBase : public ResultBase { - protected: - using ResultBase::ResultBase; - }; - - template - class BasicResult : public ResultValueBase { - public: - template - explicit BasicResult( BasicResult const &other ) - : ResultValueBase( other.type() ), - m_errorMessage( other.errorMessage() ) - { - assert( type() != ResultBase::Ok ); - } - - template - 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: - virtual void enforceOk() const { - // !TBD: If no exceptions, std::terminate here or something - switch( m_type ) { - case ResultBase::LogicError: - throw std::logic_error( m_errorMessage ); - case ResultBase::RuntimeError: - throw std::runtime_error( m_errorMessage ); - case ResultBase::Ok: - break; - } - } - - std::string m_errorMessage; // Only populated if resultType is an error - - BasicResult( ResultBase::Type type, std::string const &message ) - : ResultValueBase(type), - m_errorMessage(message) - { - assert( m_type != ResultBase::Ok ); - } - - using ResultValueBase::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; - using ParserResult = BasicResult; - using InternalParseResult = BasicResult; - - struct HelpColumns { - std::string left; - std::string right; - }; - - template - 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(), []( char c ) { return static_cast( ::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 ); - } - - struct BoundRefBase { - BoundRefBase() = default; - BoundRefBase( BoundRefBase const & ) = delete; - BoundRefBase( BoundRefBase && ) = delete; - BoundRefBase &operator=( BoundRefBase const & ) = delete; - BoundRefBase &operator=( BoundRefBase && ) = delete; - - virtual ~BoundRefBase() = default; - - virtual auto isFlag() const -> bool = 0; - virtual auto isContainer() const -> bool { return false; } - virtual auto setValue( std::string const &arg ) -> ParserResult = 0; - virtual auto setFlag( bool flag ) -> ParserResult = 0; - }; - - struct BoundValueRefBase : BoundRefBase { - auto isFlag() const -> bool override { return false; } - - auto setFlag( bool ) -> ParserResult override { - return ParserResult::logicError( "Flags can only be set on boolean fields" ); - } - }; - - struct BoundFlagRefBase : BoundRefBase { - auto isFlag() const -> bool override { return true; } - - auto setValue( std::string const &arg ) -> ParserResult override { - bool flag; - auto result = convertInto( arg, flag ); - if( result ) - setFlag( flag ); - return result; - } - }; - - template - struct BoundRef : BoundValueRefBase { - T &m_ref; - - explicit BoundRef( T &ref ) : m_ref( ref ) {} - - auto setValue( std::string const &arg ) -> ParserResult override { - return convertInto( arg, m_ref ); - } - }; - - template - struct BoundRef> : BoundValueRefBase { - std::vector &m_ref; - - explicit BoundRef( std::vector &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; - } - }; - - 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 ); - } - }; - - template - struct LambdaInvoker { - static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" ); - - template - static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { - return lambda( arg ); - } - }; - - template<> - struct LambdaInvoker { - template - static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { - lambda( arg ); - return ParserResult::ok( ParseResultType::Matched ); - } - }; - - template - inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { - ArgType temp; - auto result = convertInto( arg, temp ); - return !result - ? result - : LambdaInvoker::ReturnType>::invoke( lambda, temp ); - }; - - template - struct BoundLambda : BoundValueRefBase { - L m_lambda; - - static_assert( UnaryLambdaTraits::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::ArgType>( m_lambda, arg ); - } - }; - - template - struct BoundFlagLambda : BoundFlagRefBase { - L m_lambda; - - static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); - static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); - - explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} - - auto setFlag( bool flag ) -> ParserResult override { - return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); - } - }; - - 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 ) ); - } - }; - - template - class ComposableParserImpl : public ParserBase { - public: - template - auto operator|( T const &other ) const -> Parser; - }; - - // Common code and state for Args and Opts - template - class ParserRefImpl : public ComposableParserImpl { - protected: - Optionality m_optionality = Optionality::Optional; - std::shared_ptr m_ref; - std::string m_hint; - std::string m_description; - - explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} - - public: - template - ParserRefImpl( T &ref, std::string const &hint ) - : m_ref( std::make_shared>( ref ) ), - m_hint( hint ) - {} - - template - ParserRefImpl( LambdaT const &ref, std::string const &hint ) - : m_ref( std::make_shared>( ref ) ), - m_hint(hint) - {} - - auto operator()( std::string const &description ) -> DerivedT & { - m_description = description; - return static_cast( *this ); - } - - auto optional() -> DerivedT & { - m_optionality = Optionality::Optional; - return static_cast( *this ); - }; - - auto required() -> DerivedT & { - m_optionality = Optionality::Required; - return static_cast( *this ); - }; - - auto isOptional() const -> bool { - return m_optionality == Optionality::Optional; - } - - auto cardinality() const -> size_t override { - if( m_ref->isContainer() ) - return 0; - else - return 1; - } - - auto hint() const -> std::string { return m_hint; } - }; - - class ExeName : public ComposableParserImpl { - std::shared_ptr m_name; - std::shared_ptr m_ref; - - template - static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { - return std::make_shared>( lambda) ; - } - - public: - ExeName() : m_name( std::make_shared( "" ) ) {} - - explicit ExeName( std::string &ref ) : ExeName() { - m_ref = std::make_shared>( ref ); - } - - template - explicit ExeName( LambdaT const& lambda ) : ExeName() { - m_ref = std::make_shared>( lambda ); - } - - // 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 ) ); - } - - 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 Arg : public ParserRefImpl { - public: - using ParserRefImpl::ParserRefImpl; - - 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 ) ); - - auto result = m_ref->setValue( remainingTokens->token ); - if( !result ) - return InternalParseResult( result ); - else - return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); - } - }; - - inline auto normaliseOpt( std::string const &optName ) -> std::string { -#ifdef CATCH_PLATFORM_WINDOWS - if( optName[0] == '/' ) - return "-" + optName.substr( 1 ); - else -#endif - return optName; - } - - class Opt : public ParserRefImpl { - protected: - std::vector m_optNames; - - public: - template - explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} - - explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} - - template - Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} - - template - 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 { - std::ostringstream oss; - bool first = true; - for( auto const &opt : m_optNames ) { - if (first) - first = false; - else - oss << ", "; - oss << opt; - } - 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; - } - return false; - } - - 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 result = m_ref->setFlag( true ); - if( !result ) - return InternalParseResult( result ); - if( result.value() == ParseResultType::ShortCircuitAll ) - return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); - } else { - ++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 = m_ref->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 ) ); - } - - 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(); - } - }; - - struct Help : Opt { - Help( bool &showHelpFlag ) - : Opt([&]( bool flag ) { - showHelpFlag = flag; - return ParserResult::ok( ParseResultType::ShortCircuitAll ); - }) - { - static_cast( *this ) - ("display usage information") - ["-?"]["-h"]["--help"] - .optional(); - } - }; - - struct Parser : ParserBase { - - mutable ExeName m_exeName; - std::vector m_options; - std::vector m_args; - - auto operator|=( ExeName const &exeName ) -> Parser & { - m_exeName = exeName; - return *this; - } - - 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 - auto operator|( T const &other ) const -> Parser { - return Parser( *this ) |= other; - } - - auto getHelpColumns() const -> std::vector { - std::vector cols; - for (auto const &o : m_options) { - auto childCols = o.getHelpColumns(); - cols.insert( cols.end(), childCols.begin(), childCols.end() ); - } - return cols; - } - - 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; - } - - 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); - - 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; - } - } - - friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { - parser.writeToStream( os ); - return os; - } - - 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(); - } - - 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; - } - }; - - template - template - auto ComposableParserImpl::operator|( T const &other ) const -> Parser { - return Parser() | static_cast( *this ) | other; - } -} // namespace detail - -// A Combined parser -using detail::Parser; - -// A parser for options -using detail::Opt; - -// 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 -#include - -namespace Catch { - - clara::Parser makeCommandLineParser( ConfigData& config ) { - - using namespace clara; - - auto const setWarning = [&]( std::string const& warning ) { - if( warning != "NoAssertions" ) - return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); - config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); - 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 + ',' ); - } - } - 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( 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 == "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: 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 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( config.reporterNames, "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( 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, "start|exit|both" ) - ["--wait-for-keypress"] - ( "waits for a keypress before exiting" ) - | Opt( config.benchmarkResolutionMultiple, "multiplier" ) - ["--benchmark-resolution-multiple"] - ( "multiple of clock resolution to run benchmarks" ) - - | 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 - -#include -#include - -namespace Catch { - - SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) noexcept - : file( _file ), - line( _line ) - {} - bool SourceLineInfo::empty() const noexcept { - return file[0] == '\0'; - } - 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 { - return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); - } - - 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; - } - - bool isTrue( bool value ){ return value; } - bool alwaysTrue() { return true; } - bool alwaysFalse() { return false; } - - 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() ) - { - if( !data.testsOrTags.empty() ) { - TestSpecParser parser( ITagAliasRegistry::get() ); - for( auto const& testOrTags : 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::vector const& Config::getReporterNames() const { return m_data.reporterNames; } - std::vector const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } - - TestSpec const& Config::testSpec() const { return m_testSpec; } - - 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; } - ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } - RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } - unsigned int Config::rngSeed() const { return m_data.rngSeed; } - int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } - 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; } - - IStream const* Config::openStream() { - if( m_data.outputFilename.empty() ) - return new CoutStream(); - else if( m_data.outputFilename[0] == '%' ) { - if( m_data.outputFilename == "%debug" ) - return new DebugOutStream(); - else - CATCH_ERROR( "Unrecognised stream: '" << m_data.outputFilename << "'" ); - } - else - return new FileStream( 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; - }; - -} - -// end catch_errno_guard.h -// start catch_windows_h_proxy.h - - -#if defined(CATCH_PLATFORM_WINDOWS) - -#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) -# define CATCH_DEFINED_NOMINMAX -# define NOMINMAX -#endif -#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 - -#ifdef __AFXDLL -#include -#else -#include -#endif - -#ifdef CATCH_DEFINED_NOMINMAX -# undef NOMINMAX -#endif -#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# undef WIN32_LEAN_AND_MEAN -#endif - -#endif // defined(CATCH_PLATFORM_WINDOWS) - -// end catch_windows_h_proxy.h -namespace Catch { - namespace { - - struct IColourImpl { - virtual ~IColourImpl() = default; - virtual void use( Colour::Code _colourCode ) = 0; - }; - - struct NoColourImpl : IColourImpl { - void use( Colour::Code ) {} - - 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 { - - 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 ); - } - - virtual 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 ); - - 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::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); - } - } - - 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 ) ////////////////////////////////////// - -#include - -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: - virtual 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::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); - } - } - static IColourImpl* instance() { - static PosixColourImpl s_instance; - return &s_instance; - } - - private: - void setColour( const char* _escapeCode ) { - Catch::cout() << '\033' << _escapeCode; - } - }; - - bool useColourOnPlatform() { - return -#ifdef CATCH_PLATFORM_MAC - !isDebuggerActive() && -#endif - isatty(STDOUT_FILENO); - } - 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 - -#else // not Windows or ANSI /////////////////////////////////////////////// - -namespace Catch { - - static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } - -} // end namespace Catch - -#endif // Windows/ ANSI/ None - -namespace Catch { - - Colour::Colour( Code _colourCode ) { use( _colourCode ); } - Colour::Colour( Colour&& rhs ) noexcept { - m_moved = rhs.m_moved; - rhs.m_moved = true; - } - Colour& Colour::operator=( Colour&& rhs ) noexcept { - m_moved = rhs.m_moved; - rhs.m_moved = true; - return *this; - } - - Colour::~Colour(){ if( !m_moved ) use( None ); } - - void Colour::use( Code _colourCode ) { - static IColourImpl* impl = platformColourInstance(); - 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 { - - class Context : public IMutableContext, NonCopyable { - - public: // IContext - virtual IResultCapture* getResultCapture() override { - return m_resultCapture; - } - virtual IRunner* getRunner() override { - return m_runner; - } - - virtual IConfigPtr getConfig() const override { - return m_config; - } - - virtual ~Context() override; - - public: // IMutableContext - virtual void setResultCapture( IResultCapture* resultCapture ) override { - m_resultCapture = resultCapture; - } - virtual void setRunner( IRunner* runner ) override { - m_runner = runner; - } - virtual void setConfig( IConfigPtr const& config ) override { - m_config = config; - } - - friend IMutableContext& getCurrentMutableContext(); - - private: - IConfigPtr m_config; - IRunner* m_runner = nullptr; - IResultCapture* m_resultCapture = nullptr; - }; - - namespace { - Context* currentContext = nullptr; - } - IMutableContext& getCurrentMutableContext() { - if( !currentContext ) - currentContext = new Context(); - return *currentContext; - } - IContext& getCurrentContext() { - return getCurrentMutableContext(); - } - - void cleanUpContext() { - delete currentContext; - currentContext = nullptr; - } - IContext::~IContext() = default; - IMutableContext::~IMutableContext() = default; - Context::~Context() = default; -} -// end catch_context.cpp -// start catch_debug_console.cpp - -// start catch_debug_console.h - -#include - -namespace Catch { - void writeToDebugConsole( std::string const& text ); -} - -// end catch_debug_console.h -#ifdef CATCH_PLATFORM_WINDOWS - - namespace Catch { - void writeToDebugConsole( std::string const& text ) { - ::OutputDebugStringA( text.c_str() ); - } - } -#else - namespace Catch { - void writeToDebugConsole( std::string const& text ) { - // !TBD: Need a version for Mac/ XCode and other IDEs - Catch::cout() << text; - } - } -#endif // Platform -// end catch_debug_console.cpp -// start catch_debugger.cpp - -#ifdef CATCH_PLATFORM_MAC - - #include - #include - #include - #include - #include - - namespace Catch { - - // The following function is taken directly from the following technical note: - // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.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; - - // 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 ); - } - } // namespace Catch - -#elif defined(CATCH_PLATFORM_LINUX) - #include - #include - - 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'; - } - } - - return false; - } - } // namespace Catch -#elif defined(_MSC_VER) - extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); - namespace Catch { - bool isDebuggerActive() { - return IsDebuggerPresent() != 0; - } - } -#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 - -namespace Catch { - - ITransientExpression::~ITransientExpression() = default; - - 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_errno_guard.cpp - -#include - -namespace Catch { - ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} - ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } -} -// end catch_errno_guard.cpp -// start catch_exception_translator_registry.cpp - -// start catch_exception_translator_registry.h - -#include -#include -#include - -namespace Catch { - - class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { - public: - ~ExceptionTranslatorRegistry(); - virtual void registerTranslator( const IExceptionTranslator* translator ); - virtual std::string translateActiveException() const override; - std::string tryTranslators() const; - - private: - std::vector> 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( translator ) ); - } - - 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 - return tryTranslators(); -#endif - } - 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() ); - } -} -// end catch_exception_translator_registry.cpp -// start catch_fatal_condition.cpp - -// start catch_fatal_condition.h - -#include - -#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// - -# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) - -namespace Catch { - struct FatalConditionHandler { - void reset(); - }; -} - -# else // CATCH_CONFIG_WINDOWS_SEH is defined - -namespace Catch { - - struct FatalConditionHandler { - - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); - FatalConditionHandler(); - static void reset(); - ~FatalConditionHandler(); - - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; - -} // namespace Catch - -# endif // CATCH_CONFIG_WINDOWS_SEH - -#else // Not Windows - assumed to be POSIX compatible ////////////////////////// - -# if !defined(CATCH_CONFIG_POSIX_SIGNALS) - -namespace Catch { - struct FatalConditionHandler { - void reset(); - }; -} - -# else // CATCH_CONFIG_POSIX_SIGNALS is defined - -#include - -namespace Catch { - - struct FatalConditionHandler { - - static bool isSet; - static struct sigaction oldSigActions[];// [sizeof(signalDefs) / sizeof(SignalDefs)]; - static stack_t oldSigStack; - static char altStackMem[]; - - static void handleSignal( int sig ); - - FatalConditionHandler(); - ~FatalConditionHandler(); - static void reset(); - }; - -} // namespace Catch - -# endif // CATCH_CONFIG_POSIX_SIGNALS - -#endif // not Windows - -// end catch_fatal_condition.h -namespace { - // Report the error condition - void reportFatal( char const * const message ) { - Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); - } -} - -#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// - -# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) - -namespace Catch { - void FatalConditionHandler::reset() {} -} - -# else // CATCH_CONFIG_WINDOWS_SEH is defined - -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[] = { - { 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" }, - }; - - LONG CALLBACK FatalConditionHandler::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; - } - - FatalConditionHandler::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 = nullptr; - // Register as first handler in current chain - exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); - } - - void FatalConditionHandler::reset() { - if (isSet) { - // Unregister handler and restore the old guarantee - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = nullptr; - isSet = false; - } - } - - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } - -bool FatalConditionHandler::isSet = false; -ULONG FatalConditionHandler::guaranteeSize = 0; -PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; - -} // namespace Catch - -# endif // CATCH_CONFIG_WINDOWS_SEH - -#else // Not Windows - assumed to be POSIX compatible ////////////////////////// - -# if !defined(CATCH_CONFIG_POSIX_SIGNALS) - -namespace Catch { - void FatalConditionHandler::reset() {} -} - -# else // CATCH_CONFIG_POSIX_SIGNALS is defined - -#include - -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" } - }; - - void FatalConditionHandler::handleSignal( int sig ) { - char const * name = ""; - for (auto const& def : signalDefs) { - if (sig == def.id) { - name = def.name; - break; - } - } - reset(); - reportFatal(name); - raise( sig ); - } - - FatalConditionHandler::FatalConditionHandler() { - isSet = true; - stack_t sigStack; - sigStack.ss_sp = altStackMem; - sigStack.ss_size = SIGSTKSZ; - sigStack.ss_flags = 0; - sigaltstack(&sigStack, &oldSigStack); - struct sigaction sa = { }; - - sa.sa_handler = handleSignal; - sa.sa_flags = SA_ONSTACK; - for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { - sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); - } - } - - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } - - void FatalConditionHandler::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], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - isSet = false; - } - } - - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; - -} // namespace Catch - -# endif // CATCH_CONFIG_POSIX_SIGNALS - -#endif // not Windows -// end catch_fatal_condition.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_multi.h - -namespace Catch { - - class MultipleReporters : public IStreamingReporter { - using Reporters = std::vector; - Reporters m_reporters; - - public: - void add( IStreamingReporterPtr&& reporter ); - - public: // IStreamingReporter - - ReporterPreferences getPreferences() const override; - - void noMatchingTestCases( std::string const& spec ) override; - - static std::set getSupportedVerbosities(); - - void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; - void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override; - - 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_multi.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 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 ); - } - } - - AssertionStats::~AssertionStats() = default; - - SectionStats::SectionStats( SectionInfo const& _sectionInfo, - Counts const& _assertions, - double _durationInSeconds, - bool _missingAssertions ) - : sectionInfo( _sectionInfo ), - assertions( _assertions ), - durationInSeconds( _durationInSeconds ), - missingAssertions( _missingAssertions ) - {} - - 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 ) - {} - - 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; - - void addReporter( IStreamingReporterPtr& existingReporter, IStreamingReporterPtr&& additionalReporter ) { - - if( !existingReporter ) { - existingReporter = std::move( additionalReporter ); - return; - } - - MultipleReporters* multi = nullptr; - - if( existingReporter->isMulti() ) { - multi = static_cast( existingReporter.get() ); - } - else { - auto newMulti = std::unique_ptr( new MultipleReporters ); - newMulti->add( std::move( existingReporter ) ); - multi = newMulti.get(); - existingReporter = std::move( newMulti ); - } - multi->add( std::move( additionalReporter ) ); - } - -} // 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 - -namespace Catch { - -#ifdef CATCH_CONFIG_WINDOWS_CRTDBG -#include - - 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 - - LeakDetector::LeakDetector(){} - -#endif - -} -// end catch_leak_detector.cpp -// start catch_list.cpp - -// start catch_list.h - -#include - -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 spellings; - std::size_t count = 0; - }; - - std::size_t listTags( Config const& config ); - - std::size_t listReporters( Config const& /*config*/ ); - - Option list( 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 -#include -#include - -namespace Catch { - - std::size_t listTests( Config const& config ) { - TestSpec testSpec = config.testSpec(); - if( config.testSpec().hasFilters() ) - Catch::cout() << "Matching test cases:\n"; - else { - Catch::cout() << "All available test cases:\n"; - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); - } - - 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; - } - if( !testCaseInfo.tags.empty() ) - Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n"; - } - - if( !config.testSpec().hasFilters() ) - 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(); - } - - 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 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 { - std::string out; - for( auto const& spelling : spellings ) - out += "[" + spelling + "]"; - return out; - } - - 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(); - } - - std::map tagCounts; - - std::vector 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 ); - } - } - - for( auto const& tagCount : tagCounts ) { - std::ostringstream oss; - oss << " " << std::setw(2) << tagCount.second.count << " "; - auto wrapper = Column( tagCount.second.all() ) - .initialIndent( 0 ) - .indent( oss.str().size() ) - .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); - Catch::cout() << oss.str() << wrapper << '\n'; - } - Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; - return tagCounts.size(); - } - - std::size_t listReporters( Config const& /*config*/ ) { - 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() ); - - 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(); - } - - Option list( Config const& config ) { - Option listedCount; - 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( config ); - 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; - } - - MatcherUntypedBase::~MatcherUntypedBase() = default; - - } // namespace Impl -} // namespace Matchers - -using namespace Matchers; -using Matchers::Impl::MatcherBase; - -} // namespace Catch -// end catch_matchers.cpp -// start catch_matchers_string.cpp - -namespace Catch { -namespace Matchers { - - namespace StdString { - - 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; - } - std::string CasedString::caseSensitivitySuffix() const { - return m_caseSensitivity == CaseSensitive::No - ? " (case insensitive)" - : std::string(); - } - - StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) - : m_comparator( comparator ), - m_operation( operation ) { - } - - 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; - } - - 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; - } - - ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} - - bool ContainsMatcher::match( std::string const& source ) const { - return contains( m_comparator.adjustString( source ), m_comparator.m_str ); - } - - StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} - - bool StartsWithMatcher::match( std::string const& source ) const { - return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); - } - - EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} - - bool EndsWithMatcher::match( std::string const& source ) const { - return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); - } - - } // 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) ); - } - -} // namespace Matchers -} // namespace Catch -// end catch_matchers_string.cpp -// start catch_message.cpp - -namespace Catch { - - MessageInfo::MessageInfo( std::string 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( std::string const& macroName, - SourceLineInfo const& lineInfo, - ResultWas::OfType type ) - :m_info(macroName, lineInfo, type) {} - - //////////////////////////////////////////////////////////////////////////// - - ScopedMessage::ScopedMessage( MessageBuilder const& builder ) - : m_info( builder.m_info ) - { - m_info.message = builder.m_stream.str(); - getResultCapture().pushScopedMessage( m_info ); - } - - ScopedMessage::~ScopedMessage() { - if ( !std::uncaught_exception() ){ - getResultCapture().popScopedMessage(m_info); - } - } - -} // end namespace Catch -// end catch_message.cpp -// start catch_random_number_generator.cpp - -// start catch_random_number_generator.h - -#include - -namespace Catch { - - struct IConfig; - - void seedRng( IConfig const& config ); - - unsigned int rngSeed(); - - struct RandomNumberGenerator { - using result_type = unsigned int; - - static constexpr result_type (min)() { return 0; } - static constexpr result_type (max)() { return 1000000; } - - result_type operator()( result_type n ) const; - result_type operator()() const; - - template - static void shuffle( V& vector ) { - RandomNumberGenerator rng; - std::shuffle( vector.begin(), vector.end(), rng ); - } - }; - -} - -// end catch_random_number_generator.h -#include - -namespace Catch { - - void seedRng( IConfig const& config ) { - if( config.rngSeed() != 0 ) - std::srand( config.rngSeed() ); - } - unsigned int rngSeed() { - return getCurrentContext().getConfig()->rngSeed(); - } - - RandomNumberGenerator::result_type RandomNumberGenerator::operator()( result_type n ) const { - return std::rand() % n; - } - RandomNumberGenerator::result_type RandomNumberGenerator::operator()() const { - return std::rand() % (max)(); - } - -} -// end catch_random_number_generator.cpp -// start catch_registry_hub.cpp - -// start catch_test_case_registry_impl.h - -#include -#include -#include -#include - -namespace Catch { - - class TestCase; - struct IConfig; - - std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ); - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - - void enforceNoDuplicateTestCases( std::vector const& functions ); - - std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector const& getAllTestCasesSorted( IConfig const& config ); - - class TestRegistry : public ITestCaseRegistry { - public: - virtual ~TestRegistry() = default; - - virtual void registerTest( TestCase const& testCase ); - - std::vector const& getAllTests() const override; - std::vector const& getAllTestsSorted( IConfig const& config ) const override; - - private: - std::vector m_functions; - mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; - mutable std::vector m_sortedFunctions; - std::size_t m_unnamedCount = 0; - std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised - }; - - /////////////////////////////////////////////////////////////////////////// - - class TestInvokerAsFunction : public ITestInvoker { - void(*m_testAsFunction)(); - public: - TestInvokerAsFunction( void(*testAsFunction)() ) noexcept; - - void invoke() const override; - }; - - std::string extractClassName( std::string const& classOrQualifiedMethodName ); - - /////////////////////////////////////////////////////////////////////////// - -} // end namespace Catch - -// end catch_test_case_registry_impl.h -// start catch_reporter_registry.h - -#include - -namespace Catch { - - class ReporterRegistry : public IReporterRegistry { - - public: - - ~ReporterRegistry() override; - - IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override; - - void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ); - void registerListener( IReporterFactoryPtr const& factory ); - - FactoryMap const& getFactories() const override; - Listeners const& getListeners() const override; - - private: - FactoryMap m_factories; - Listeners m_listeners; - }; -} - -// end catch_reporter_registry.h -// start catch_tag_alias_registry.h - -// start catch_tag_alias.h - -#include - -namespace Catch { - - struct TagAlias { - TagAlias(std::string const& _tag, SourceLineInfo _lineInfo); - - std::string tag; - SourceLineInfo lineInfo; - }; - -} // end namespace Catch - -// end catch_tag_alias.h -#include - -namespace Catch { - - class TagAliasRegistry : public ITagAliasRegistry { - public: - ~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: - std::map m_registry; - }; - -} // end namespace Catch - -// end catch_tag_alias_registry.h -// start catch_startup_exception_registry.h - -#include -#include - -namespace Catch { - - class StartupExceptionRegistry { - public: - void add(std::exception_ptr const& exception) noexcept; - std::vector const& getExceptions() const noexcept; - private: - std::vector m_exceptions; - }; - -} // end namespace Catch - -// end catch_startup_exception_registry.h -namespace Catch { - - namespace { - - class RegistryHub : public IRegistryHub, public IMutableRegistryHub, - private NonCopyable { - - public: // IRegistryHub - RegistryHub() = default; - IReporterRegistry const& getReporterRegistry() const override { - return m_reporterRegistry; - } - ITestCaseRegistry const& getTestCaseRegistry() const override { - return m_testCaseRegistry; - } - IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() override { - return m_exceptionTranslatorRegistry; - } - ITagAliasRegistry const& getTagAliasRegistry() const override { - return m_tagAliasRegistry; - } - StartupExceptionRegistry const& getStartupExceptionRegistry() const override { - return m_exceptionRegistry; - } - - public: // IMutableRegistryHub - void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { - m_reporterRegistry.registerReporter( name, factory ); - } - void registerListener( IReporterFactoryPtr const& factory ) override { - m_reporterRegistry.registerListener( factory ); - } - void registerTest( TestCase const& testInfo ) override { - m_testCaseRegistry.registerTest( testInfo ); - } - void registerTranslator( const IExceptionTranslator* translator ) override { - m_exceptionTranslatorRegistry.registerTranslator( translator ); - } - void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { - m_tagAliasRegistry.add( alias, tag, lineInfo ); - } - void registerStartupException() noexcept override { - m_exceptionRegistry.add(std::current_exception()); - } - - private: - TestRegistry m_testCaseRegistry; - ReporterRegistry m_reporterRegistry; - ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; - TagAliasRegistry m_tagAliasRegistry; - StartupExceptionRegistry m_exceptionRegistry; - }; - - // Single, global, instance - RegistryHub*& getTheRegistryHub() { - static RegistryHub* theRegistryHub = nullptr; - if( !theRegistryHub ) - theRegistryHub = new RegistryHub(); - return theRegistryHub; - } - } - - IRegistryHub& getRegistryHub() { - return *getTheRegistryHub(); - } - IMutableRegistryHub& getMutableRegistryHub() { - return *getTheRegistryHub(); - } - void cleanUp() { - delete getTheRegistryHub(); - getTheRegistryHub() = nullptr; - cleanUpContext(); - } - std::string translateActiveException() { - return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); - } - -} // end namespace Catch -// end catch_registry_hub.cpp -// start catch_reporter_registry.cpp - -namespace Catch { - - 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 { - - bool isOk( ResultWas::OfType resultType ) { - return ( resultType & ResultWas::FailureBit ) == 0; - } - bool isJustInfo( int flags ) { - return flags == ResultWas::Info; - } - - ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { - return static_cast( static_cast( lhs ) | static_cast( rhs ) ); - } - - bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } - bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } - bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } - -} // end namespace Catch -// end catch_result_type.cpp -// start catch_run_context.cpp -// start catch_run_context.h - -#include - -namespace Catch { - - struct IMutableContext; - - class StreamRedirect { - - public: - StreamRedirect(std::ostream& stream, std::string& targetString); - - ~StreamRedirect(); - - private: - std::ostream& m_stream; - std::streambuf* m_prevBuf; - std::ostringstream m_oss; - std::string& m_targetString; - }; - - // 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); - ~StdErrRedirect(); - private: - std::streambuf* m_cerrBuf; - std::streambuf* m_clogBuf; - std::ostringstream m_oss; - std::string& m_targetString; - }; - - /////////////////////////////////////////////////////////////////////////// - - class RunContext : public IResultCapture, public IRunner { - - public: - RunContext( RunContext const& ) = delete; - RunContext& operator =( RunContext const& ) = delete; - - explicit RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter); - - virtual ~RunContext(); - - 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); - - Totals runTest(TestCase const& testCase); - - IConfigPtr config() const; - IStreamingReporter& reporter() const; - - private: // IResultCapture - - void assertionStarting(AssertionInfo const& info) override; - void assertionEnded(AssertionResult const& result) override; - - bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; - bool testForMissingAssertions(Counts& assertions); - - void sectionEnded(SectionEndInfo const& endInfo) override; - void sectionEndedEarly(SectionEndInfo const& endInfo) override; - - void benchmarkStarting( BenchmarkInfo const& info ) override; - void benchmarkEnded( BenchmarkStats const& stats ) override; - - void pushScopedMessage(MessageInfo const& message) override; - void popScopedMessage(MessageInfo const& message) override; - - std::string getCurrentTestName() const override; - - const AssertionResult* getLastResult() const override; - - void exceptionEarlyReported() override; - - void handleFatalErrorCondition( StringRef message ) override; - - bool lastAssertionPassed() override; - - void assertionPassed() override; - - void assertionRun() override; - - public: - // !TBD We need to do this another way! - bool aborting() const override; - - private: - - void runCurrentTest(std::string& redirectedCout, std::string& redirectedCerr); - void invokeActiveTestCase(); - - private: - - void handleUnfinishedSections(); - - TestRunInfo m_runInfo; - IMutableContext& m_context; - TestCase const* m_activeTestCase = nullptr; - ITracker* m_testCaseTracker; - Option m_lastResult; - - IConfigPtr m_config; - Totals m_totals; - IStreamingReporterPtr m_reporter; - std::vector m_messages; - AssertionInfo m_lastAssertionInfo; - std::vector m_unfinishedSections; - std::vector m_activeSections; - TrackerContext m_trackerContext; - std::size_t m_prevPassed = 0; - bool m_shouldReportUnexpected = true; - }; - - IResultCapture& getResultCapture(); - -} // end namespace Catch - -// end catch_run_context.h - -#include -#include - -namespace Catch { - - StreamRedirect::StreamRedirect(std::ostream& stream, std::string& targetString) - : m_stream(stream), - m_prevBuf(stream.rdbuf()), - m_targetString(targetString) { - stream.rdbuf(m_oss.rdbuf()); - } - - StreamRedirect::~StreamRedirect() { - m_targetString += m_oss.str(); - m_stream.rdbuf(m_prevBuf); - } - - StdErrRedirect::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::~StdErrRedirect() { - m_targetString += m_oss.str(); - cerr().rdbuf(m_cerrBuf); - clog().rdbuf(m_clogBuf); - } - - RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) - : m_runInfo(_config->name()), - m_context(getCurrentMutableContext()), - m_config(_config), - m_reporter(std::move(reporter)), - m_lastAssertionInfo{ "", SourceLineInfo("",0), "", ResultDisposition::Normal } - { - 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; - - TestCaseInfo testInfo = testCase.getTestCaseInfo(); - - m_reporter->testCaseStarting(testInfo); - - m_activeTestCase = &testCase; - - ITracker& rootTracker = m_trackerContext.startRun(); - assert(rootTracker.isSectionTracker()); - static_cast(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; - - return deltaTotals; - } - - IConfigPtr RunContext::config() const { - return m_config; - } - - IStreamingReporter& RunContext::reporter() const { - return *m_reporter; - } - - void RunContext::assertionStarting(AssertionInfo const& info) { - m_reporter->assertionStarting( info ); - } - void RunContext::assertionEnded(AssertionResult const & result) { - if (result.getResultType() == ResultWas::Ok) { - m_totals.assertions.passed++; - } else if (!result.isOk()) { - if( m_activeTestCase->getTestCaseInfo().okToFail() ) - m_totals.assertions.failedButOk++; - else - m_totals.assertions.failed++; - } - - // 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(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); - - // Reset working state - m_lastAssertionInfo = { "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}", m_lastAssertionInfo.resultDisposition }; - m_lastResult = result; - } - - 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; - } - - 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; - } - - void RunContext::sectionEnded(SectionEndInfo const & endInfo) { - Counts assertions = m_totals.assertions - endInfo.prevAssertions; - bool missingAssertions = testForMissingAssertions(assertions); - - if (!m_activeSections.empty()) { - m_activeSections.back()->close(); - m_activeSections.pop_back(); - } - - m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); - m_messages.clear(); - } - - 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); - } - void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { - m_reporter->benchmarkStarting( info ); - } - void RunContext::benchmarkEnded( BenchmarkStats const& stats ) { - m_reporter->benchmarkEnded( stats ); - } - - void RunContext::pushScopedMessage(MessageInfo const & message) { - m_messages.push_back(message); - } - - void RunContext::popScopedMessage(MessageInfo const & message) { - m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); - } - - std::string RunContext::getCurrentTestName() const { - return m_activeTestCase - ? m_activeTestCase->getTestCaseInfo().name - : std::string(); - } - - const AssertionResult * RunContext::getLastResult() const { - return &(*m_lastResult); - } - - void RunContext::exceptionEarlyReported() { - m_shouldReportUnexpected = false; - } - - void RunContext::handleFatalErrorCondition( StringRef message ) { - // First notify reporter that bad things happened - m_reporter->fatalErrorEncountered(message); - - // Don't rebuild the result -- the stringification itself can cause more fatal errors - // Instead, fake a result data. - AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); - tempResult.message = message; - AssertionResult result(m_lastAssertionInfo, tempResult); - - getResultCapture().assertionEnded(result); - - handleUnfinishedSections(); - - // 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, testCaseInfo.description); - - 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)); - } - - bool RunContext::lastAssertionPassed() { - return m_totals.assertions.passed == (m_prevPassed + 1); - } - - void RunContext::assertionPassed() { - ++m_totals.assertions.passed; - m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"; - m_lastAssertionInfo.macroName = ""; - } - - void RunContext::assertionRun() { - m_prevPassed = m_totals.assertions.passed; - } - - bool RunContext::aborting() const { - return m_totals.assertions.failed == static_cast(m_config->abortAfter()); - } - - void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { - auto 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 = { "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal }; - - seedRng(*m_config); - - Timer timer; - timer.start(); - if (m_reporter->getPreferences().shouldRedirectStdOut) { - StreamRedirect coutRedir(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) { - AssertionHandler - ( m_lastAssertionInfo.macroName, - m_lastAssertionInfo.lineInfo, - m_lastAssertionInfo.capturedExpression, - m_lastAssertionInfo.resultDisposition ).useActiveException(); - } - } - m_testCaseTracker->close(); - handleUnfinishedSections(); - m_messages.clear(); - - Counts assertions = m_totals.assertions - prevAssertions; - bool missingAssertions = testForMissingAssertions(assertions); - SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); - m_reporter->sectionEnded(testCaseSectionStats); - } - - void RunContext::invokeActiveTestCase() { - FatalConditionHandler fatalConditionHandler; // Handle signals - m_activeTestCase->invoke(); - fatalConditionHandler.reset(); - } - - 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(); - } - - IResultCapture& getResultCapture() { - if (auto* capture = getCurrentContext().getResultCapture()) - return *capture; - else - CATCH_INTERNAL_ERROR("No result capture instance"); - } -} -// end catch_run_context.cpp -// start catch_section.cpp - -namespace Catch { - - Section::Section( SectionInfo const& info ) - : m_info( info ), - m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) - { - m_timer.start(); - } - -#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 ); - else - getResultCapture().sectionEnded( endInfo ); - } - } -#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; - } - -} // end namespace Catch -// end catch_section.cpp -// start catch_section_info.cpp - -namespace Catch { - - SectionInfo::SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description ) - : name( _name ), - description( _description ), - lineInfo( _lineInfo ) - {} - - SectionEndInfo::SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) - : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) - {} - -} // end namespace Catch -// end catch_section_info.cpp -// start catch_session.cpp - -// start catch_session.h - -#include - -namespace Catch { - - class Session : NonCopyable { - public: - - Session(); - ~Session() override; - - void showHelp() const; - void libIdentify(); - - int applyCommandLine( int argc, char* argv[] ); - - void useConfigData( ConfigData const& configData ); - - int run( int argc, char* argv[] ); - #if defined(WIN32) && defined(UNICODE) - int run( int argc, wchar_t* const argv[] ); - #endif - int run(); - - clara::Parser const& cli() const; - void cli( clara::Parser const& newParser ); - ConfigData& configData(); - Config& config(); - private: - int runInternal(); - - clara::Parser m_cli; - ConfigData m_configData; - std::shared_ptr m_config; - bool m_startupExceptions = false; - }; - -} // end namespace Catch - -// end catch_session.h -// start catch_version.h - -#include - -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 -#include - -namespace { - const int MaxExitCode = 255; - using Catch::IStreamingReporterPtr; - using Catch::IConfigPtr; - using Catch::Config; - - 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 << "'"); - - return reporter; - } - -#ifndef CATCH_CONFIG_DEFAULT_REPORTER -#define CATCH_CONFIG_DEFAULT_REPORTER "console" -#endif - - IStreamingReporterPtr makeReporter(std::shared_ptr const& config) { - auto const& reporterNames = config->getReporterNames(); - if (reporterNames.empty()) - return createReporter(CATCH_CONFIG_DEFAULT_REPORTER, config); - - IStreamingReporterPtr reporter; - for (auto const& name : reporterNames) - addReporter(reporter, createReporter(name, config)); - return reporter; - } - -#undef CATCH_CONFIG_DEFAULT_REPORTER - - void addListeners(IStreamingReporterPtr& reporters, IConfigPtr const& config) { - auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); - for (auto const& listener : listeners) - addReporter(reporters, listener->create(Catch::ReporterConfig(config))); - } - - Catch::Totals runTests(std::shared_ptr const& config) { - using namespace Catch; - IStreamingReporterPtr reporter = makeReporter(config); - addListeners(reporter, config); - - RunContext context(config, std::move(reporter)); - - Totals totals; - - context.testGroupStarting(config->name(), 1, 1); - - TestSpec testSpec = config->testSpec(); - if (!testSpec.hasFilters()) - testSpec = TestSpecParser(ITagAliasRegistry::get()).parse("~[.]").testSpec(); // All not hidden tests - - auto const& allTestCases = getAllTestCasesSorted(*config); - for (auto const& testCase : allTestCases) { - if (!context.aborting() && matchTest(testCase, testSpec, *config)) - totals += context.runTest(testCase); - else - context.reporter().skipTest(testCase); - } - - context.testGroupEnded(config->name(), totals, 1, 1); - return totals; - } - - void applyFilenamesAsTags(Catch::IConfig const& config) { - using namespace Catch; - auto& tests = const_cast&>(getAllTestCasesSorted(config)); - for (auto& testCase : tests) { - auto tags = testCase.tags; - - 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); - } - } - -} - -namespace Catch { - - Session::Session() { - static bool alreadyInstantiated = false; - if( alreadyInstantiated ) { - try { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } - catch(...) { getMutableRegistryHub().registerStartupException(); } - } - - const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); - if ( !exceptions.empty() ) { - m_startupExceptions = true; - Colour colourGuard( Colour::Red ); - Catch::cerr() << "Errors occured 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'; - } - } - } - - alreadyInstantiated = true; - m_cli = makeCommandLineParser( m_configData ); - } - Session::~Session() { - Catch::cleanUp(); - } - - 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 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 Session::applyCommandLine( int argc, char* argv[] ) { - if( m_startupExceptions ) - return 1; - - auto result = m_cli.parse( clara::Args( argc, argv ) ); - if( !result ) { - 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; - } - - void Session::useConfigData( ConfigData const& configData ) { - m_configData = configData; - m_config.reset(); - } - - int Session::run( int argc, char* argv[] ) { - if( m_startupExceptions ) - return 1; - int returnCode = applyCommandLine( argc, argv ); - if( returnCode == 0 ) - returnCode = run(); - return returnCode; - } - -#if defined(WIN32) && defined(UNICODE) - int Session::run( int argc, wchar_t* const argv[] ) { - - char **utf8Argv = new char *[ argc ]; - - for ( int i = 0; i < argc; ++i ) { - int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); - - utf8Argv[ i ] = new char[ bufSize ]; - - WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); - } - - int returnCode = run( argc, utf8Argv ); - - for ( int i = 0; i < argc; ++i ) - delete [] utf8Argv[ i ]; - - delete [] utf8Argv; - - return returnCode; - } -#endif - int Session::run() { - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before starting" << std::endl; - static_cast(std::getchar()); - } - int exitCode = runInternal(); - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; - static_cast(std::getchar()); - } - return exitCode; - } - - 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( m_configData ); - return *m_config; - } - - int Session::runInternal() { - if( m_startupExceptions ) - return 1; - - if( m_configData.showHelp || m_configData.libIdentify ) - return 0; - - try - { - config(); // Force config to be constructed - - seedRng( *m_config ); - - if( m_configData.filenamesAsTags ) - applyFilenamesAsTags( *m_config ); - - // Handle list request - if( Option listed = list( config() ) ) - return static_cast( *listed ); - - return (std::min)( MaxExitCode, static_cast( runTests( m_config ).assertions.failed ) ); - } - catch( std::exception& ex ) { - Catch::cerr() << ex.what() << std::endl; - return MaxExitCode; - } - } - -} // end namespace Catch -// end catch_session.cpp -// start catch_startup_exception_registry.cpp - -namespace Catch { - void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { - try { - m_exceptions.push_back(exception); - } - catch(...) { - // If we run out of memory during start-up there's really not a lot more we can do about it - std::terminate(); - } - } - - std::vector const& StartupExceptionRegistry::getExceptions() const noexcept { - return m_exceptions; - } - -} // end namespace Catch -// end catch_startup_exception_registry.cpp -// start catch_stream.cpp - -#include -#include -#include - -namespace Catch { - - template - class StreamBufImpl : public StreamBufBase { - char data[bufferSize]; - WriterF m_writer; - - public: - StreamBufImpl() { - setp( data, data + sizeof(data) ); - } - - ~StreamBufImpl() noexcept { - StreamBufImpl::sync(); - } - - private: - int overflow( int c ) override { - sync(); - - if( c != EOF ) { - if( pbase() == epptr() ) - m_writer( std::string( 1, static_cast( c ) ) ); - else - sputc( static_cast( c ) ); - } - return 0; - } - - int sync() override { - if( pbase() != pptr() ) { - m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); - setp( pbase(), epptr() ); - } - return 0; - } - }; - - /////////////////////////////////////////////////////////////////////////// - - Catch::IStream::~IStream() = default; - - FileStream::FileStream( std::string const& filename ) { - m_ofs.open( filename.c_str() ); - CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); - } - - std::ostream& FileStream::stream() const { - return m_ofs; - } - - struct OutputDebugWriter { - - void operator()( std::string const&str ) { - writeToDebugConsole( str ); - } - }; - - DebugOutStream::DebugOutStream() - : m_streamBuf( new StreamBufImpl() ), - m_os( m_streamBuf.get() ) - {} - - std::ostream& DebugOutStream::stream() const { - return m_os; - } - - // Store the streambuf from cout up-front because - // cout may get redirected when running tests - CoutStream::CoutStream() - : m_os( Catch::cout().rdbuf() ) - {} - - std::ostream& CoutStream::stream() const { - return m_os; - } - -#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_streambuf.cpp - -namespace Catch { - StreamBufBase::~StreamBufBase() = default; -} -// end catch_streambuf.cpp -// start catch_string_manip.cpp - -#include -#include -#include -#include - -namespace Catch { - - 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; - } - char toLowerCh(char c) { - return static_cast( std::tolower( c ) ); - } - 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 ); - - return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); - } - - 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; - } - - 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; - } - -} -// end catch_string_manip.cpp -// start catch_stringref.cpp - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - -#include -#include -#include - -namespace Catch { - - auto getEmptyStringRef() -> StringRef { - static StringRef s_emptyStringRef(""); - return s_emptyStringRef; - } - - StringRef::StringRef() noexcept - : StringRef( getEmptyStringRef() ) - {} - - StringRef::StringRef( StringRef const& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ) - {} - - StringRef::StringRef( StringRef&& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ), - m_data( other.m_data ) - { - other.m_data = nullptr; - } - - StringRef::StringRef( char const* rawChars ) noexcept - : m_start( rawChars ), - m_size( static_cast( std::strlen( rawChars ) ) ) - { - assert( rawChars != nullptr ); - } - - StringRef::StringRef( char const* rawChars, size_type size ) noexcept - : m_start( rawChars ), - m_size( size ) - { - size_type rawSize = rawChars == nullptr ? 0 : static_cast( std::strlen( rawChars ) ); - if( rawSize < size ) - m_size = rawSize; - } - - StringRef::StringRef( std::string const& stdString ) noexcept - : m_start( stdString.c_str() ), - m_size( stdString.size() ) - {} - - StringRef::~StringRef() noexcept { - delete[] m_data; - } - - auto StringRef::operator = ( StringRef other ) noexcept -> StringRef& { - swap( other ); - return *this; - } - StringRef::operator std::string() const { - return std::string( m_start, m_size ); - } - - void StringRef::swap( StringRef& other ) noexcept { - std::swap( m_start, other.m_start ); - std::swap( m_size, other.m_size ); - std::swap( m_data, other.m_data ); - } - - auto StringRef::c_str() const -> char const* { - if( isSubstring() ) - const_cast( this )->takeOwnership(); - return m_start; - } - auto StringRef::data() const noexcept -> char const* { - return m_start; - } - - auto StringRef::isOwned() const noexcept -> bool { - return m_data != nullptr; - } - auto StringRef::isSubstring() const noexcept -> bool { - return m_start[m_size] != '\0'; - } - - void StringRef::takeOwnership() { - if( !isOwned() ) { - m_data = new char[m_size+1]; - memcpy( m_data, m_start, m_size ); - m_data[m_size] = '\0'; - m_start = m_data; - } - } - auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { - if( start < m_size ) - return StringRef( m_start+start, size ); - else - return StringRef(); - } - auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { - return - size() == other.size() && - (std::strncmp( m_start, other.m_start, size() ) == 0); - } - auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { - return !operator==( other ); - } - - auto StringRef::operator[](size_type index) const noexcept -> char { - return m_start[index]; - } - - auto StringRef::empty() const noexcept -> bool { - return m_size == 0; - } - - auto StringRef::size() const noexcept -> size_type { - return m_size; - } - auto StringRef::numberOfCharacters() const noexcept -> size_type { - size_type noChars = m_size; - // Make adjustments for uft encodings - for( size_type i=0; i < m_size; ++i ) { - char c = m_start[i]; - if( ( c & 0b11000000 ) == 0b11000000 ) { - if( ( c & 0b11100000 ) == 0b11000000 ) - noChars--; - else if( ( c & 0b11110000 ) == 0b11100000 ) - noChars-=2; - else if( ( c & 0b11111000 ) == 0b11110000 ) - noChars-=3; - } - } - return noChars; - } - - auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string { - std::string str; - str.reserve( lhs.size() + rhs.size() ); - str += lhs; - str += rhs; - return str; - } - auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string { - return std::string( lhs ) + std::string( rhs ); - } - auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string { - return std::string( lhs ) + std::string( rhs ); - } - - auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { - return os << str.c_str(); - } - -} // namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif -// 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) { - try { - getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); - } catch (...) { - // Do not throw when constructing global objects, instead register the exception to be processed later - getMutableRegistryHub().registerStartupException(); - } - } - -} -// end catch_tag_alias_autoregistrar.cpp -// start catch_tag_alias_registry.cpp - -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 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 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 ); - } - - ITagAliasRegistry::~ITagAliasRegistry() {} - - ITagAliasRegistry const& ITagAliasRegistry::get() { - return getRegistryHub().getTagAliasRegistry(); - } - -} // end namespace Catch -// end catch_tag_alias_registry.cpp -// start catch_test_case_info.cpp - -#include -#include -#include - -namespace Catch { - - 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::Benchmark | TestCaseInfo::IsHidden ); - else - return TestCaseInfo::None; - } - bool isReservedTag( std::string const& tag ) { - return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( 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 alpha-numeric characters are reserved\n" - << _lineInfo ); - } - - TestCase makeTestCase( ITestInvoker* _testCase, - std::string const& _className, - std::string const& _name, - std::string const& _descOrTags, - SourceLineInfo const& _lineInfo ) - { - bool isHidden = false; - - // Parse out tags - std::vector tags; - std::string desc, tag; - bool inTag = false; - for (char c : _descOrTags) { - if( !inTag ) { - if( c == '[' ) - inTag = true; - else - desc += c; - } - else { - if( c == ']' ) { - TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); - if( ( prop & TestCaseInfo::IsHidden ) != 0 ) - isHidden = true; - else if( prop == TestCaseInfo::None ) - enforceNotReservedTag( tag, _lineInfo ); - - tags.push_back( tag ); - tag.clear(); - inTag = false; - } - else - tag += c; - } - } - if( isHidden ) { - tags.push_back( "." ); - } - - TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); - return TestCase( _testCase, info ); - } - - void setTags( TestCaseInfo& testCaseInfo, std::vector tags ) { - std::sort(begin(tags), end(tags)); - tags.erase(std::unique(begin(tags), end(tags)), end(tags)); - testCaseInfo.lcaseTags.clear(); - - for( auto const& tag : tags ) { - std::string lcaseTag = toLower( tag ); - testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); - testCaseInfo.lcaseTags.push_back( lcaseTag ); - } - testCaseInfo.tags = std::move(tags); - } - - TestCaseInfo::TestCaseInfo( std::string const& _name, - std::string const& _className, - std::string const& _description, - std::vector const& _tags, - SourceLineInfo const& _lineInfo ) - : name( _name ), - className( _className ), - description( _description ), - lineInfo( _lineInfo ), - properties( None ) - { - setTags( *this, _tags ); - } - - bool TestCaseInfo::isHidden() const { - return ( properties & IsHidden ) != 0; - } - bool TestCaseInfo::throws() const { - return ( properties & Throws ) != 0; - } - bool TestCaseInfo::okToFail() const { - return ( properties & (ShouldFail | MayFail ) ) != 0; - } - bool TestCaseInfo::expectedToFail() const { - return ( properties & (ShouldFail ) ) != 0; - } - - 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( ITestInvoker* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} - - TestCase TestCase::withName( std::string const& _newName ) const { - TestCase other( *this ); - other.name = _newName; - return other; - } - - void TestCase::invoke() const { - test->invoke(); - } - - bool TestCase::operator == ( TestCase const& other ) const { - return test.get() == other.test.get() && - name == other.name && - className == other.className; - } - - bool TestCase::operator < ( TestCase const& other ) const { - return name < other.name; - } - - TestCaseInfo const& TestCase::getTestCaseInfo() const - { - return *this; - } - -} // end namespace Catch -// end catch_test_case_info.cpp -// start catch_test_case_registry_impl.cpp - -#include - -namespace Catch { - - std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { - - std::vector sorted = unsortedTestCases; - - 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; - } - return sorted; - } - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { - return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); - } - - void enforceNoDuplicateTestCases( std::vector const& functions ) { - std::set 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 ); - } - } - - std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { - std::vector filtered; - filtered.reserve( testCases.size() ); - for( auto const& testCase : testCases ) - if( matchTest( testCase, testSpec, config ) ) - filtered.push_back( testCase ); - return filtered; - } - std::vector const& getAllTestCasesSorted( IConfig const& config ) { - return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); - } - - void TestRegistry::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 ); - } - - std::vector const& TestRegistry::getAllTests() const { - return m_functions; - } - std::vector 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; - } - - /////////////////////////////////////////////////////////////////////////// - TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {} - - void TestInvokerAsFunction::invoke() const { - m_testAsFunction(); - } - - 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 ); - } - return className; - } - -} // end namespace Catch -// end catch_test_case_registry_impl.cpp -// start catch_test_case_tracker.cpp - -#include -#include -#include -#include - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - -namespace Catch { -namespace TestCaseTracking { - - NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) - : name( _name ), - location( _location ) - {} - - ITracker::~ITracker() = default; - - TrackerContext& TrackerContext::instance() { - static TrackerContext s_instance; - return s_instance; - } - - ITracker& TrackerContext::startRun() { - m_rootTracker = std::make_shared( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); - m_currentTracker = nullptr; - m_runState = Executing; - return *m_rootTracker; - } - - void TrackerContext::endRun() { - m_rootTracker.reset(); - m_currentTracker = nullptr; - m_runState = NotStarted; - } - - void TrackerContext::startCycle() { - m_currentTracker = m_rootTracker.get(); - m_runState = Executing; - } - void TrackerContext::completeCycle() { - m_runState = CompletedCycle; - } - - bool TrackerContext::completedCycle() const { - return m_runState == CompletedCycle; - } - ITracker& TrackerContext::currentTracker() { - return *m_currentTracker; - } - void TrackerContext::setCurrentTracker( ITracker* tracker ) { - m_currentTracker = tracker; - } - - TrackerBase::TrackerHasName::TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} - bool TrackerBase::TrackerHasName::operator ()( ITrackerPtr const& tracker ) const { - return - tracker->nameAndLocation().name == m_nameAndLocation.name && - tracker->nameAndLocation().location == m_nameAndLocation.location; - } - - TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : m_nameAndLocation( nameAndLocation ), - m_ctx( ctx ), - m_parent( parent ) - {} - - NameAndLocation const& TrackerBase::nameAndLocation() const { - return m_nameAndLocation; - } - bool TrackerBase::isComplete() const { - return m_runState == CompletedSuccessfully || m_runState == Failed; - } - bool TrackerBase::isSuccessfullyCompleted() const { - return m_runState == CompletedSuccessfully; - } - bool TrackerBase::isOpen() const { - return m_runState != NotStarted && !isComplete(); - } - bool TrackerBase::hasChildren() const { - return !m_children.empty(); - } - - void TrackerBase::addChild( ITrackerPtr const& child ) { - m_children.push_back( child ); - } - - ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { - auto it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); - return( it != m_children.end() ) - ? *it - : nullptr; - } - ITracker& TrackerBase::parent() { - assert( m_parent ); // Should always be non-null except for root - return *m_parent; - } - - void TrackerBase::openChild() { - if( m_runState != ExecutingChildren ) { - m_runState = ExecutingChildren; - if( m_parent ) - m_parent->openChild(); - } - } - - bool TrackerBase::isSectionTracker() const { return false; } - bool TrackerBase::isIndexTracker() const { return false; } - - void TrackerBase::open() { - m_runState = Executing; - moveToThis(); - if( m_parent ) - m_parent->openChild(); - } - - void TrackerBase::close() { - - // Close any still open children (e.g. generators) - while( &m_ctx.currentTracker() != this ) - m_ctx.currentTracker().close(); - - switch( m_runState ) { - case NeedsAnotherRun: - break; - - case Executing: - m_runState = CompletedSuccessfully; - break; - case ExecutingChildren: - if( m_children.empty() || m_children.back()->isComplete() ) - m_runState = CompletedSuccessfully; - break; - - case NotStarted: - case CompletedSuccessfully: - case Failed: - CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); - - default: - CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); - } - moveToParent(); - m_ctx.completeCycle(); - } - void TrackerBase::fail() { - m_runState = Failed; - if( m_parent ) - m_parent->markAsNeedingAnotherRun(); - moveToParent(); - m_ctx.completeCycle(); - } - void TrackerBase::markAsNeedingAnotherRun() { - m_runState = NeedsAnotherRun; - } - - void TrackerBase::moveToParent() { - assert( m_parent ); - m_ctx.setCurrentTracker( m_parent ); - } - void TrackerBase::moveToThis() { - m_ctx.setCurrentTracker( this ); - } - - SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : TrackerBase( nameAndLocation, ctx, parent ) - { - if( parent ) { - while( !parent->isSectionTracker() ) - parent = &parent->parent(); - - SectionTracker& parentSection = static_cast( *parent ); - addNextFilters( parentSection.m_filters ); - } - } - - bool SectionTracker::isSectionTracker() const { return true; } - - SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { - std::shared_ptr section; - - ITracker& currentTracker = ctx.currentTracker(); - if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { - assert( childTracker ); - assert( childTracker->isSectionTracker() ); - section = std::static_pointer_cast( childTracker ); - } - else { - section = std::make_shared( nameAndLocation, ctx, ¤tTracker ); - currentTracker.addChild( section ); - } - if( !ctx.completedCycle() ) - section->tryOpen(); - return *section; - } - - void SectionTracker::tryOpen() { - if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) - open(); - } - - void SectionTracker::addInitialFilters( std::vector 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() ); - } - } - void SectionTracker::addNextFilters( std::vector const& filters ) { - if( filters.size() > 1 ) - m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); - } - - IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) - : TrackerBase( nameAndLocation, ctx, parent ), - m_size( size ) - {} - - bool IndexTracker::isIndexTracker() const { return true; } - - IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { - std::shared_ptr tracker; - - ITracker& currentTracker = ctx.currentTracker(); - if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { - assert( childTracker ); - assert( childTracker->isIndexTracker() ); - tracker = std::static_pointer_cast( childTracker ); - } - else { - tracker = std::make_shared( nameAndLocation, ctx, ¤tTracker, size ); - currentTracker.addChild( tracker ); - } - - if( !ctx.completedCycle() && !tracker->isComplete() ) { - if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) - tracker->moveNext(); - tracker->open(); - } - - return *tracker; - } - - int IndexTracker::index() const { return m_index; } - - void IndexTracker::moveNext() { - m_index++; - m_children.clear(); - } - - void IndexTracker::close() { - TrackerBase::close(); - if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) - m_runState = Executing; - } - -} // namespace TestCaseTracking - -using TestCaseTracking::ITracker; -using TestCaseTracking::TrackerContext; -using TestCaseTracking::SectionTracker; -using TestCaseTracking::IndexTracker; - -} // 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 ); - } - - NameAndTags::NameAndTags( StringRef name_ , StringRef tags_ ) noexcept : name( name_ ), tags( tags_ ) {} - - AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept { - try { - getMutableRegistryHub() - .registerTest( - makeTestCase( - invoker, - extractClassName( classOrMethod ), - nameAndTags.name, - nameAndTags.tags, - lineInfo)); - } catch (...) { - // Do not throw when constructing global objects, instead register the exception to be processed later - getMutableRegistryHub().registerStartupException(); - } - } - - AutoReg::~AutoReg() = default; -} -// end catch_test_registry.cpp -// start catch_test_spec.cpp - -#include -#include -#include -#include - -namespace Catch { - - TestSpec::Pattern::~Pattern() = default; - TestSpec::NamePattern::~NamePattern() = default; - TestSpec::TagPattern::~TagPattern() = default; - TestSpec::ExcludedPattern::~ExcludedPattern() = default; - - TestSpec::NamePattern::NamePattern( std::string const& name ) - : m_wildcardPattern( toLower( name ), CaseSensitive::No ) - {} - bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { - return m_wildcardPattern.matches( toLower( testCase.name ) ); - } - - TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} - bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { - return std::find(begin(testCase.lcaseTags), - end(testCase.lcaseTags), - m_tag) != end(testCase.lcaseTags); - } - - TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} - bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } - - bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { - // All patterns in a filter must match for the filter to be a match - for( auto const& pattern : m_patterns ) { - if( !pattern->matches( testCase ) ) - return false; - } - return true; - } - - bool TestSpec::hasFilters() const { - return !m_filters.empty(); - } - bool TestSpec::matches( TestCaseInfo const& testCase ) const { - // A TestSpec matches if any filter matches - for( auto const& filter : m_filters ) - if( filter.matches( testCase ) ) - return true; - return false; - } -} -// end catch_test_spec.cpp -// start catch_test_spec_parser.cpp - -namespace Catch { - - TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} - - TestSpecParser& 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(); - return *this; - } - TestSpec TestSpecParser::testSpec() { - addFilter(); - return m_testSpec; - } - - void TestSpecParser::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(); - addFilter(); - } - else if( c == '[' ) { - if( subString() == "exclude:" ) - m_exclusion = true; - else - addPattern(); - startNewMode( Tag, ++m_pos ); - } - else if( c == '\\' ) - escape(); - } - else if( m_mode == EscapedName ) - m_mode = Name; - else if( m_mode == QuotedName && c == '"' ) - addPattern(); - else if( m_mode == Tag && c == ']' ) - addPattern(); - } - void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { - m_mode = mode; - m_start = start; - } - void TestSpecParser::escape() { - if( m_mode == None ) - m_start = m_pos; - m_mode = EscapedName; - m_escapeChars.push_back( m_pos ); - } - std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } - - void TestSpecParser::addFilter() { - if( !m_currentFilter.m_patterns.empty() ) { - m_testSpec.m_filters.push_back( m_currentFilter ); - m_currentFilter = TestSpec::Filter(); - } - } - - TestSpec parseTestSpec( std::string const& arg ) { - return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); - } - -} // namespace Catch -// end catch_test_spec_parser.cpp -// start catch_timer.cpp - -#include - -namespace Catch { - - auto getCurrentNanosecondsSinceEpoch() -> uint64_t { - return std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); - } - - auto estimateClockResolution() -> uint64_t { - uint64_t sum = 0; - static const uint64_t iterations = 1000000; - - 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; - } - - // 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; - } - auto getEstimatedClockResolution() -> uint64_t { - static auto s_resolution = estimateClockResolution(); - return s_resolution; - } - - void Timer::start() { - m_nanoseconds = getCurrentNanosecondsSinceEpoch(); - } - auto Timer::getElapsedNanoseconds() const -> unsigned int { - return static_cast(getCurrentNanosecondsSinceEpoch() - m_nanoseconds); - } - auto Timer::getElapsedMicroseconds() const -> unsigned int { - return static_cast(getElapsedNanoseconds()/1000); - } - auto Timer::getElapsedMilliseconds() const -> unsigned int { - return static_cast(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 - -#include - -namespace Catch { - -namespace Detail { - - const std::string unprintableString = "{?}"; - - namespace { - const int hexThreshold = 255; - - struct Endianness { - 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; - } - }; - } - - std::string rawMemoryToString( const void *object, std::size_t size ) { - // Reverse order for little endian architectures - int i = 0, end = static_cast( size ), inc = 1; - if( Endianness::which() == Endianness::Little ) { - i = end-1; - end = inc = -1; - } - - unsigned char const *bytes = static_cast(object); - std::ostringstream os; - os << "0x" << std::setfill('0') << std::hex; - for( ; i != end; i += inc ) - os << std::setw(2) << static_cast(bytes[i]); - return os.str(); - } -} - -template -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 ); - } - return d; -} - -//// ======================================================= //// -// -// Out-of-line defs for full specialization of StringMaker -// -//// ======================================================= //// - -std::string StringMaker::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 StringMaker::convert(const std::wstring& wstr) { - std::string s; - s.reserve(wstr.size()); - for (auto c : wstr) { - s += (c <= 0xff) ? static_cast(c) : '?'; - } - return ::Catch::Detail::stringify(s); -} - -std::string StringMaker::convert(char const* str) { - if (str) { - return ::Catch::Detail::stringify(std::string{ str }); - } else { - return{ "{null string}" }; - } -} -std::string StringMaker::convert(char* str) { - if (str) { - return ::Catch::Detail::stringify(std::string{ str }); - } else { - return{ "{null string}" }; - } -} -std::string StringMaker::convert(wchar_t const * str) { - if (str) { - return ::Catch::Detail::stringify(std::wstring{ str }); - } else { - return{ "{null string}" }; - } -} -std::string StringMaker::convert(wchar_t * str) { - if (str) { - return ::Catch::Detail::stringify(std::wstring{ str }); - } else { - return{ "{null string}" }; - } -} - -std::string StringMaker::convert(int value) { - return ::Catch::Detail::stringify(static_cast(value)); -} -std::string StringMaker::convert(long value) { - return ::Catch::Detail::stringify(static_cast(value)); -} -std::string StringMaker::convert(long long value) { - std::ostringstream oss; - oss << value; - if (value > Detail::hexThreshold) { - oss << " (0x" << std::hex << value << ')'; - } - return oss.str(); -} - -std::string StringMaker::convert(unsigned int value) { - return ::Catch::Detail::stringify(static_cast(value)); -} -std::string StringMaker::convert(unsigned long value) { - return ::Catch::Detail::stringify(static_cast(value)); -} -std::string StringMaker::convert(unsigned long long value) { - std::ostringstream oss; - oss << value; - if (value > Detail::hexThreshold) { - oss << " (0x" << std::hex << value << ')'; - } - return oss.str(); -} - -std::string StringMaker::convert(bool b) { - return b ? "true" : "false"; -} - -std::string StringMaker::convert(char value) { - if (value == '\r') { - return "'\\r'"; - } else if (value == '\f') { - return "'\\f'"; - } else if (value == '\n') { - return "'\\n'"; - } else if (value == '\t') { - return "'\\t'"; - } else if ('\0' <= value && value < ' ') { - return ::Catch::Detail::stringify(static_cast(value)); - } else { - char chstr[] = "' '"; - chstr[1] = value; - return chstr; - } -} -std::string StringMaker::convert(signed char c) { - return ::Catch::Detail::stringify(static_cast(c)); -} -std::string StringMaker::convert(unsigned char c) { - return ::Catch::Detail::stringify(static_cast(c)); -} - -std::string StringMaker::convert(std::nullptr_t) { - return "nullptr"; -} - -std::string StringMaker::convert(float value) { - return fpToString(value, 5) + 'f'; -} -std::string StringMaker::convert(double value) { - return fpToString(value, 10); -} - -} // end namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - -// end catch_tostring.cpp -// start catch_totals.cpp - -namespace Catch { - - 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; - } - - Counts& Counts::operator += ( Counts const& other ) { - passed += other.passed; - failed += other.failed; - failedButOk += other.failedButOk; - return *this; - } - - 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; - } - - Totals Totals::operator - ( Totals const& other ) const { - Totals diff; - diff.assertions = assertions - other.assertions; - diff.testCases = testCases - other.testCases; - return diff; - } - - Totals& Totals::operator += ( Totals const& other ) { - assertions += other.assertions; - testCases += other.testCases; - return *this; - } - - 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 - ++diff.testCases.passed; - return diff; - } - -} -// end catch_totals.cpp -// start catch_version.cpp - -#include - -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 ) - {} - - 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; - } - - Version const& libraryVersion() { - static Version version( 2, 0, 1, "", 0 ); - return version; - } - -} -// end catch_version.cpp -// start catch_wildcard_pattern.cpp - -namespace Catch { - - WildcardPattern::WildcardPattern( std::string const& pattern, - CaseSensitive::Choice caseSensitivity ) - : m_caseSensitivity( caseSensitivity ), - 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( m_wildcard | WildcardAtEnd ); - } - } - - bool WildcardPattern::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 ); - default: - CATCH_INTERNAL_ERROR( "Unknown enum" ); - } - } - - std::string WildcardPattern::adjustCase( std::string const& str ) const { - return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; - } -} -// end catch_wildcard_pattern.cpp -// start catch_xmlwriter.cpp - -// start catch_xmlwriter.h - -#include -#include - -namespace Catch { - - class XmlEncode { - public: - enum ForWhat { ForTextNodes, ForAttributes }; - - XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); - - void encodeTo( std::ostream& os ) const; - - friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); - - private: - std::string m_str; - ForWhat m_forWhat; - }; - - class XmlWriter { - public: - - class ScopedElement { - public: - ScopedElement( XmlWriter* writer ); - - ScopedElement( ScopedElement&& other ) noexcept; - ScopedElement& operator=( ScopedElement&& other ) noexcept; - - ~ScopedElement(); - - ScopedElement& writeText( std::string const& text, bool indent = true ); - - template - ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { - m_writer->writeAttribute( name, attribute ); - return *this; - } - - private: - mutable XmlWriter* m_writer = nullptr; - }; - - XmlWriter( std::ostream& os = Catch::cout() ); - ~XmlWriter(); - - XmlWriter( XmlWriter const& ) = delete; - XmlWriter& operator=( XmlWriter const& ) = delete; - - XmlWriter& startElement( std::string const& name ); - - ScopedElement scopedElement( std::string const& name ); - - XmlWriter& endElement(); - - XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); - - XmlWriter& writeAttribute( std::string const& name, bool attribute ); - - template - XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { - m_oss.clear(); - m_oss.str(std::string()); - m_oss << attribute; - return writeAttribute( name, m_oss.str() ); - } - - XmlWriter& writeText( std::string const& text, bool indent = true ); - - XmlWriter& writeComment( std::string const& text ); - - void writeStylesheetRef( std::string const& url ); - - XmlWriter& writeBlankLine(); - - void ensureTagClosed(); - - private: - - void writeDeclaration(); - - void newlineIfNecessary(); - - bool m_tagIsOpen = false; - bool m_needsNewline = false; - std::vector m_tags; - std::string m_indent; - std::ostream& m_os; - std::ostringstream m_oss; - }; - -} - -// end catch_xmlwriter.h -#include - -namespace Catch { - - XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) - : m_str( str ), - m_forWhat( forWhat ) - {} - - void XmlEncode::encodeTo( std::ostream& os ) const { - - // Apostrophe escaping not necessary if we always use " to write attributes - // (see: 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( c ); - } - else - os << c; - } - } - } - - std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { - xmlEncode.encodeTo( os ); - return os; - } - - XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) - : m_writer( writer ) - {} - - XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept - : m_writer( other.m_writer ){ - other.m_writer = nullptr; - } - XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { - if ( m_writer ) { - m_writer->endElement(); - } - m_writer = other.m_writer; - other.m_writer = nullptr; - return *this; - } - - XmlWriter::ScopedElement::~ScopedElement() { - if( m_writer ) - m_writer->endElement(); - } - - XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { - m_writer->writeText( text, indent ); - return *this; - } - - XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) - { - writeDeclaration(); - } - - XmlWriter::~XmlWriter() { - while( !m_tags.empty() ) - endElement(); - } - - XmlWriter& XmlWriter::startElement( std::string const& name ) { - ensureTagClosed(); - newlineIfNecessary(); - m_os << m_indent << '<' << name; - m_tags.push_back( name ); - m_indent += " "; - m_tagIsOpen = true; - return *this; - } - - XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { - ScopedElement scoped( this ); - startElement( name ); - return scoped; - } - - XmlWriter& XmlWriter::endElement() { - newlineIfNecessary(); - m_indent = m_indent.substr( 0, m_indent.size()-2 ); - if( m_tagIsOpen ) { - m_os << "/>"; - m_tagIsOpen = false; - } - else { - m_os << m_indent << ""; - } - m_os << std::endl; - m_tags.pop_back(); - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { - if( !name.empty() && !attribute.empty() ) - m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { - m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { - if( !text.empty() ){ - bool tagWasOpen = m_tagIsOpen; - ensureTagClosed(); - if( tagWasOpen && indent ) - m_os << m_indent; - m_os << XmlEncode( text ); - m_needsNewline = true; - } - return *this; - } - - XmlWriter& XmlWriter::writeComment( std::string const& text ) { - ensureTagClosed(); - m_os << m_indent << ""; - m_needsNewline = true; - return *this; - } - - void XmlWriter::writeStylesheetRef( std::string const& url ) { - m_os << "\n"; - } - - XmlWriter& XmlWriter::writeBlankLine() { - ensureTagClosed(); - m_os << '\n'; - return *this; - } - - void XmlWriter::ensureTagClosed() { - if( m_tagIsOpen ) { - m_os << ">" << std::endl; - m_tagIsOpen = false; - } - } - - void XmlWriter::writeDeclaration() { - m_os << "\n"; - } - - void XmlWriter::newlineIfNecessary() { - if( m_needsNewline ) { - m_os << std::endl; - m_needsNewline = false; - } - } -} -// end catch_xmlwriter.cpp -// start catch_reporter_bases.cpp - -#include -#include -#include -#include -#include - -namespace Catch { - void prepareExpandedExpression(AssertionResult& result) { - result.getExpandedExpression(); - } - - // 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); -#else - sprintf(buffer, "%.3f", duration); -#endif - return std::string(buffer); - } - - TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) - :StreamingReporterBase(_config) {} - - void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} - - bool TestEventListenerBase::assertionEnded(AssertionStats const &) { - return false; - } - -} // end namespace Catch -// end catch_reporter_bases.cpp -// start catch_reporter_compact.cpp - -namespace { - -#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 - - // Colour::LightGrey - Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } - - std::string bothOrAll( std::size_t count ) { - return count == 1 ? std::string() : - count == 2 ? "both " : "all " ; - } -} - -namespace Catch { - - struct CompactReporter : StreamingReporterBase { - - using StreamingReporterBase::StreamingReporterBase; - - ~CompactReporter() override; - - static std::string getDescription() { - return "Reports test results on a single line, suitable for IDEs"; - } - - ReporterPreferences getPreferences() const override { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = false; - return prefs; - } - - void noMatchingTestCases( std::string const& spec ) override { - stream << "No test cases matched '" << spec << '\'' << std::endl; - } - - void assertionStarting( AssertionInfo const& ) override {} - - bool assertionEnded( AssertionStats const& _assertionStats ) override { - AssertionResult const& result = _assertionStats.assertionResult; - - 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; - } - - AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); - printer.print(); - - stream << std::endl; - return true; - } - - void sectionEnded(SectionStats const& _sectionStats) override { - if (m_config->showDurations() == ShowDurations::Always) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; - } - } - - void testRunEnded( TestRunStats const& _testRunStats ) override { - printTotals( _testRunStats.totals ); - stream << '\n' << std::endl; - StreamingReporterBase::testRunEnded( _testRunStats ); - } - - private: - 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; - } - } - - private: - void printSourceInfo() const { - Colour colourGuard( Colour::FileName ); - stream << result.getSourceInfo() << ':'; - } - - void printResultType( Colour::Code colour, std::string const& passOrFail ) const { - if( !passOrFail.empty() ) { - { - Colour colourGuard( colour ); - stream << ' ' << passOrFail; - } - stream << ':'; - } - } - - void printIssue( std::string const& issue ) const { - stream << ' ' << issue; - } - - void printExpressionWas() { - if( result.hasExpression() ) { - stream << ';'; - { - Colour colour( dimColour() ); - stream << " expression was:"; - } - printOriginalExpression(); - } - } - - void printOriginalExpression() const { - if( result.hasExpression() ) { - stream << ' ' << result.getExpression(); - } - } - - void printReconstructedExpression() const { - if( result.hasExpandedExpression() ) { - { - Colour colour( dimColour() ); - stream << " for: "; - } - stream << result.getExpandedExpression(); - } - } - - void printMessage() { - if ( itMessage != messages.end() ) { - stream << " '" << itMessage->message << '\''; - ++itMessage; - } - } - - void printRemainingMessages( Colour::Code colour = dimColour() ) { - if ( itMessage == messages.end() ) - return; - - // using messages.end() directly yields (or auto) compilation error: - std::vector::const_iterator itEnd = messages.end(); - const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); - - { - Colour colourGuard( colour ); - stream << " with " << pluralise( N, "message" ) << ':'; - } - - 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"; - } - } - } - } - - private: - std::ostream& stream; - AssertionResult const& result; - std::vector messages; - std::vector::const_iterator itMessage; - bool printInfoMessages; - }; - - // 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( 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" ) << '.'; - } - } - }; - - CompactReporter::~CompactReporter() {} - - CATCH_REGISTER_REPORTER( "compact", CompactReporter ) - -} // end namespace Catch -// end catch_reporter_compact.cpp -// start catch_reporter_console.cpp - -#include -#include - -#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 { - - namespace { - 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; - } - - 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; - } - - struct ColumnInfo { - enum Justification { Left, Right }; - std::string name; - int width; - Justification justification; - }; - struct ColumnBreak {}; - struct RowBreak {}; - - class TablePrinter { - std::ostream& m_os; - std::vector m_columnInfos; - std::ostringstream m_oss; - int m_currentColumn = -1; - bool m_isOpen = false; - - public: - TablePrinter( std::ostream& os, std::vector const& columnInfos ) - : m_os( os ), - m_columnInfos( columnInfos ) - {} - - auto columnInfos() const -> std::vector const& { - return m_columnInfos; - } - - void open() { - if( !m_isOpen ) { - m_isOpen = true; - *this << RowBreak(); - for( auto const& info : m_columnInfos ) - *this << info.name << ColumnBreak(); - *this << RowBreak(); - m_os << Catch::getLineOfChars<'-'>() << "\n"; - } - } - void close() { - if( m_isOpen ) { - *this << RowBreak(); - m_os << std::endl; - m_isOpen = false; - } - } - - template - friend TablePrinter& operator << ( TablePrinter& tp, T const& value ) { - tp.m_oss << value; - return tp; - } - - friend TablePrinter& operator << ( TablePrinter& tp, ColumnBreak ) { - auto colStr = tp.m_oss.str(); - // This takes account of utf8 encodings - auto strSize = Catch::StringRef( colStr ).numberOfCharacters(); - tp.m_oss.str(""); - tp.open(); - if( tp.m_currentColumn == static_cast(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+2 < static_cast( colInfo.width ) ) - ? std::string( colInfo.width-(strSize+2), ' ' ) - : std::string(); - if( colInfo.justification == ColumnInfo::Left ) - tp.m_os << colStr << padding << " "; - else - tp.m_os << padding << colStr << " "; - return tp; - } - - friend TablePrinter& operator << ( TablePrinter& tp, RowBreak ) { - if( tp.m_currentColumn > 0 ) { - tp.m_os << "\n"; - tp.m_currentColumn = -1; - } - return tp; - } - }; - - 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; - - uint64_t m_inNanoseconds; - Unit m_units; - - public: - Duration( uint64_t 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; - } - - } - - auto value() const -> double { - switch( m_units ) { - case Unit::Microseconds: - return m_inNanoseconds / static_cast( s_nanosecondsInAMicrosecond ); - case Unit::Milliseconds: - return m_inNanoseconds / static_cast( s_nanosecondsInAMillisecond ); - case Unit::Seconds: - return m_inNanoseconds / static_cast( s_nanosecondsInASecond ); - case Unit::Minutes: - return m_inNanoseconds / static_cast( s_nanosecondsInAMinute ); - default: - return static_cast( m_inNanoseconds ); - } - } - auto unitsAsString() const -> std::string { - switch( m_units ) { - case Unit::Nanoseconds: - return "ns"; - case Unit::Microseconds: - return "µs"; - case Unit::Milliseconds: - return "ms"; - case Unit::Seconds: - return "s"; - case Unit::Minutes: - return "m"; - default: - return "** internal error **"; - } - - } - friend auto operator << ( std::ostream& os, Duration const& duration ) -> std::ostream& { - return os << duration.value() << " " << duration.unitsAsString(); - } - }; - } // end anon namespace - - struct ConsoleReporter : StreamingReporterBase { - TablePrinter m_tablePrinter; - - ConsoleReporter( ReporterConfig const& config ) - : StreamingReporterBase( config ), - m_tablePrinter( config.stream(), - { - { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH-32, ColumnInfo::Left }, - { "iters", 8, ColumnInfo::Right }, - { "elapsed ns", 14, ColumnInfo::Right }, - { "average", 14, ColumnInfo::Right } - } ) - {} - ~ConsoleReporter() override; - static std::string getDescription() { - return "Reports test results as plain lines of text"; - } - - void noMatchingTestCases( std::string const& spec ) override { - stream << "No test cases matched '" << spec << '\'' << std::endl; - } - - void assertionStarting( AssertionInfo const& ) override { - } - - bool assertionEnded( AssertionStats const& _assertionStats ) 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; - - lazyPrint(); - - AssertionPrinter printer( stream, _assertionStats, includeResults ); - printer.print(); - stream << std::endl; - return true; - } - - void sectionStarting( SectionInfo const& _sectionInfo ) override { - m_headerPrinted = false; - StreamingReporterBase::sectionStarting( _sectionInfo ); - } - void sectionEnded( SectionStats const& _sectionStats ) override { - 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; - } - if( m_config->showDurations() == ShowDurations::Always ) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; - } - if( m_headerPrinted ) { - m_headerPrinted = false; - } - StreamingReporterBase::sectionEnded( _sectionStats ); - } - - void benchmarkStarting( BenchmarkInfo const& info ) override { - lazyPrintWithoutClosingBenchmarkTable(); - - auto nameCol = Column( info.name ).width( m_tablePrinter.columnInfos()[0].width-2 ); - - bool firstLine = true; - for( auto line : nameCol ) { - if( !firstLine ) - m_tablePrinter << ColumnBreak() << ColumnBreak() << ColumnBreak(); - else - firstLine = false; - - m_tablePrinter << line << ColumnBreak(); - } - } - void benchmarkEnded( BenchmarkStats const& stats ) override { - Duration average( stats.elapsedTimeInNanoseconds/stats.iterations ); - m_tablePrinter - << stats.iterations << ColumnBreak() - << stats.elapsedTimeInNanoseconds << ColumnBreak() - << average << ColumnBreak(); - } - - void testCaseEnded( TestCaseStats const& _testCaseStats ) override { - m_tablePrinter.close(); - StreamingReporterBase::testCaseEnded( _testCaseStats ); - m_headerPrinted = false; - } - void testGroupEnded( TestGroupStats const& _testGroupStats ) override { - if( currentGroupInfo.used ) { - printSummaryDivider(); - stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; - printTotals( _testGroupStats.totals ); - stream << '\n' << std::endl; - } - StreamingReporterBase::testGroupEnded( _testGroupStats ); - } - void testRunEnded( TestRunStats const& _testRunStats ) override { - printTotalsDivider( _testRunStats.totals ); - printTotals( _testRunStats.totals ); - stream << std::endl; - StreamingReporterBase::testRunEnded( _testRunStats ); - } - - private: - - class AssertionPrinter { - public: - AssertionPrinter& operator= ( AssertionPrinter const& ) = delete; - AssertionPrinter( AssertionPrinter const& ) = delete; - 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 print() const { - printSourceInfo(); - if( stats.totals.assertions.total() > 0 ) { - if( result.isOk() ) - stream << '\n'; - printResultType(); - printOriginalExpression(); - printReconstructedExpression(); - } - else { - stream << '\n'; - } - printMessage(); - } - - 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() << ": "; - } - - std::ostream& stream; - AssertionStats const& stats; - AssertionResult const& result; - Colour::Code colour; - std::string passOrFail; - std::string messageLabel; - std::string message; - std::vector messages; - bool printInfoMessages; - }; - - void lazyPrint() { - - m_tablePrinter.close(); - lazyPrintWithoutClosingBenchmarkTable(); - } - - void lazyPrintWithoutClosingBenchmarkTable() { - - if( !currentTestRunInfo.used ) - lazyPrintRunInfo(); - if( !currentGroupInfo.used ) - lazyPrintGroupInfo(); - - 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"; - - if( m_config->rngSeed() != 0 ) - stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; - - 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 ); - - 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 ); - } - - SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; - - if( !lineInfo.empty() ){ - stream << getLineOfChars<'-'>() << '\n'; - Colour colourGuard( Colour::FileName ); - stream << lineInfo << '\n'; - } - stream << getLineOfChars<'.'>() << '\n' << std::endl; - } - - void printClosedHeader( std::string const& _name ) { - printOpenHeader( _name ); - stream << getLineOfChars<'.'>() << '\n'; - } - void printOpenHeader( std::string const& _name ) { - stream << getLineOfChars<'-'>() << '\n'; - { - Colour colourGuard( Colour::Headers ); - printHeaderString( _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 ) { - 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'; - } - - struct SummaryColumn { - - 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( auto& oldRow : rows ) { - while( oldRow.size() < row.size() ) - oldRow = ' ' + oldRow; - while( oldRow.size() > row.size() ) - row = ' ' + row; - } - rows.push_back( row ); - return *this; - } - - std::string label; - Colour::Code colour; - std::vector rows; - - }; - - 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 { - - std::vector 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 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'; - } - - 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'; - } - void printSummaryDivider() { - stream << getLineOfChars<'-'>() << '\n'; - } - - private: - bool m_headerPrinted = false; - }; - - CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) - - ConsoleReporter::~ConsoleReporter() {} - -} // end namespace Catch - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif -// end catch_reporter_console.cpp -// start catch_reporter_junit.cpp - -#include - -#include -#include - -namespace Catch { - - namespace { - std::string getCurrentTimestamp() { - // Beware, this is not reentrant because of backward compatibility issues - // Also, UTC only, again because of backward compatibility (%z is C++11) - time_t rawtime; - std::time(&rawtime); - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); - -#ifdef _MSC_VER - std::tm timeInfo = {}; - gmtime_s(&timeInfo, &rawtime); -#else - std::tm* timeInfo; - timeInfo = std::gmtime(&rawtime); -#endif - - char timeStamp[timeStampSize]; - const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; - -#ifdef _MSC_VER - std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); -#else - std::strftime(timeStamp, timeStampSize, fmt, timeInfo); -#endif - return std::string(timeStamp); - } - - std::string fileNameTag(const std::vector &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(); - } - } - - class JunitReporter : public CumulativeReporterBase { - public: - JunitReporter( ReporterConfig const& _config ) - : CumulativeReporterBase( _config ), - xml( _config.stream() ) - { - m_reporterPrefs.shouldRedirectStdOut = true; - } - - ~JunitReporter() override; - - static std::string getDescription() { - return "Reports test results in an XML format that looks like Ant's junitreport target"; - } - - void noMatchingTestCases( std::string const& /*spec*/ ) override {} - - void testRunStarting( TestRunInfo const& runInfo ) override { - CumulativeReporterBase::testRunStarting( runInfo ); - xml.startElement( "testsuites" ); - } - - void testGroupStarting( GroupInfo const& groupInfo ) override { - suiteTimer.start(); - stdOutForSuite.str(""); - stdErrForSuite.str(""); - unexpectedExceptions = 0; - CumulativeReporterBase::testGroupStarting( groupInfo ); - } - - void testCaseStarting( TestCaseInfo const& testCaseInfo ) override { - m_okToFail = testCaseInfo.okToFail(); - } - bool assertionEnded( AssertionStats const& assertionStats ) override { - if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) - unexpectedExceptions++; - return CumulativeReporterBase::assertionEnded( assertionStats ); - } - - void testCaseEnded( TestCaseStats const& testCaseStats ) override { - stdOutForSuite << testCaseStats.stdOut; - stdErrForSuite << testCaseStats.stdErr; - CumulativeReporterBase::testCaseEnded( testCaseStats ); - } - - void testGroupEnded( TestGroupStats const& testGroupStats ) override { - double suiteTime = suiteTimer.getElapsedSeconds(); - CumulativeReporterBase::testGroupEnded( testGroupStats ); - writeGroup( *m_testGroups.back(), suiteTime ); - } - - void testRunEndedCumulative() override { - xml.endElement(); - } - - 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() ); - - // Write test cases - for( auto const& child : groupNode.children ) - writeTestCase( *child ); - - xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); - xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); - } - - void writeTestCase( TestCaseNode const& testCaseNode ) { - TestCaseStats const& stats = testCaseNode.value; - - // 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(); - - std::string className = stats.testInfo.className; - - if( className.empty() ) { - className = fileNameTag(stats.testInfo.tags); - if ( className.empty() ) - className = "global"; - } - - if ( !m_config->name().empty() ) - className = m_config->name() + "." + className; - - 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::Detail::stringify( 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( auto const& childNode : sectionNode.childSections ) - if( className.empty() ) - writeSection( name, "", *childNode ); - else - writeSection( className, name, *childNode ); - } - - void writeAssertions( SectionNode const& sectionNode ) { - for( auto const& assertion : sectionNode.assertions ) - writeAssertion( assertion ); - } - 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; - } - - XmlWriter::ScopedElement e = xml.scopedElement( elementName ); - - xml.writeAttribute( "message", result.getExpandedExpression() ); - xml.writeAttribute( "type", result.getTestMacroName() ); - - std::ostringstream oss; - if( !result.getMessage().empty() ) - oss << result.getMessage() << '\n'; - for( auto const& msg : stats.infoMessages ) - if( msg.type == ResultWas::Info ) - oss << msg.message << '\n'; - - oss << "at " << result.getSourceInfo(); - xml.writeText( oss.str(), false ); - } - } - - XmlWriter xml; - Timer suiteTimer; - std::ostringstream stdOutForSuite; - std::ostringstream stdErrForSuite; - unsigned int unexpectedExceptions = 0; - bool m_okToFail = false; - }; - - JunitReporter::~JunitReporter() {} - CATCH_REGISTER_REPORTER( "junit", JunitReporter ) - -} // end namespace Catch -// end catch_reporter_junit.cpp -// start catch_reporter_multi.cpp - -namespace Catch { - - void MultipleReporters::add( IStreamingReporterPtr&& reporter ) { - m_reporters.push_back( std::move( reporter ) ); - } - - ReporterPreferences MultipleReporters::getPreferences() const { - return m_reporters[0]->getPreferences(); - } - - std::set MultipleReporters::getSupportedVerbosities() { - return std::set{ }; - } - - void MultipleReporters::noMatchingTestCases( std::string const& spec ) { - for( auto const& reporter : m_reporters ) - reporter->noMatchingTestCases( spec ); - } - - void MultipleReporters::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { - for( auto const& reporter : m_reporters ) - reporter->benchmarkStarting( benchmarkInfo ); - } - void MultipleReporters::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { - for( auto const& reporter : m_reporters ) - reporter->benchmarkEnded( benchmarkStats ); - } - - void MultipleReporters::testRunStarting( TestRunInfo const& testRunInfo ) { - for( auto const& reporter : m_reporters ) - reporter->testRunStarting( testRunInfo ); - } - - void MultipleReporters::testGroupStarting( GroupInfo const& groupInfo ) { - for( auto const& reporter : m_reporters ) - reporter->testGroupStarting( groupInfo ); - } - - void MultipleReporters::testCaseStarting( TestCaseInfo const& testInfo ) { - for( auto const& reporter : m_reporters ) - reporter->testCaseStarting( testInfo ); - } - - void MultipleReporters::sectionStarting( SectionInfo const& sectionInfo ) { - for( auto const& reporter : m_reporters ) - reporter->sectionStarting( sectionInfo ); - } - - void MultipleReporters::assertionStarting( AssertionInfo const& assertionInfo ) { - for( auto const& reporter : m_reporters ) - reporter->assertionStarting( assertionInfo ); - } - - // The return value indicates if the messages buffer should be cleared: - bool MultipleReporters::assertionEnded( AssertionStats const& assertionStats ) { - bool clearBuffer = false; - for( auto const& reporter : m_reporters ) - clearBuffer |= reporter->assertionEnded( assertionStats ); - return clearBuffer; - } - - void MultipleReporters::sectionEnded( SectionStats const& sectionStats ) { - for( auto const& reporter : m_reporters ) - reporter->sectionEnded( sectionStats ); - } - - void MultipleReporters::testCaseEnded( TestCaseStats const& testCaseStats ) { - for( auto const& reporter : m_reporters ) - reporter->testCaseEnded( testCaseStats ); - } - - void MultipleReporters::testGroupEnded( TestGroupStats const& testGroupStats ) { - for( auto const& reporter : m_reporters ) - reporter->testGroupEnded( testGroupStats ); - } - - void MultipleReporters::testRunEnded( TestRunStats const& testRunStats ) { - for( auto const& reporter : m_reporters ) - reporter->testRunEnded( testRunStats ); - } - - void MultipleReporters::skipTest( TestCaseInfo const& testInfo ) { - for( auto const& reporter : m_reporters ) - reporter->skipTest( testInfo ); - } - - bool MultipleReporters::isMulti() const { - return true; - } - -} // end namespace Catch -// end catch_reporter_multi.cpp -// start catch_reporter_xml.cpp - -#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 { - class XmlReporter : public StreamingReporterBase { - public: - XmlReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ), - m_xml(_config.stream()) - { - m_reporterPrefs.shouldRedirectStdOut = true; - } - - ~XmlReporter() override; - - static std::string getDescription() { - return "Reports test results as an XML document"; - } - - virtual std::string getStylesheetRef() const { - return std::string(); - } - - void writeSourceInfo( SourceLineInfo const& sourceInfo ) { - m_xml - .writeAttribute( "filename", sourceInfo.file ) - .writeAttribute( "line", sourceInfo.line ); - } - - public: // StreamingReporterBase - - void noMatchingTestCases( std::string const& s ) override { - StreamingReporterBase::noMatchingTestCases( s ); - } - - void testRunStarting( TestRunInfo const& testInfo ) 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() ); - } - - void testGroupStarting( GroupInfo const& groupInfo ) override { - StreamingReporterBase::testGroupStarting( groupInfo ); - m_xml.startElement( "Group" ) - .writeAttribute( "name", groupInfo.name ); - } - - void testCaseStarting( TestCaseInfo const& testInfo ) override { - StreamingReporterBase::testCaseStarting(testInfo); - m_xml.startElement( "TestCase" ) - .writeAttribute( "name", trim( testInfo.name ) ) - .writeAttribute( "description", testInfo.description ) - .writeAttribute( "tags", testInfo.tagsAsString() ); - - writeSourceInfo( testInfo.lineInfo ); - - if ( m_config->showDurations() == ShowDurations::Always ) - m_testCaseTimer.start(); - m_xml.ensureTagClosed(); - } - - void sectionStarting( SectionInfo const& sectionInfo ) 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(); - } - } - - void assertionStarting( AssertionInfo const& ) override { } - - bool assertionEnded( AssertionStats const& assertionStats ) override { - - AssertionResult const& result = assertionStats.assertionResult; - - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - if( includeResults ) { - // Print any info messages in tags. - for( auto const& msg : assertionStats.infoMessages ) { - if( msg.type == ResultWas::Info ) { - m_xml.scopedElement( "Info" ) - .writeText( msg.message ); - } else if ( msg.type == ResultWas::Warning ) { - m_xml.scopedElement( "Warning" ) - .writeText( msg.message ); - } - } - } - - // Drop out if result was successful but we're not printing them. - if( !includeResults && result.getResultType() != ResultWas::Warning ) - return true; - - // Print the expression if there is one. - if( result.hasExpression() ) { - m_xml.startElement( "Expression" ) - .writeAttribute( "success", result.succeeded() ) - .writeAttribute( "type", result.getTestMacroName() ); - - writeSourceInfo( result.getSourceInfo() ); - - m_xml.scopedElement( "Original" ) - .writeText( result.getExpression() ); - m_xml.scopedElement( "Expanded" ) - .writeText( result.getExpandedExpression() ); - } - - // 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( result.hasExpression() ) - m_xml.endElement(); - - return true; - } - - void sectionEnded( SectionStats const& sectionStats ) 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 ); - - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); - - m_xml.endElement(); - } - } - - void testCaseEnded( TestCaseStats const& testCaseStats ) override { - StreamingReporterBase::testCaseEnded( testCaseStats ); - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); - e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); - - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); - - 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 ); - - m_xml.endElement(); - } - - void testGroupEnded( TestGroupStats const& testGroupStats ) 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 testRunEnded( TestRunStats const& testRunStats ) 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(); - } - - private: - Timer m_testCaseTimer; - XmlWriter m_xml; - int m_sectionDepth = 0; - }; - - XmlReporter::~XmlReporter() {} - 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 -// start catch_default_main.hpp - -#ifndef __OBJC__ - -#if defined(WIN32) && 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 -// Standard C/C++ main entry point -int main (int argc, char * argv[]) { -#endif - - return Catch::Session().run( argc, argv ); -} - -#else // __OBJC__ - -// Objective-C entry point -int main (int argc, char * const argv[]) { -#if !CATCH_ARC_ENABLED - NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; -#endif - - Catch::registerTestMethods(); - int result = Catch::Session().run( argc, (char**)argv ); - -#if !CATCH_ARC_ENABLED - [pool drain]; -#endif - - return result; -} - -#endif // __OBJC__ - -// end catch_default_main.hpp -#endif - -#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 - -#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( ... ) 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_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_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 ) - -#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) -#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) -#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) - -#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_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() - -// "BDD-style" convenience wrappers -#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) -#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) -#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 ) - -// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required -#else - -#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( ... ) 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_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_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 ) - -#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) -#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) -#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) - -#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 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() - -#endif - -#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) - -// "BDD-style" convenience wrappers -#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) -#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) - -#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 ) - -using Catch::Detail::Approx; - -#else -////// -// 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_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_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____ )) - -// "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_WHEN( desc ) -#define CATCH_AND_WHEN( desc ) -#define CATCH_THEN( desc ) -#define CATCH_AND_THEN( desc ) - -// 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 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 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____ )) - -#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 WHEN( desc ) -#define AND_WHEN( desc ) -#define THEN( desc ) -#define AND_THEN( desc ) - -using Catch::Detail::Approx; - -#endif - -// start catch_reenable_warnings.h - - -#ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(pop) -# else -# pragma clang diagnostic pop -# endif -#elif defined __GNUC__ -# pragma GCC diagnostic pop -#endif - -// end catch_reenable_warnings.h -// end catch.hpp -#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED diff --git a/tests/catch/main.cpp b/tests/catch/main.cpp deleted file mode 100644 index 52c6fad..0000000 --- a/tests/catch/main.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2018 OpenCyphal Development Team - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * Contributors: https://github.com/OpenCyphal/libcanard/contributors - */ - -// For speedy compilation, the file that contains CATCH_CONFIG_MAIN should not contain any actual tests. -// https://github.com/catchorg/Catch2/blob/master/docs/slow-compiles.md -#define CATCH_CONFIG_MAIN -#include "catch.hpp" // NOLINT diff --git a/tests/exposed.hpp b/tests/exposed.hpp deleted file mode 100644 index 5dd42c8..0000000 --- a/tests/exposed.hpp +++ /dev/null @@ -1,132 +0,0 @@ -// This software is distributed under the terms of the MIT License. -// Copyright (c) 2016 Cyphal Development Team. -/// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -#pragma once - -#include "../libudpard/udpard.h" -#include -#include -#include -#include - -/// Definitions that are not exposed by the library but that are needed for testing. -/// Please keep them in sync with the library by manually updating as necessary. -namespace exposed -{ -using TransferCRC = std::uint32_t; - -struct TxItem final : UdpardTxQueueItem -{ - [[nodiscard]] auto getPayloadByte(const std::size_t offset) const -> std::uint8_t - { - return reinterpret_cast(frame.payload)[offset + sizeof(UdpardFrameHeader)]; - } - - [[nodiscard]] auto getFrameHeader() const - { - if (frame.payload_size < sizeof(UdpardFrameHeader)) - { - // Can't use REQUIRE because it is not thread-safe. - throw std::logic_error( - "Can't get the frame header because the payload is not large enough to hold the header."); - } - return reinterpret_cast(frame.payload); - } - - [[nodiscard]] auto isStartOfTransfer() const - { - return (getFrameHeader()->frame_index_eot & ((1U << 31U) - 1U)) == 0; - } - [[nodiscard]] auto isEndOfTransfer() const { return (getFrameHeader()->frame_index_eot >> 31U) == 1; } - - ~TxItem() = default; - TxItem(const TxItem&) = delete; - TxItem(const TxItem&&) = delete; - auto operator=(const TxItem&) -> TxItem& = delete; - auto operator=(const TxItem&&) -> TxItem& = delete; -}; - -struct RxSession -{ - UdpardMicrosecond transfer_timestamp_usec = std::numeric_limits::max(); - std::size_t total_payload_size = 0U; - std::size_t payload_size = 0U; - std::uint8_t* payload = nullptr; - TransferCRC calculated_crc = 0U; - UdpardTransferID transfer_id = std::numeric_limits::max(); - std::uint8_t redundant_transport_index = std::numeric_limits::max(); -}; - -struct RxFrameModel -{ - UdpardMicrosecond timestamp_usec = std::numeric_limits::max(); - UdpardHeaderVersion version = 1U; - UdpardPriority priority = UdpardPriorityOptional; - UdpardTransferKind transfer_kind = UdpardTransferKindMessage; - UdpardPortID port_id = std::numeric_limits::max(); - UdpardNodeID source_node_id = UDPARD_NODE_ID_UNSET; - UdpardNodeID destination_node_id = UDPARD_NODE_ID_UNSET; - UdpardTransferID transfer_id = std::numeric_limits::max(); - bool start_of_transfer = false; - bool end_of_transfer = false; - std::size_t payload_size = 0U; - const std::uint8_t* payload = nullptr; - uint32_t frame_index = 0U; -}; - -// Extern C effectively discards the outer namespaces. -extern "C" { - -auto crcAdd(const std::uint32_t crc, const std::size_t size, const void* const bytes) -> std::uint32_t; - -auto cyphalHeaderCrcAdd(const std::uint16_t crc, const std::size_t size, const void* const bytes) -> std::uint16_t; - -auto crcValue(const std::uint32_t crc) -> std::uint32_t; - -auto txMakeMessageSessionSpecifier(const UdpardPortID subject_id, - const UdpardNodeID src_node_id, - const UdpardIPv4Addr local_node_addr, - UdpardSessionSpecifier* const out_spec) -> std::uint32_t; - -auto txMakeServiceSessionSpecifier(const UdpardPortID service_id, - const UdpardNodeID src_node_id, - const UdpardIPv4Addr local_node_addr, - UdpardSessionSpecifier* const out_spec) -> std::uint32_t; - -auto adjustPresentationLayerMTU(const std::size_t mtu_bytes) -> std::size_t; - -auto txMakeSessionSpecifier(const UdpardTransferMetadata* const tr, - const UdpardNodeID local_node_id, - const UdpardIPv4Addr local_node_addr, - UdpardSessionSpecifier* const spec) -> std::int32_t; - -void txMakeFrameHeader(UdpardFrameHeader* const header, - const std::uint8_t priority, - const UdpardTransferID transfer_id, - const bool end_of_transfer, - const std::uint32_t frame_index); - -auto txRoundFramePayloadSizeUp(const std::size_t x) -> std::size_t; - -auto rxTryParseFrame(const UdpardMicrosecond timestamp_usec, - UdpardFrame* const frame, - RxFrameModel* const out) -> bool; - -auto rxSessionWritePayload(UdpardInstance* const ins, - RxSession* const rxs, - const std::size_t extent, - const std::size_t payload_size, - const void* const payload) -> std::int8_t; - -void rxSessionRestart(UdpardInstance* const ins, RxSession* const rxs); - -auto rxSessionUpdate(UdpardInstance* const ins, - RxSession* const rxs, - const RxFrameModel* const frame, - const std::uint8_t redundant_transport_index, - const UdpardMicrosecond transfer_id_timeout_usec, - const std::size_t extent, - UdpardRxTransfer* const out_transfer) -> std::int8_t; -} -} // namespace exposed diff --git a/tests/helpers.hpp b/tests/helpers.hpp index a05f29f..1d49e4b 100644 --- a/tests/helpers.hpp +++ b/tests/helpers.hpp @@ -4,10 +4,10 @@ #pragma once -#include "../libudpard/udpard.h" -#include "exposed.hpp" +#include // Shall always be included first. #include #include +#include #include #include #include @@ -15,6 +15,7 @@ #include #include #include +#include #if !(defined(UDPARD_VERSION_MAJOR) && defined(UDPARD_VERSION_MINOR)) # error "Library version not defined" @@ -28,16 +29,17 @@ namespace helpers { namespace dummy_allocator { -inline auto allocate(UdpardInstance* const ins, const std::size_t amount) -> void* +inline auto allocate(UdpardMemoryResource* const ins, const std::size_t amount) -> void* { (void) ins; (void) amount; return nullptr; } -inline void free(UdpardInstance* const ins, void* const pointer) +inline void free(UdpardMemoryResource* const ins, const size_t size, void* const pointer) { (void) ins; + (void) size; (void) pointer; } } // namespace dummy_allocator @@ -46,11 +48,11 @@ inline void free(UdpardInstance* const ins, void* const pointer) template inline auto getRandomNatural(const T upper_open) -> T { - return static_cast(static_cast(std::rand()) % upper_open); // NOLINT + return static_cast(static_cast(std::rand()) % upper_open); } template -static inline void traverse(const UdpardTreeNode* const root, const F& fun) // NOLINT recursion +inline void traverse(const UdpardTreeNode* const root, const F& fun) { if (root != nullptr) { @@ -62,6 +64,7 @@ static inline void traverse(const UdpardTreeNode* const root, const F& fun) // /// An allocator that sits on top of the standard malloc() providing additional testing capabilities. /// It allows the user to specify the maximum amount of memory that can be allocated; further requests will emulate OOM. +/// It also performs correctness checks on the memory use. class TestAllocator { public: @@ -73,7 +76,7 @@ class TestAllocator virtual ~TestAllocator() { - std::unique_lock locker(lock_); + const std::unique_lock locker(lock_); for (const auto& pair : allocated_) { // Clang-tidy complains about manual memory management. Suppressed because we need it for testing purposes. @@ -83,8 +86,8 @@ class TestAllocator [[nodiscard]] auto allocate(const std::size_t amount) -> void* { - std::unique_lock locker(lock_); - std::uint8_t* p = nullptr; + const std::unique_lock locker(lock_); + std::uint8_t* p = nullptr; if ((amount > 0U) && ((getTotalAllocatedAmount() + amount) <= ceiling_)) { const auto amount_with_canaries = amount + canary_.size() * 2U; @@ -103,42 +106,43 @@ class TestAllocator return p; } - void deallocate(void* const user_pointer) + void free(const std::size_t size, void* const pointer) { - if (user_pointer != nullptr) + (void) size; // TODO FIXME ensure the size passed to this function is correct. + if (pointer != nullptr) { - std::unique_lock locker(lock_); - const auto it = allocated_.find(static_cast(user_pointer)); + const std::unique_lock locker(lock_); + const auto it = allocated_.find(static_cast(pointer)); if (it == std::end(allocated_)) // Catch an attempt to deallocate memory that is not allocated. { throw std::logic_error("Attempted to deallocate memory that was never allocated; ptr=" + - std::to_string(reinterpret_cast(user_pointer))); + std::to_string(reinterpret_cast(pointer))); } const auto [p, amount] = *it; if ((0 != std::memcmp(p - canary_.size(), canary_.begin(), canary_.size())) || (0 != std::memcmp(p + amount, canary_.begin(), canary_.size()))) { throw std::logic_error("Dead canary detected at ptr=" + - std::to_string(reinterpret_cast(user_pointer))); + std::to_string(reinterpret_cast(pointer))); } std::generate_n(p - canary_.size(), // Damage the memory to make sure it's not used after deallocation. amount + canary_.size() * 2U, []() { return static_cast(getRandomNatural(256U)); }); - std::free(p - canary_.size()); // NOLINT we require manual memory management here. + std::free(p - canary_.size()); allocated_.erase(it); } } [[nodiscard]] auto getNumAllocatedFragments() const { - std::unique_lock locker(lock_); + const std::unique_lock locker(lock_); return std::size(allocated_); } [[nodiscard]] auto getTotalAllocatedAmount() const -> std::size_t { - std::unique_lock locker(lock_); - std::size_t out = 0U; + const std::unique_lock locker(lock_); + std::size_t out = 0U; for (const auto& pair : allocated_) { out += pair.second; @@ -164,194 +168,4 @@ class TestAllocator std::atomic ceiling_ = std::numeric_limits::max(); }; -/// An enhancing wrapper over the library to remove boilerplate from the tests. -class Instance -{ -public: - Instance() { udpard_.user_reference = this; } - - virtual ~Instance() = default; - - Instance(const Instance&) = delete; - Instance(const Instance&&) = delete; - auto operator=(const Instance&) -> Instance& = delete; - auto operator=(const Instance&&) -> Instance& = delete; - - [[nodiscard]] auto rxAccept(const UdpardMicrosecond timestamp_usec, - UdpardFrame& frame, - const uint8_t redundant_transport_index, - UdpardSessionSpecifier& /*specifier*/, - UdpardRxTransfer& out_transfer, - UdpardRxSubscription** const out_subscription) - { - return udpardRxAccept(&udpard_, - timestamp_usec, - &frame, - redundant_transport_index, - &out_transfer, - out_subscription); - } - - [[nodiscard]] auto rxSubscribe(const UdpardTransferKind transfer_kind, - const UdpardPortID port_id, - const std::size_t extent, - const UdpardMicrosecond transfer_id_timeout_usec, - UdpardRxSubscription& out_subscription) - { - return udpardRxSubscribe(&udpard_, transfer_kind, port_id, extent, transfer_id_timeout_usec, &out_subscription); - } - - [[nodiscard]] auto rxUnsubscribe(const UdpardTransferKind transfer_kind, const UdpardPortID port_id) - { - return udpardRxUnsubscribe(&udpard_, transfer_kind, port_id); - } - - /// The items are sorted by port-ID. - [[nodiscard]] auto getSubs(const UdpardTransferKind tk) const -> std::vector - { - std::vector out; - traverse(udpard_.rx_subscriptions[tk], [&](const UdpardTreeNode* const item) { - out.push_back(reinterpret_cast(item)); - }); - return out; - } - [[nodiscard]] auto getMessageSubs() const { return getSubs(UdpardTransferKindMessage); } - [[nodiscard]] auto getResponseSubs() const { return getSubs(UdpardTransferKindResponse); } - [[nodiscard]] auto getRequestSubs() const { return getSubs(UdpardTransferKindRequest); } - - [[nodiscard]] auto getNodeID() const { return udpard_.node_id; } - void setNodeID(const std::uint8_t x) { udpard_.node_id = x; } - - [[nodiscard]] auto getNodeAddr() const { return udpard_.local_ip_addr; } - void setNodeAddr(const std::uint32_t x) { udpard_.local_ip_addr = x; } - - [[nodiscard]] auto getAllocator() -> TestAllocator& { return allocator_; } - - [[nodiscard]] auto getInstance() -> UdpardInstance& { return udpard_; } - [[nodiscard]] auto getInstance() const -> const UdpardInstance& { return udpard_; } - -private: - static auto trampolineAllocate(UdpardInstance* const ins, const std::size_t amount) -> void* - { - auto* p = reinterpret_cast(ins->user_reference); - return p->allocator_.allocate(amount); - } - - static void trampolineDeallocate(UdpardInstance* const ins, void* const pointer) - { - auto* p = reinterpret_cast(ins->user_reference); - p->allocator_.deallocate(pointer); - } - - UdpardInstance udpard_ = udpardInit(&Instance::trampolineAllocate, &Instance::trampolineDeallocate); - TestAllocator allocator_; -}; - -class TxQueue -{ -public: - explicit TxQueue(const std::size_t capacity, const std::size_t mtu_bytes) : que_(udpardTxInit(capacity, mtu_bytes)) - { - enforce(que_.user_reference == nullptr, "Incorrect initialization of the user reference in TxQueue"); - enforce(que_.mtu_bytes == mtu_bytes, "Incorrect MTU"); - que_.user_reference = this; // This is simply to ensure it is not overwritten unexpectedly. - checkInvariants(); - } - virtual ~TxQueue() = default; - - TxQueue(const TxQueue&) = delete; - TxQueue(TxQueue&&) = delete; - auto operator=(const TxQueue&) -> TxQueue& = delete; - auto operator=(TxQueue&&) -> TxQueue& = delete; - - [[nodiscard]] auto getMTU() const { return que_.mtu_bytes; } - void setMTU(const std::size_t x) { que_.mtu_bytes = x; } - - [[nodiscard]] auto push(UdpardInstance* const ins, - const UdpardMicrosecond transmission_deadline_usec, - const UdpardTransferMetadata& metadata, - const size_t payload_size, - const void* const payload) - { - checkInvariants(); - const auto size_before = que_.size; - const auto ret = udpardTxPush(&que_, ins, transmission_deadline_usec, &metadata, payload_size, payload); - const auto num_added = static_cast(ret); - enforce((ret < 0) || ((size_before + num_added) == que_.size), - "Unexpected size change after push"); - checkInvariants(); - return ret; - } - - [[nodiscard]] auto peek() const -> const exposed::TxItem* - { - checkInvariants(); - const auto before = que_.size; - const auto* const ret = udpardTxPeek(&que_); - enforce(((ret == nullptr) ? (before == 0) : (before > 0)) && (que_.size == before), "Bad peek"); - checkInvariants(); - return static_cast(ret); // NOLINT static downcast - } - - [[nodiscard]] auto pop(const UdpardTxQueueItem* const which) -> exposed::TxItem* - { - checkInvariants(); - const auto size_before = que_.size; - const auto* volatile pk = peek(); - auto* out = udpardTxPop(&que_, which); - enforce(pk == out, "Peek/pop pointer mismatch"); - if (out == nullptr) - { - enforce((size_before == 0) && (que_.size == 0), "Bad empty pop"); - } - else - { - enforce((size_before > 0) && (que_.size == (size_before - 1U)), "Bad non-empty pop"); - } - checkInvariants(); - return static_cast(out); // NOLINT static downcast - } - - [[nodiscard]] auto getSize() const - { - std::size_t out = 0; - traverse(que_.root, [&](auto* _) { - (void) _; - out++; - }); - enforce(que_.size == out, "Size miscalculation"); - return out; - } - - [[nodiscard]] auto linearize() const -> std::vector - { - std::vector out; - traverse(que_.root, [&](const UdpardTreeNode* const item) { - out.push_back(reinterpret_cast(item)); - }); - enforce(out.size() == getSize(), "Internal error"); - return out; - } - - [[nodiscard]] auto getInstance() -> UdpardTxQueue& { return que_; } - [[nodiscard]] auto getInstance() const -> const UdpardTxQueue& { return que_; } - -private: - static void enforce(const bool expect_true, const std::string& message) - { - if (!expect_true) - { - throw std::logic_error("TxQueue invariant violation: " + message); - } - } - - void checkInvariants() const - { - enforce(que_.user_reference == this, "User reference damaged"); - enforce(que_.size == getSize(), "Size miscalculation"); - } - - UdpardTxQueue que_; -}; - } // namespace helpers diff --git a/tests/test_private_cavl.cpp b/tests/test_private_cavl.cpp index 1cabfe3..b442fa8 100644 --- a/tests/test_private_cavl.cpp +++ b/tests/test_private_cavl.cpp @@ -2,10 +2,8 @@ // Copyright (c) 2016-2020 OpenCyphal Development Team. // These tests have been adapted from the Cavl test suite that you can find at https://github.com/pavel-kirienko/cavl -//#include -//#include "catch.hpp" -#include "../libudpard/cavl.h" -#include "catch/catch.hpp" +#include <_udpard_cavl.h> +#include #include #include #include @@ -43,8 +41,8 @@ struct Node final : Cavl ((check_ri == nullptr) || (check_ri->up == this)); } - auto min() -> Node* { return reinterpret_cast(cavlFindExtremum(this, false)); } - auto max() -> Node* { return reinterpret_cast(cavlFindExtremum(this, true)); } + auto min() -> Node* { return static_cast(cavlFindExtremum(this, false)); } + auto max() -> Node* { return static_cast(cavlFindExtremum(this, true)); } auto operator=(const Cavl& cv) -> Node& { @@ -64,7 +62,7 @@ auto search(Node** const root, const Predicate& predicate, const Factory& fac static auto callPredicate(void* const user_reference, const Cavl* const node) -> std::int8_t { - const auto ret = static_cast(user_reference)->predicate(reinterpret_cast&>(*node)); + const auto ret = static_cast(user_reference)->predicate(static_cast&>(*node)); if (ret > 0) { return 1; @@ -82,7 +80,7 @@ auto search(Node** const root, const Predicate& predicate, const Factory& fac } } refs{predicate, factory}; Cavl* const out = cavlSearch(reinterpret_cast(root), &refs, &Refs::callPredicate, &Refs::callFactory); - return reinterpret_cast*>(out); + return static_cast*>(out); } template auto search(Node** const root, const Predicate& predicate) -> Node* @@ -100,18 +98,18 @@ void remove(Node** const root, const Node* const n) template auto getHeight(const Node* const n) -> std::uint8_t // NOLINT recursion { - return (n != nullptr) ? std::uint8_t(1U + std::max(getHeight(reinterpret_cast*>(n->lr[0])), - getHeight(reinterpret_cast*>(n->lr[1])))) + return (n != nullptr) ? static_cast(1U + std::max(getHeight(static_cast*>(n->lr[0])), + getHeight(static_cast*>(n->lr[1])))) : 0; } template void print(const Node* const nd, const std::uint8_t depth = 0, const char marker = 'T') // NOLINT recursion { - REQUIRE(10 > getHeight(nd)); // Fail early for malformed cyclic trees, do not overwhelm stdout. + ASSERT_TRUE(10 > getHeight(nd)); // Fail early for malformed cyclic trees, do not overwhelm stdout. if (nd != nullptr) { - print(reinterpret_cast*>(nd->lr[0]), static_cast(depth + 1U), 'L'); + print(static_cast*>(nd->lr[0]), static_cast(depth + 1U), 'L'); for (std::uint16_t i = 1U; i < depth; i++) { std::cout << " "; @@ -130,7 +128,7 @@ void print(const Node* const nd, const std::uint8_t depth = 0, const char mar } std::cout << marker << "=" << static_cast(nd->value) // << " [" << static_cast(nd->bf) << "]" << std::endl; - print(reinterpret_cast*>(nd->lr[1]), static_cast(depth + 1U), 'R'); + print(static_cast*>(nd->lr[1]), static_cast(depth + 1U), 'R'); } } @@ -139,9 +137,9 @@ void traverse(Node* const root, const Visitor& visitor) // NOLINT recursion nee { if (root != nullptr) { - traverse(reinterpret_cast(root->lr[!Ascending]), visitor); + traverse(static_cast(root->lr[!Ascending]), visitor); visitor(root); - traverse(reinterpret_cast(root->lr[Ascending]), visitor); + traverse(static_cast(root->lr[Ascending]), visitor); } } @@ -170,7 +168,7 @@ auto findBrokenAncestry(const Node* const n, const Cavl* const parent = nullp { for (auto* ch : n->lr) // NOLINT array decay due to C API { - if (const Node* p = findBrokenAncestry(reinterpret_cast*>(ch), n)) + if (const Node* p = findBrokenAncestry(static_cast*>(ch), n)) { return p; } @@ -189,15 +187,15 @@ auto findBrokenBalanceFactor(const Node* const n) -> const Cavl* // NOLINT r { return n; } - const std::int16_t hl = getHeight(reinterpret_cast*>(n->lr[0])); - const std::int16_t hr = getHeight(reinterpret_cast*>(n->lr[1])); + const std::int16_t hl = getHeight(static_cast*>(n->lr[0])); + const std::int16_t hr = getHeight(static_cast*>(n->lr[1])); if (n->bf != (hr - hl)) { return n; } for (auto* ch : n->lr) // NOLINT array decay due to C API { - if (const Cavl* p = findBrokenBalanceFactor(reinterpret_cast*>(ch))) + if (const Cavl* p = findBrokenBalanceFactor(static_cast*>(ch))) { return p; } @@ -207,7 +205,7 @@ auto findBrokenBalanceFactor(const Node* const n) -> const Cavl* // NOLINT r } } // namespace -TEST_CASE("CheckAscension") +TEST(Cavl, CheckAscension) { using N = Node; N t{2}; @@ -218,20 +216,23 @@ TEST_CASE("CheckAscension") t.lr[0] = &l; t.lr[1] = &r; r.lr[1] = &rr; - REQUIRE(4 == checkAscension(&t)); - REQUIRE(3 == getHeight(&t)); + ASSERT_TRUE(4 == checkAscension(&t)); + ASSERT_TRUE(3 == getHeight(&t)); // Break the arrangement and make sure the breakage is detected. t.lr[1] = &l; t.lr[0] = &r; - REQUIRE(4 != checkAscension(&t)); - REQUIRE(3 == getHeight(&t)); - REQUIRE(&t == findBrokenBalanceFactor(&t)); // All zeros, incorrect. + ASSERT_TRUE(4 != checkAscension(&t)); + ASSERT_TRUE(3 == getHeight(&t)); + ASSERT_TRUE(&t == findBrokenBalanceFactor(&t)); // All zeros, incorrect. r.lr[1] = nullptr; - REQUIRE(2 == getHeight(&t)); - REQUIRE(nullptr == findBrokenBalanceFactor(&t)); // Balanced now as we removed one node. + std::cout << __LINE__ << ": " << static_cast(getHeight(&t)) << std::endl; + print(&t); + std::cout << __LINE__ << ": " << static_cast(getHeight(&t)) << std::endl; + ASSERT_EQ(2, getHeight(&t)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t)); // Balanced now as we removed one node. } -TEST_CASE("Rotation") +TEST(Cavl, Rotation) { using N = Node; // Original state: @@ -255,29 +256,29 @@ TEST_CASE("Rotation") a.up = &x; std::cout << "Before rotation:\n"; - REQUIRE(nullptr == findBrokenAncestry(&x)); + ASSERT_TRUE(nullptr == findBrokenAncestry(&x)); print(&x); std::cout << "After left rotation:\n"; cavlPrivateRotate(&x, false); // z is now the root - REQUIRE(nullptr == findBrokenAncestry(&z)); + ASSERT_TRUE(nullptr == findBrokenAncestry(&z)); print(&z); - REQUIRE(&a == x.lr[0]); - REQUIRE(&b == x.lr[1]); - REQUIRE(&x == z.lr[0]); - REQUIRE(&c == z.lr[1]); + ASSERT_TRUE(&a == x.lr[0]); + ASSERT_TRUE(&b == x.lr[1]); + ASSERT_TRUE(&x == z.lr[0]); + ASSERT_TRUE(&c == z.lr[1]); std::cout << "After right rotation, back into the original configuration:\n"; cavlPrivateRotate(&z, true); // x is now the root - REQUIRE(nullptr == findBrokenAncestry(&x)); + ASSERT_TRUE(nullptr == findBrokenAncestry(&x)); print(&x); - REQUIRE(&a == x.lr[0]); - REQUIRE(&z == x.lr[1]); - REQUIRE(&b == z.lr[0]); - REQUIRE(&c == z.lr[1]); + ASSERT_TRUE(&a == x.lr[0]); + ASSERT_TRUE(&z == x.lr[1]); + ASSERT_TRUE(&b == z.lr[0]); + ASSERT_TRUE(&c == z.lr[1]); } -TEST_CASE("BalancingA") +TEST(Cavl, BalancingA) { using N = Node; // Double left-right rotation. @@ -302,32 +303,32 @@ TEST_CASE("BalancingA") y.lr[0] = &f; y.lr[1] = &g; print(&x); - REQUIRE(nullptr == findBrokenAncestry(&x)); - REQUIRE(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology - REQUIRE(-1 == x.bf); - REQUIRE(&z == cavlPrivateAdjustBalance(&z, true)); // bf = +1, same topology - REQUIRE(+1 == z.bf); - REQUIRE(&y == cavlPrivateAdjustBalance(&x, false)); // bf = -2, rotation needed + ASSERT_TRUE(nullptr == findBrokenAncestry(&x)); + ASSERT_TRUE(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology + ASSERT_TRUE(-1 == x.bf); + ASSERT_TRUE(&z == cavlPrivateAdjustBalance(&z, true)); // bf = +1, same topology + ASSERT_TRUE(+1 == z.bf); + ASSERT_TRUE(&y == cavlPrivateAdjustBalance(&x, false)); // bf = -2, rotation needed print(&y); - REQUIRE(nullptr == findBrokenBalanceFactor(&y)); // Should be balanced now. - REQUIRE(nullptr == findBrokenAncestry(&y)); - REQUIRE(&z == y.lr[0]); - REQUIRE(&x == y.lr[1]); - REQUIRE(&d == z.lr[0]); - REQUIRE(&f == z.lr[1]); - REQUIRE(&g == x.lr[0]); - REQUIRE(&c == x.lr[1]); - REQUIRE(Zz == d.lr[0]); - REQUIRE(Zz == d.lr[1]); - REQUIRE(Zz == f.lr[0]); - REQUIRE(Zz == f.lr[1]); - REQUIRE(Zz == g.lr[0]); - REQUIRE(Zz == g.lr[1]); - REQUIRE(Zz == c.lr[0]); - REQUIRE(Zz == c.lr[1]); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&y)); // Should be balanced now. + ASSERT_TRUE(nullptr == findBrokenAncestry(&y)); + ASSERT_TRUE(&z == y.lr[0]); + ASSERT_TRUE(&x == y.lr[1]); + ASSERT_TRUE(&d == z.lr[0]); + ASSERT_TRUE(&f == z.lr[1]); + ASSERT_TRUE(&g == x.lr[0]); + ASSERT_TRUE(&c == x.lr[1]); + ASSERT_TRUE(Zz == d.lr[0]); + ASSERT_TRUE(Zz == d.lr[1]); + ASSERT_TRUE(Zz == f.lr[0]); + ASSERT_TRUE(Zz == f.lr[1]); + ASSERT_TRUE(Zz == g.lr[0]); + ASSERT_TRUE(Zz == g.lr[1]); + ASSERT_TRUE(Zz == c.lr[0]); + ASSERT_TRUE(Zz == c.lr[1]); } -TEST_CASE("BalancingB") +TEST(Cavl, BalancingB) { using N = Node; // Without F the handling of Z and Y is more complex; Z flips the sign of its balance factor: @@ -351,32 +352,32 @@ TEST_CASE("BalancingB") y = {{&z, {Zz, &g}, 0}, 5}; // bf = +1 g = {{&y, {Zz, Zz}, 0}, 7}; print(&x); - REQUIRE(nullptr == findBrokenAncestry(&x)); - REQUIRE(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology - REQUIRE(-1 == x.bf); - REQUIRE(&z == cavlPrivateAdjustBalance(&z, true)); // bf = +1, same topology - REQUIRE(+1 == z.bf); - REQUIRE(&y == cavlPrivateAdjustBalance(&y, true)); // bf = +1, same topology - REQUIRE(+1 == y.bf); - REQUIRE(&y == cavlPrivateAdjustBalance(&x, false)); // bf = -2, rotation needed + ASSERT_TRUE(nullptr == findBrokenAncestry(&x)); + ASSERT_TRUE(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology + ASSERT_TRUE(-1 == x.bf); + ASSERT_TRUE(&z == cavlPrivateAdjustBalance(&z, true)); // bf = +1, same topology + ASSERT_TRUE(+1 == z.bf); + ASSERT_TRUE(&y == cavlPrivateAdjustBalance(&y, true)); // bf = +1, same topology + ASSERT_TRUE(+1 == y.bf); + ASSERT_TRUE(&y == cavlPrivateAdjustBalance(&x, false)); // bf = -2, rotation needed print(&y); - REQUIRE(nullptr == findBrokenBalanceFactor(&y)); // Should be balanced now. - REQUIRE(nullptr == findBrokenAncestry(&y)); - REQUIRE(&z == y.lr[0]); - REQUIRE(&x == y.lr[1]); - REQUIRE(&d == z.lr[0]); - REQUIRE(Zz == z.lr[1]); - REQUIRE(&g == x.lr[0]); - REQUIRE(&c == x.lr[1]); - REQUIRE(Zz == d.lr[0]); - REQUIRE(Zz == d.lr[1]); - REQUIRE(Zz == g.lr[0]); - REQUIRE(Zz == g.lr[1]); - REQUIRE(Zz == c.lr[0]); - REQUIRE(Zz == c.lr[1]); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&y)); // Should be balanced now. + ASSERT_TRUE(nullptr == findBrokenAncestry(&y)); + ASSERT_TRUE(&z == y.lr[0]); + ASSERT_TRUE(&x == y.lr[1]); + ASSERT_TRUE(&d == z.lr[0]); + ASSERT_TRUE(Zz == z.lr[1]); + ASSERT_TRUE(&g == x.lr[0]); + ASSERT_TRUE(&c == x.lr[1]); + ASSERT_TRUE(Zz == d.lr[0]); + ASSERT_TRUE(Zz == d.lr[1]); + ASSERT_TRUE(Zz == g.lr[0]); + ASSERT_TRUE(Zz == g.lr[1]); + ASSERT_TRUE(Zz == c.lr[0]); + ASSERT_TRUE(Zz == c.lr[1]); } -TEST_CASE("BalancingC") +TEST(Cavl, BalancingC) { using N = Node; // Both X and Z are heavy on the same side. @@ -402,32 +403,32 @@ TEST_CASE("BalancingC") f = {{&d, {Zz, Zz}, 0}, 6}; g = {{&d, {Zz, Zz}, 0}, 7}; print(&x); - REQUIRE(nullptr == findBrokenAncestry(&x)); - REQUIRE(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology - REQUIRE(-1 == x.bf); - REQUIRE(&z == cavlPrivateAdjustBalance(&z, false)); // bf = -1, same topology - REQUIRE(-1 == z.bf); - REQUIRE(&z == cavlPrivateAdjustBalance(&x, false)); + ASSERT_TRUE(nullptr == findBrokenAncestry(&x)); + ASSERT_TRUE(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology + ASSERT_TRUE(-1 == x.bf); + ASSERT_TRUE(&z == cavlPrivateAdjustBalance(&z, false)); // bf = -1, same topology + ASSERT_TRUE(-1 == z.bf); + ASSERT_TRUE(&z == cavlPrivateAdjustBalance(&x, false)); print(&z); - REQUIRE(nullptr == findBrokenBalanceFactor(&z)); - REQUIRE(nullptr == findBrokenAncestry(&z)); - REQUIRE(&d == z.lr[0]); - REQUIRE(&x == z.lr[1]); - REQUIRE(&f == d.lr[0]); - REQUIRE(&g == d.lr[1]); - REQUIRE(&y == x.lr[0]); - REQUIRE(&c == x.lr[1]); - REQUIRE(Zz == f.lr[0]); - REQUIRE(Zz == f.lr[1]); - REQUIRE(Zz == g.lr[0]); - REQUIRE(Zz == g.lr[1]); - REQUIRE(Zz == y.lr[0]); - REQUIRE(Zz == y.lr[1]); - REQUIRE(Zz == c.lr[0]); - REQUIRE(Zz == c.lr[1]); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&z)); + ASSERT_TRUE(nullptr == findBrokenAncestry(&z)); + ASSERT_TRUE(&d == z.lr[0]); + ASSERT_TRUE(&x == z.lr[1]); + ASSERT_TRUE(&f == d.lr[0]); + ASSERT_TRUE(&g == d.lr[1]); + ASSERT_TRUE(&y == x.lr[0]); + ASSERT_TRUE(&c == x.lr[1]); + ASSERT_TRUE(Zz == f.lr[0]); + ASSERT_TRUE(Zz == f.lr[1]); + ASSERT_TRUE(Zz == g.lr[0]); + ASSERT_TRUE(Zz == g.lr[1]); + ASSERT_TRUE(Zz == y.lr[0]); + ASSERT_TRUE(Zz == y.lr[1]); + ASSERT_TRUE(Zz == c.lr[0]); + ASSERT_TRUE(Zz == c.lr[1]); } -TEST_CASE("RetracingOnGrowth") +TEST(Cavl, RetracingOnGrowth) { using N = Node; std::array t{}; @@ -449,46 +450,46 @@ TEST_CASE("RetracingOnGrowth") t[40] = {&t[30], {Zzzzzz, Zzzzzz}, 00}; t[10] = {&t[20], {Zzzzzz, Zzzzzz}, 00}; print(&t[50]); // The tree is imbalanced because we just added 1 and are about to retrace it. - REQUIRE(nullptr == findBrokenAncestry(&t[50])); - REQUIRE(6 == checkAscension(&t[50])); - REQUIRE(&t[30] == cavlPrivateRetraceOnGrowth(&t[10])); + ASSERT_TRUE(nullptr == findBrokenAncestry(&t[50])); + ASSERT_TRUE(6 == checkAscension(&t[50])); + ASSERT_TRUE(&t[30] == cavlPrivateRetraceOnGrowth(&t[10])); std::puts("ADD 10:"); print(&t[30]); // This is the new root. - REQUIRE(&t[20] == t[30].lr[0]); - REQUIRE(&t[50] == t[30].lr[1]); - REQUIRE(&t[10] == t[20].lr[0]); - REQUIRE(Zzzzzz == t[20].lr[1]); - REQUIRE(&t[40] == t[50].lr[0]); - REQUIRE(&t[60] == t[50].lr[1]); - REQUIRE(Zzzzzz == t[10].lr[0]); - REQUIRE(Zzzzzz == t[10].lr[1]); - REQUIRE(Zzzzzz == t[40].lr[0]); - REQUIRE(Zzzzzz == t[40].lr[1]); - REQUIRE(Zzzzzz == t[60].lr[0]); - REQUIRE(Zzzzzz == t[60].lr[1]); - REQUIRE(-1 == t[20].bf); - REQUIRE(+0 == t[30].bf); - REQUIRE(nullptr == findBrokenAncestry(&t[30])); - REQUIRE(nullptr == findBrokenBalanceFactor(&t[30])); - REQUIRE(6 == checkAscension(&t[30])); + ASSERT_TRUE(&t[20] == t[30].lr[0]); + ASSERT_TRUE(&t[50] == t[30].lr[1]); + ASSERT_TRUE(&t[10] == t[20].lr[0]); + ASSERT_TRUE(Zzzzzz == t[20].lr[1]); + ASSERT_TRUE(&t[40] == t[50].lr[0]); + ASSERT_TRUE(&t[60] == t[50].lr[1]); + ASSERT_TRUE(Zzzzzz == t[10].lr[0]); + ASSERT_TRUE(Zzzzzz == t[10].lr[1]); + ASSERT_TRUE(Zzzzzz == t[40].lr[0]); + ASSERT_TRUE(Zzzzzz == t[40].lr[1]); + ASSERT_TRUE(Zzzzzz == t[60].lr[0]); + ASSERT_TRUE(Zzzzzz == t[60].lr[1]); + ASSERT_TRUE(-1 == t[20].bf); + ASSERT_TRUE(+0 == t[30].bf); + ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); + ASSERT_TRUE(6 == checkAscension(&t[30])); // Add a new child under 20 and ensure that retracing stops at 20 because it becomes perfectly balanced: // 30 // / ` // 20 50 // / ` / ` // 10 21 40 60 - REQUIRE(nullptr == findBrokenAncestry(&t[30])); - REQUIRE(nullptr == findBrokenBalanceFactor(&t[30])); + ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); t[21] = {&t[20], {Zzzzzz, Zzzzzz}, 0}; t[20].lr[1] = &t[21]; - REQUIRE(nullptr == cavlPrivateRetraceOnGrowth(&t[21])); // Root not reached, NULL returned. + ASSERT_TRUE(nullptr == cavlPrivateRetraceOnGrowth(&t[21])); // Root not reached, NULL returned. std::puts("ADD 21:"); print(&t[30]); - REQUIRE(0 == t[20].bf); - REQUIRE(0 == t[30].bf); - REQUIRE(nullptr == findBrokenAncestry(&t[30])); - REQUIRE(nullptr == findBrokenBalanceFactor(&t[30])); - REQUIRE(7 == checkAscension(&t[30])); + ASSERT_TRUE(0 == t[20].bf); + ASSERT_TRUE(0 == t[30].bf); + ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); + ASSERT_TRUE(7 == checkAscension(&t[30])); // 30 // / ` // 20 50 @@ -543,97 +544,97 @@ TEST_CASE("RetracingOnGrowth") // / / ` // 10 18 21 std::puts("ADD 15:"); - REQUIRE(nullptr == findBrokenAncestry(&t[30])); - REQUIRE(nullptr == findBrokenBalanceFactor(&t[30])); - REQUIRE(7 == checkAscension(&t[30])); + ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); + ASSERT_TRUE(7 == checkAscension(&t[30])); t[15] = {&t[10], {Zzzzzz, Zzzzzz}, 0}; t[10].lr[1] = &t[15]; - REQUIRE(&t[30] == cavlPrivateRetraceOnGrowth(&t[15])); // Same root, its balance becomes -1. + ASSERT_TRUE(&t[30] == cavlPrivateRetraceOnGrowth(&t[15])); // Same root, its balance becomes -1. print(&t[30]); - REQUIRE(+1 == t[10].bf); - REQUIRE(-1 == t[20].bf); - REQUIRE(-1 == t[30].bf); - REQUIRE(nullptr == findBrokenAncestry(&t[30])); - REQUIRE(nullptr == findBrokenBalanceFactor(&t[30])); - REQUIRE(8 == checkAscension(&t[30])); + ASSERT_TRUE(+1 == t[10].bf); + ASSERT_TRUE(-1 == t[20].bf); + ASSERT_TRUE(-1 == t[30].bf); + ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); + ASSERT_TRUE(8 == checkAscension(&t[30])); std::puts("ADD 17:"); t[17] = {&t[15], {Zzzzzz, Zzzzzz}, 0}; t[15].lr[1] = &t[17]; - REQUIRE(nullptr == cavlPrivateRetraceOnGrowth(&t[17])); // Same root, same balance, 10 rotated left. + ASSERT_TRUE(nullptr == cavlPrivateRetraceOnGrowth(&t[17])); // Same root, same balance, 10 rotated left. print(&t[30]); // Check 10 - REQUIRE(&t[15] == t[10].up); - REQUIRE(0 == t[10].bf); - REQUIRE(nullptr == t[10].lr[0]); - REQUIRE(nullptr == t[10].lr[1]); + ASSERT_TRUE(&t[15] == t[10].up); + ASSERT_TRUE(0 == t[10].bf); + ASSERT_TRUE(nullptr == t[10].lr[0]); + ASSERT_TRUE(nullptr == t[10].lr[1]); // Check 17 - REQUIRE(&t[15] == t[17].up); - REQUIRE(0 == t[17].bf); - REQUIRE(nullptr == t[17].lr[0]); - REQUIRE(nullptr == t[17].lr[1]); + ASSERT_TRUE(&t[15] == t[17].up); + ASSERT_TRUE(0 == t[17].bf); + ASSERT_TRUE(nullptr == t[17].lr[0]); + ASSERT_TRUE(nullptr == t[17].lr[1]); // Check 15 - REQUIRE(&t[20] == t[15].up); - REQUIRE(0 == t[15].bf); - REQUIRE(&t[10] == t[15].lr[0]); - REQUIRE(&t[17] == t[15].lr[1]); + ASSERT_TRUE(&t[20] == t[15].up); + ASSERT_TRUE(0 == t[15].bf); + ASSERT_TRUE(&t[10] == t[15].lr[0]); + ASSERT_TRUE(&t[17] == t[15].lr[1]); // Check 20 -- leaning left - REQUIRE(&t[30] == t[20].up); - REQUIRE(-1 == t[20].bf); - REQUIRE(&t[15] == t[20].lr[0]); - REQUIRE(&t[21] == t[20].lr[1]); + ASSERT_TRUE(&t[30] == t[20].up); + ASSERT_TRUE(-1 == t[20].bf); + ASSERT_TRUE(&t[15] == t[20].lr[0]); + ASSERT_TRUE(&t[21] == t[20].lr[1]); // Check the root -- still leaning left by one. - REQUIRE(nullptr == t[30].up); - REQUIRE(-1 == t[30].bf); - REQUIRE(&t[20] == t[30].lr[0]); - REQUIRE(&t[50] == t[30].lr[1]); + ASSERT_TRUE(nullptr == t[30].up); + ASSERT_TRUE(-1 == t[30].bf); + ASSERT_TRUE(&t[20] == t[30].lr[0]); + ASSERT_TRUE(&t[50] == t[30].lr[1]); // Check hard invariants. - REQUIRE(nullptr == findBrokenAncestry(&t[30])); - REQUIRE(nullptr == findBrokenBalanceFactor(&t[30])); - REQUIRE(9 == checkAscension(&t[30])); + ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); + ASSERT_TRUE(9 == checkAscension(&t[30])); std::puts("ADD 18:"); t[18] = {&t[17], {Zzzzzz, Zzzzzz}, 0}; t[17].lr[1] = &t[18]; - REQUIRE(nullptr == cavlPrivateRetraceOnGrowth(&t[18])); // Same root, 15 went left, 20 went right. + ASSERT_TRUE(nullptr == cavlPrivateRetraceOnGrowth(&t[18])); // Same root, 15 went left, 20 went right. print(&t[30]); // Check 17 - REQUIRE(&t[30] == t[17].up); - REQUIRE(0 == t[17].bf); - REQUIRE(&t[15] == t[17].lr[0]); - REQUIRE(&t[20] == t[17].lr[1]); + ASSERT_TRUE(&t[30] == t[17].up); + ASSERT_TRUE(0 == t[17].bf); + ASSERT_TRUE(&t[15] == t[17].lr[0]); + ASSERT_TRUE(&t[20] == t[17].lr[1]); // Check 15 - REQUIRE(&t[17] == t[15].up); - REQUIRE(-1 == t[15].bf); - REQUIRE(&t[10] == t[15].lr[0]); - REQUIRE(nullptr == t[15].lr[1]); + ASSERT_TRUE(&t[17] == t[15].up); + ASSERT_TRUE(-1 == t[15].bf); + ASSERT_TRUE(&t[10] == t[15].lr[0]); + ASSERT_TRUE(nullptr == t[15].lr[1]); // Check 20 - REQUIRE(&t[17] == t[20].up); - REQUIRE(0 == t[20].bf); - REQUIRE(&t[18] == t[20].lr[0]); - REQUIRE(&t[21] == t[20].lr[1]); + ASSERT_TRUE(&t[17] == t[20].up); + ASSERT_TRUE(0 == t[20].bf); + ASSERT_TRUE(&t[18] == t[20].lr[0]); + ASSERT_TRUE(&t[21] == t[20].lr[1]); // Check 10 - REQUIRE(&t[15] == t[10].up); - REQUIRE(0 == t[10].bf); - REQUIRE(nullptr == t[10].lr[0]); - REQUIRE(nullptr == t[10].lr[1]); + ASSERT_TRUE(&t[15] == t[10].up); + ASSERT_TRUE(0 == t[10].bf); + ASSERT_TRUE(nullptr == t[10].lr[0]); + ASSERT_TRUE(nullptr == t[10].lr[1]); // Check 18 - REQUIRE(&t[20] == t[18].up); - REQUIRE(0 == t[18].bf); - REQUIRE(nullptr == t[18].lr[0]); - REQUIRE(nullptr == t[18].lr[1]); + ASSERT_TRUE(&t[20] == t[18].up); + ASSERT_TRUE(0 == t[18].bf); + ASSERT_TRUE(nullptr == t[18].lr[0]); + ASSERT_TRUE(nullptr == t[18].lr[1]); // Check 21 - REQUIRE(&t[20] == t[21].up); - REQUIRE(0 == t[21].bf); - REQUIRE(nullptr == t[21].lr[0]); - REQUIRE(nullptr == t[21].lr[1]); + ASSERT_TRUE(&t[20] == t[21].up); + ASSERT_TRUE(0 == t[21].bf); + ASSERT_TRUE(nullptr == t[21].lr[0]); + ASSERT_TRUE(nullptr == t[21].lr[1]); // Check hard invariants. - REQUIRE(nullptr == findBrokenAncestry(&t[30])); - REQUIRE(nullptr == findBrokenBalanceFactor(&t[30])); - REQUIRE(10 == checkAscension(&t[30])); + ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); + ASSERT_TRUE(10 == checkAscension(&t[30])); } -TEST_CASE("SearchTrivial") +TEST(Cavl, SearchTrivial) { using N = Node; // A @@ -655,29 +656,29 @@ TEST_CASE("SearchTrivial") f = {&c, {Zz, Zz}, 0}; g = {&c, {Zz, Zz}, 0}; q = {Zz, {Zz, Zz}, 0}; - REQUIRE(nullptr == findBrokenBalanceFactor(&a)); - REQUIRE(nullptr == findBrokenAncestry(&a)); - REQUIRE(7 == checkAscension(&a)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&a)); + ASSERT_TRUE(nullptr == findBrokenAncestry(&a)); + ASSERT_TRUE(7 == checkAscension(&a)); N* root = &a; - REQUIRE(nullptr == cavlSearch(reinterpret_cast(&root), nullptr, nullptr, nullptr)); // Bad arguments. - REQUIRE(&a == root); - REQUIRE(nullptr == search(&root, [&](const N& v) { return q.value - v.value; })); - REQUIRE(&a == root); - REQUIRE(&e == search(&root, [&](const N& v) { return e.value - v.value; })); - REQUIRE(&b == search(&root, [&](const N& v) { return b.value - v.value; })); - REQUIRE(&a == root); + ASSERT_TRUE(nullptr == cavlSearch(reinterpret_cast(&root), nullptr, nullptr, nullptr)); // Bad arguments. + ASSERT_TRUE(&a == root); + ASSERT_TRUE(nullptr == search(&root, [&](const N& v) { return q.value - v.value; })); + ASSERT_TRUE(&a == root); + ASSERT_TRUE(&e == search(&root, [&](const N& v) { return e.value - v.value; })); + ASSERT_TRUE(&b == search(&root, [&](const N& v) { return b.value - v.value; })); + ASSERT_TRUE(&a == root); print(&a); - REQUIRE(nullptr == cavlFindExtremum(nullptr, true)); - REQUIRE(nullptr == cavlFindExtremum(nullptr, false)); - REQUIRE(&g == a.max()); - REQUIRE(&d == a.min()); - REQUIRE(&g == g.max()); - REQUIRE(&g == g.min()); - REQUIRE(&d == d.max()); - REQUIRE(&d == d.min()); + ASSERT_TRUE(nullptr == cavlFindExtremum(nullptr, true)); + ASSERT_TRUE(nullptr == cavlFindExtremum(nullptr, false)); + ASSERT_TRUE(&g == a.max()); + ASSERT_TRUE(&d == a.min()); + ASSERT_TRUE(&g == g.max()); + ASSERT_TRUE(&g == g.min()); + ASSERT_TRUE(&d == d.max()); + ASSERT_TRUE(&d == d.min()); } -TEST_CASE("RemovalA") +TEST(Cavl, RemovalA) { using N = Node; // 4 @@ -703,9 +704,9 @@ TEST_CASE("RemovalA") t[9] = {&t[8], {Zzzzz, Zzzzz}, 00}; N* root = &t[4]; print(root); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(9 == checkAscension(root)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(9 == checkAscension(root)); // Remove 9, the easiest case. The rest of the tree remains unchanged. // 4 @@ -717,51 +718,51 @@ TEST_CASE("RemovalA") // 7 std::puts("REMOVE 9:"); remove(&root, &t[9]); - REQUIRE(&t[4] == root); + ASSERT_TRUE(&t[4] == root); print(root); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(8 == checkAscension(root)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(8 == checkAscension(root)); // 1 - REQUIRE(&t[2] == t[1].up); - REQUIRE(Zzzzz == t[1].lr[0]); - REQUIRE(Zzzzz == t[1].lr[1]); - REQUIRE(00 == t[1].bf); + ASSERT_TRUE(&t[2] == t[1].up); + ASSERT_TRUE(Zzzzz == t[1].lr[0]); + ASSERT_TRUE(Zzzzz == t[1].lr[1]); + ASSERT_TRUE(00 == t[1].bf); // 2 - REQUIRE(&t[4] == t[2].up); - REQUIRE(&t[1] == t[2].lr[0]); - REQUIRE(&t[3] == t[2].lr[1]); - REQUIRE(00 == t[2].bf); + ASSERT_TRUE(&t[4] == t[2].up); + ASSERT_TRUE(&t[1] == t[2].lr[0]); + ASSERT_TRUE(&t[3] == t[2].lr[1]); + ASSERT_TRUE(00 == t[2].bf); // 3 - REQUIRE(&t[2] == t[3].up); - REQUIRE(Zzzzz == t[3].lr[0]); - REQUIRE(Zzzzz == t[3].lr[1]); - REQUIRE(00 == t[3].bf); + ASSERT_TRUE(&t[2] == t[3].up); + ASSERT_TRUE(Zzzzz == t[3].lr[0]); + ASSERT_TRUE(Zzzzz == t[3].lr[1]); + ASSERT_TRUE(00 == t[3].bf); // 4 - REQUIRE(Zzzzz == t[4].up); // Nihil Supernum - REQUIRE(&t[2] == t[4].lr[0]); - REQUIRE(&t[6] == t[4].lr[1]); - REQUIRE(+1 == t[4].bf); + ASSERT_TRUE(Zzzzz == t[4].up); // Nihil Supernum + ASSERT_TRUE(&t[2] == t[4].lr[0]); + ASSERT_TRUE(&t[6] == t[4].lr[1]); + ASSERT_TRUE(+1 == t[4].bf); // 5 - REQUIRE(&t[6] == t[5].up); - REQUIRE(Zzzzz == t[5].lr[0]); - REQUIRE(Zzzzz == t[5].lr[1]); - REQUIRE(00 == t[5].bf); + ASSERT_TRUE(&t[6] == t[5].up); + ASSERT_TRUE(Zzzzz == t[5].lr[0]); + ASSERT_TRUE(Zzzzz == t[5].lr[1]); + ASSERT_TRUE(00 == t[5].bf); // 6 - REQUIRE(&t[4] == t[6].up); - REQUIRE(&t[5] == t[6].lr[0]); - REQUIRE(&t[8] == t[6].lr[1]); - REQUIRE(+1 == t[6].bf); + ASSERT_TRUE(&t[4] == t[6].up); + ASSERT_TRUE(&t[5] == t[6].lr[0]); + ASSERT_TRUE(&t[8] == t[6].lr[1]); + ASSERT_TRUE(+1 == t[6].bf); // 7 - REQUIRE(&t[8] == t[7].up); - REQUIRE(Zzzzz == t[7].lr[0]); - REQUIRE(Zzzzz == t[7].lr[1]); - REQUIRE(00 == t[7].bf); + ASSERT_TRUE(&t[8] == t[7].up); + ASSERT_TRUE(Zzzzz == t[7].lr[0]); + ASSERT_TRUE(Zzzzz == t[7].lr[1]); + ASSERT_TRUE(00 == t[7].bf); // 8 - REQUIRE(&t[6] == t[8].up); - REQUIRE(&t[7] == t[8].lr[0]); - REQUIRE(Zzzzz == t[8].lr[1]); - REQUIRE(-1 == t[8].bf); + ASSERT_TRUE(&t[6] == t[8].up); + ASSERT_TRUE(&t[7] == t[8].lr[0]); + ASSERT_TRUE(Zzzzz == t[8].lr[1]); + ASSERT_TRUE(-1 == t[8].bf); // Remove 8, 7 takes its place (the one-child case). The rest of the tree remains unchanged. // 4 @@ -771,46 +772,46 @@ TEST_CASE("RemovalA") // 1 3 5 7 std::puts("REMOVE 8:"); remove(&root, &t[8]); - REQUIRE(&t[4] == root); + ASSERT_TRUE(&t[4] == root); print(root); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(7 == checkAscension(root)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(7 == checkAscension(root)); // 1 - REQUIRE(&t[2] == t[1].up); - REQUIRE(Zzzzz == t[1].lr[0]); - REQUIRE(Zzzzz == t[1].lr[1]); - REQUIRE(00 == t[1].bf); + ASSERT_TRUE(&t[2] == t[1].up); + ASSERT_TRUE(Zzzzz == t[1].lr[0]); + ASSERT_TRUE(Zzzzz == t[1].lr[1]); + ASSERT_TRUE(00 == t[1].bf); // 2 - REQUIRE(&t[4] == t[2].up); - REQUIRE(&t[1] == t[2].lr[0]); - REQUIRE(&t[3] == t[2].lr[1]); - REQUIRE(00 == t[2].bf); + ASSERT_TRUE(&t[4] == t[2].up); + ASSERT_TRUE(&t[1] == t[2].lr[0]); + ASSERT_TRUE(&t[3] == t[2].lr[1]); + ASSERT_TRUE(00 == t[2].bf); // 3 - REQUIRE(&t[2] == t[3].up); - REQUIRE(Zzzzz == t[3].lr[0]); - REQUIRE(Zzzzz == t[3].lr[1]); - REQUIRE(00 == t[3].bf); + ASSERT_TRUE(&t[2] == t[3].up); + ASSERT_TRUE(Zzzzz == t[3].lr[0]); + ASSERT_TRUE(Zzzzz == t[3].lr[1]); + ASSERT_TRUE(00 == t[3].bf); // 4 - REQUIRE(Zzzzz == t[4].up); // Nihil Supernum - REQUIRE(&t[2] == t[4].lr[0]); - REQUIRE(&t[6] == t[4].lr[1]); - REQUIRE(00 == t[4].bf); + ASSERT_TRUE(Zzzzz == t[4].up); // Nihil Supernum + ASSERT_TRUE(&t[2] == t[4].lr[0]); + ASSERT_TRUE(&t[6] == t[4].lr[1]); + ASSERT_TRUE(00 == t[4].bf); // 5 - REQUIRE(&t[6] == t[5].up); - REQUIRE(Zzzzz == t[5].lr[0]); - REQUIRE(Zzzzz == t[5].lr[1]); - REQUIRE(00 == t[5].bf); + ASSERT_TRUE(&t[6] == t[5].up); + ASSERT_TRUE(Zzzzz == t[5].lr[0]); + ASSERT_TRUE(Zzzzz == t[5].lr[1]); + ASSERT_TRUE(00 == t[5].bf); // 6 - REQUIRE(&t[4] == t[6].up); - REQUIRE(&t[5] == t[6].lr[0]); - REQUIRE(&t[7] == t[6].lr[1]); - REQUIRE(00 == t[6].bf); + ASSERT_TRUE(&t[4] == t[6].up); + ASSERT_TRUE(&t[5] == t[6].lr[0]); + ASSERT_TRUE(&t[7] == t[6].lr[1]); + ASSERT_TRUE(00 == t[6].bf); // 7 - REQUIRE(&t[6] == t[7].up); - REQUIRE(Zzzzz == t[7].lr[0]); - REQUIRE(Zzzzz == t[7].lr[1]); - REQUIRE(00 == t[7].bf); + ASSERT_TRUE(&t[6] == t[7].up); + ASSERT_TRUE(Zzzzz == t[7].lr[0]); + ASSERT_TRUE(Zzzzz == t[7].lr[1]); + ASSERT_TRUE(00 == t[7].bf); // Remove the root node 4, 5 takes its place. The overall structure remains unchanged except that 5 is now the root. // 5 @@ -820,41 +821,41 @@ TEST_CASE("RemovalA") // 1 3 7 std::puts("REMOVE 4:"); remove(&root, &t[4]); - REQUIRE(&t[5] == root); print(root); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(6 == checkAscension(root)); + ASSERT_TRUE(&t[5] == root); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(6 == checkAscension(root)); // 1 - REQUIRE(&t[2] == t[1].up); - REQUIRE(Zzzzz == t[1].lr[0]); - REQUIRE(Zzzzz == t[1].lr[1]); - REQUIRE(00 == t[1].bf); + ASSERT_TRUE(&t[2] == t[1].up); + ASSERT_TRUE(Zzzzz == t[1].lr[0]); + ASSERT_TRUE(Zzzzz == t[1].lr[1]); + ASSERT_TRUE(00 == t[1].bf); // 2 - REQUIRE(&t[5] == t[2].up); - REQUIRE(&t[1] == t[2].lr[0]); - REQUIRE(&t[3] == t[2].lr[1]); - REQUIRE(00 == t[2].bf); + ASSERT_TRUE(&t[5] == t[2].up); + ASSERT_TRUE(&t[1] == t[2].lr[0]); + ASSERT_TRUE(&t[3] == t[2].lr[1]); + ASSERT_TRUE(00 == t[2].bf); // 3 - REQUIRE(&t[2] == t[3].up); - REQUIRE(Zzzzz == t[3].lr[0]); - REQUIRE(Zzzzz == t[3].lr[1]); - REQUIRE(00 == t[3].bf); + ASSERT_TRUE(&t[2] == t[3].up); + ASSERT_TRUE(Zzzzz == t[3].lr[0]); + ASSERT_TRUE(Zzzzz == t[3].lr[1]); + ASSERT_TRUE(00 == t[3].bf); // 5 - REQUIRE(Zzzzz == t[5].up); // Nihil Supernum - REQUIRE(&t[2] == t[5].lr[0]); - REQUIRE(&t[6] == t[5].lr[1]); - REQUIRE(00 == t[5].bf); + ASSERT_TRUE(Zzzzz == t[5].up); // Nihil Supernum + ASSERT_TRUE(&t[2] == t[5].lr[0]); + ASSERT_TRUE(&t[6] == t[5].lr[1]); + ASSERT_TRUE(00 == t[5].bf); // 6 - REQUIRE(&t[5] == t[6].up); - REQUIRE(Zzzzz == t[6].lr[0]); - REQUIRE(&t[7] == t[6].lr[1]); - REQUIRE(+1 == t[6].bf); + ASSERT_TRUE(&t[5] == t[6].up); + ASSERT_TRUE(Zzzzz == t[6].lr[0]); + ASSERT_TRUE(&t[7] == t[6].lr[1]); + ASSERT_TRUE(+1 == t[6].bf); // 7 - REQUIRE(&t[6] == t[7].up); - REQUIRE(Zzzzz == t[7].lr[0]); - REQUIRE(Zzzzz == t[7].lr[1]); - REQUIRE(00 == t[7].bf); + ASSERT_TRUE(&t[6] == t[7].up); + ASSERT_TRUE(Zzzzz == t[7].lr[0]); + ASSERT_TRUE(Zzzzz == t[7].lr[1]); + ASSERT_TRUE(00 == t[7].bf); // Remove the root node 5, 6 takes its place. // 6 @@ -864,36 +865,36 @@ TEST_CASE("RemovalA") // 1 3 std::puts("REMOVE 5:"); remove(&root, &t[5]); - REQUIRE(&t[6] == root); + ASSERT_TRUE(&t[6] == root); print(root); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(5 == checkAscension(root)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(5 == checkAscension(root)); // 1 - REQUIRE(&t[2] == t[1].up); - REQUIRE(Zzzzz == t[1].lr[0]); - REQUIRE(Zzzzz == t[1].lr[1]); - REQUIRE(00 == t[1].bf); + ASSERT_TRUE(&t[2] == t[1].up); + ASSERT_TRUE(Zzzzz == t[1].lr[0]); + ASSERT_TRUE(Zzzzz == t[1].lr[1]); + ASSERT_TRUE(00 == t[1].bf); // 2 - REQUIRE(&t[6] == t[2].up); - REQUIRE(&t[1] == t[2].lr[0]); - REQUIRE(&t[3] == t[2].lr[1]); - REQUIRE(00 == t[2].bf); + ASSERT_TRUE(&t[6] == t[2].up); + ASSERT_TRUE(&t[1] == t[2].lr[0]); + ASSERT_TRUE(&t[3] == t[2].lr[1]); + ASSERT_TRUE(00 == t[2].bf); // 3 - REQUIRE(&t[2] == t[3].up); - REQUIRE(Zzzzz == t[3].lr[0]); - REQUIRE(Zzzzz == t[3].lr[1]); - REQUIRE(00 == t[3].bf); + ASSERT_TRUE(&t[2] == t[3].up); + ASSERT_TRUE(Zzzzz == t[3].lr[0]); + ASSERT_TRUE(Zzzzz == t[3].lr[1]); + ASSERT_TRUE(00 == t[3].bf); // 6 - REQUIRE(Zzzzz == t[6].up); // Nihil Supernum - REQUIRE(&t[2] == t[6].lr[0]); - REQUIRE(&t[7] == t[6].lr[1]); - REQUIRE(-1 == t[6].bf); + ASSERT_TRUE(Zzzzz == t[6].up); // Nihil Supernum + ASSERT_TRUE(&t[2] == t[6].lr[0]); + ASSERT_TRUE(&t[7] == t[6].lr[1]); + ASSERT_TRUE(-1 == t[6].bf); // 7 - REQUIRE(&t[6] == t[7].up); - REQUIRE(Zzzzz == t[7].lr[0]); - REQUIRE(Zzzzz == t[7].lr[1]); - REQUIRE(00 == t[7].bf); + ASSERT_TRUE(&t[6] == t[7].up); + ASSERT_TRUE(Zzzzz == t[7].lr[0]); + ASSERT_TRUE(Zzzzz == t[7].lr[1]); + ASSERT_TRUE(00 == t[7].bf); // Remove the root node 6, 7 takes its place, then right rotation is done to restore balance, 2 is the new root. // 2 @@ -903,31 +904,31 @@ TEST_CASE("RemovalA") // 3 std::puts("REMOVE 6:"); remove(&root, &t[6]); - REQUIRE(&t[2] == root); + ASSERT_TRUE(&t[2] == root); print(root); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(4 == checkAscension(root)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(4 == checkAscension(root)); // 1 - REQUIRE(&t[2] == t[1].up); - REQUIRE(Zzzzz == t[1].lr[0]); - REQUIRE(Zzzzz == t[1].lr[1]); - REQUIRE(00 == t[1].bf); + ASSERT_TRUE(&t[2] == t[1].up); + ASSERT_TRUE(Zzzzz == t[1].lr[0]); + ASSERT_TRUE(Zzzzz == t[1].lr[1]); + ASSERT_TRUE(00 == t[1].bf); // 2 - REQUIRE(Zzzzz == t[2].up); // Nihil Supernum - REQUIRE(&t[1] == t[2].lr[0]); - REQUIRE(&t[7] == t[2].lr[1]); - REQUIRE(+1 == t[2].bf); + ASSERT_TRUE(Zzzzz == t[2].up); // Nihil Supernum + ASSERT_TRUE(&t[1] == t[2].lr[0]); + ASSERT_TRUE(&t[7] == t[2].lr[1]); + ASSERT_TRUE(+1 == t[2].bf); // 3 - REQUIRE(&t[7] == t[3].up); - REQUIRE(Zzzzz == t[3].lr[0]); - REQUIRE(Zzzzz == t[3].lr[1]); - REQUIRE(00 == t[3].bf); + ASSERT_TRUE(&t[7] == t[3].up); + ASSERT_TRUE(Zzzzz == t[3].lr[0]); + ASSERT_TRUE(Zzzzz == t[3].lr[1]); + ASSERT_TRUE(00 == t[3].bf); // 7 - REQUIRE(&t[2] == t[7].up); - REQUIRE(&t[3] == t[7].lr[0]); - REQUIRE(Zzzzz == t[7].lr[1]); - REQUIRE(-1 == t[7].bf); + ASSERT_TRUE(&t[2] == t[7].up); + ASSERT_TRUE(&t[3] == t[7].lr[0]); + ASSERT_TRUE(Zzzzz == t[7].lr[1]); + ASSERT_TRUE(-1 == t[7].bf); // Remove 1, then balancing makes 3 the new root node. // 3 @@ -935,26 +936,26 @@ TEST_CASE("RemovalA") // 2 7 std::puts("REMOVE 1:"); remove(&root, &t[1]); - REQUIRE(&t[3] == root); + ASSERT_TRUE(&t[3] == root); print(root); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(3 == checkAscension(root)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(3 == checkAscension(root)); // 2 - REQUIRE(&t[3] == t[2].up); - REQUIRE(Zzzzz == t[2].lr[0]); - REQUIRE(Zzzzz == t[2].lr[1]); - REQUIRE(0 == t[2].bf); + ASSERT_TRUE(&t[3] == t[2].up); + ASSERT_TRUE(Zzzzz == t[2].lr[0]); + ASSERT_TRUE(Zzzzz == t[2].lr[1]); + ASSERT_TRUE(0 == t[2].bf); // 3 - REQUIRE(Zzzzz == t[3].up); // Nihil Supernum - REQUIRE(&t[2] == t[3].lr[0]); - REQUIRE(&t[7] == t[3].lr[1]); - REQUIRE(00 == t[3].bf); + ASSERT_TRUE(Zzzzz == t[3].up); // Nihil Supernum + ASSERT_TRUE(&t[2] == t[3].lr[0]); + ASSERT_TRUE(&t[7] == t[3].lr[1]); + ASSERT_TRUE(00 == t[3].bf); // 7 - REQUIRE(&t[3] == t[7].up); - REQUIRE(Zzzzz == t[7].lr[0]); - REQUIRE(Zzzzz == t[7].lr[1]); - REQUIRE(00 == t[7].bf); + ASSERT_TRUE(&t[3] == t[7].up); + ASSERT_TRUE(Zzzzz == t[7].lr[0]); + ASSERT_TRUE(Zzzzz == t[7].lr[1]); + ASSERT_TRUE(00 == t[7].bf); // Remove 7. // 3 @@ -962,43 +963,43 @@ TEST_CASE("RemovalA") // 2 std::puts("REMOVE 7:"); remove(&root, &t[7]); - REQUIRE(&t[3] == root); + ASSERT_TRUE(&t[3] == root); print(root); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(2 == checkAscension(root)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(2 == checkAscension(root)); // 2 - REQUIRE(&t[3] == t[2].up); - REQUIRE(Zzzzz == t[2].lr[0]); - REQUIRE(Zzzzz == t[2].lr[1]); - REQUIRE(0 == t[2].bf); + ASSERT_TRUE(&t[3] == t[2].up); + ASSERT_TRUE(Zzzzz == t[2].lr[0]); + ASSERT_TRUE(Zzzzz == t[2].lr[1]); + ASSERT_TRUE(0 == t[2].bf); // 3 - REQUIRE(Zzzzz == t[3].up); // Nihil Supernum - REQUIRE(&t[2] == t[3].lr[0]); - REQUIRE(Zzzzz == t[3].lr[1]); - REQUIRE(-1 == t[3].bf); + ASSERT_TRUE(Zzzzz == t[3].up); // Nihil Supernum + ASSERT_TRUE(&t[2] == t[3].lr[0]); + ASSERT_TRUE(Zzzzz == t[3].lr[1]); + ASSERT_TRUE(-1 == t[3].bf); // Remove 3. Only 2 is left, which is now obviously the root. std::puts("REMOVE 3:"); remove(&root, &t[3]); - REQUIRE(&t[2] == root); + ASSERT_TRUE(&t[2] == root); print(root); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(1 == checkAscension(root)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(1 == checkAscension(root)); // 2 - REQUIRE(Zzzzz == t[2].up); - REQUIRE(Zzzzz == t[2].lr[0]); - REQUIRE(Zzzzz == t[2].lr[1]); - REQUIRE(0 == t[2].bf); + ASSERT_TRUE(Zzzzz == t[2].up); + ASSERT_TRUE(Zzzzz == t[2].lr[0]); + ASSERT_TRUE(Zzzzz == t[2].lr[1]); + ASSERT_TRUE(0 == t[2].bf); // Remove 2. The tree is now empty, make sure the root pointer is updated accordingly. std::puts("REMOVE 2:"); remove(&root, &t[2]); - REQUIRE(nullptr == root); + ASSERT_TRUE(nullptr == root); } -TEST_CASE("MutationManual") +TEST(Cavl, MutationManual) { using N = Node; // Build a tree with 31 elements from 1 to 31 inclusive by adding new elements successively: @@ -1021,27 +1022,27 @@ TEST_CASE("MutationManual") for (std::uint8_t i = 1; i < 32; i++) { const auto pred = [&](const N& v) { return t.at(i).value - v.value; }; - REQUIRE(nullptr == search(&root, pred)); - REQUIRE(&t[i] == search(&root, pred, [&]() { return &t.at(i); })); - REQUIRE(&t[i] == search(&root, pred)); + ASSERT_TRUE(nullptr == search(&root, pred)); + ASSERT_TRUE(&t[i] == search(&root, pred, [&]() { return &t.at(i); })); + ASSERT_TRUE(&t[i] == search(&root, pred)); // Validate the tree after every mutation. - REQUIRE(nullptr != root); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(i == checkAscension(root)); + ASSERT_TRUE(nullptr != root); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(i == checkAscension(root)); } print(root); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(31 == checkAscension(root)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(31 == checkAscension(root)); // Check composition -- ensure that every element is in the tree and it is there exactly once. { std::array seen{}; traverse(root, [&](const N* const n) { - REQUIRE(!seen.at(n->value)); + ASSERT_TRUE(!seen.at(n->value)); seen[n->value] = true; }); - REQUIRE(std::all_of(&seen[1], &seen[31], [](bool x) { return x; })); + ASSERT_TRUE(std::all_of(&seen[1], &seen[31], [](bool x) { return x; })); } // REMOVE 24 @@ -1055,15 +1056,15 @@ TEST_CASE("MutationManual") // / ` / ` / ` / ` / ` / ` ` / ` // 1 3 5 7 9 11 13 15 17 19 21 23 27 29 31 std::puts("REMOVE 24:"); - REQUIRE(t[24].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); + ASSERT_TRUE(t[24].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); remove(&root, &t[24]); - REQUIRE(&t[16] == root); + ASSERT_TRUE(&t[16] == root); print(root); - REQUIRE(t[25].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); - REQUIRE(t[26].checkLinkageUpLeftRightBF(&t[28], Zzzzzz, &t[27], +1)); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(30 == checkAscension(root)); + ASSERT_TRUE(t[25].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); + ASSERT_TRUE(t[26].checkLinkageUpLeftRightBF(&t[28], Zzzzzz, &t[27], +1)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(30 == checkAscension(root)); // REMOVE 25 // 16 @@ -1076,15 +1077,15 @@ TEST_CASE("MutationManual") // / ` / ` / ` / ` / ` / ` / ` // 1 3 5 7 9 11 13 15 17 19 21 23 29 31 std::puts("REMOVE 25:"); - REQUIRE(t[25].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); + ASSERT_TRUE(t[25].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); remove(&root, &t[25]); - REQUIRE(&t[16] == root); + ASSERT_TRUE(&t[16] == root); print(root); - REQUIRE(t[26].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); - REQUIRE(t[28].checkLinkageUpLeftRightBF(&t[26], &t[27], &t[30], +1)); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(29 == checkAscension(root)); + ASSERT_TRUE(t[26].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); + ASSERT_TRUE(t[28].checkLinkageUpLeftRightBF(&t[26], &t[27], &t[30], +1)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(29 == checkAscension(root)); // REMOVE 26 // 16 @@ -1097,16 +1098,16 @@ TEST_CASE("MutationManual") // / ` / ` / ` / ` / ` / ` ` // 1 3 5 7 9 11 13 15 17 19 21 23 29 std::puts("REMOVE 26:"); - REQUIRE(t[26].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); + ASSERT_TRUE(t[26].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); remove(&root, &t[26]); - REQUIRE(&t[16] == root); + ASSERT_TRUE(&t[16] == root); print(root); - REQUIRE(t[27].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[30], 00)); - REQUIRE(t[30].checkLinkageUpLeftRightBF(&t[27], &t[28], &t[31], -1)); - REQUIRE(t[28].checkLinkageUpLeftRightBF(&t[30], Zzzzzz, &t[29], +1)); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(28 == checkAscension(root)); + ASSERT_TRUE(t[27].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[30], 00)); + ASSERT_TRUE(t[30].checkLinkageUpLeftRightBF(&t[27], &t[28], &t[31], -1)); + ASSERT_TRUE(t[28].checkLinkageUpLeftRightBF(&t[30], Zzzzzz, &t[29], +1)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(28 == checkAscension(root)); // REMOVE 20 // 16 @@ -1119,15 +1120,15 @@ TEST_CASE("MutationManual") // / ` / ` / ` / ` / ` ` ` // 1 3 5 7 9 11 13 15 17 19 23 29 std::puts("REMOVE 20:"); - REQUIRE(t[20].checkLinkageUpLeftRightBF(&t[27], &t[18], &t[22], 00)); + ASSERT_TRUE(t[20].checkLinkageUpLeftRightBF(&t[27], &t[18], &t[22], 00)); remove(&root, &t[20]); - REQUIRE(&t[16] == root); + ASSERT_TRUE(&t[16] == root); print(root); - REQUIRE(t[21].checkLinkageUpLeftRightBF(&t[27], &t[18], &t[22], 00)); - REQUIRE(t[22].checkLinkageUpLeftRightBF(&t[21], Zzzzzz, &t[23], +1)); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(27 == checkAscension(root)); + ASSERT_TRUE(t[21].checkLinkageUpLeftRightBF(&t[27], &t[18], &t[22], 00)); + ASSERT_TRUE(t[22].checkLinkageUpLeftRightBF(&t[21], Zzzzzz, &t[23], +1)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(27 == checkAscension(root)); // REMOVE 27 // 16 @@ -1140,15 +1141,15 @@ TEST_CASE("MutationManual") // / ` / ` / ` / ` / ` ` // 1 3 5 7 9 11 13 15 17 19 23 std::puts("REMOVE 27:"); - REQUIRE(t[27].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], 00)); + ASSERT_TRUE(t[27].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], 00)); remove(&root, &t[27]); - REQUIRE(&t[16] == root); + ASSERT_TRUE(&t[16] == root); print(root); - REQUIRE(t[28].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); - REQUIRE(t[30].checkLinkageUpLeftRightBF(&t[28], &t[29], &t[31], 00)); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(26 == checkAscension(root)); + ASSERT_TRUE(t[28].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); + ASSERT_TRUE(t[30].checkLinkageUpLeftRightBF(&t[28], &t[29], &t[31], 00)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(26 == checkAscension(root)); // REMOVE 28 // 16 @@ -1161,15 +1162,15 @@ TEST_CASE("MutationManual") // / ` / ` / ` / ` / ` ` // 1 3 5 7 9 11 13 15 17 19 23 std::puts("REMOVE 28:"); - REQUIRE(t[28].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); + ASSERT_TRUE(t[28].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); remove(&root, &t[28]); - REQUIRE(&t[16] == root); + ASSERT_TRUE(&t[16] == root); print(root); - REQUIRE(t[29].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); - REQUIRE(t[30].checkLinkageUpLeftRightBF(&t[29], Zzzzzz, &t[31], +1)); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(25 == checkAscension(root)); + ASSERT_TRUE(t[29].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); + ASSERT_TRUE(t[30].checkLinkageUpLeftRightBF(&t[29], Zzzzzz, &t[31], +1)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(25 == checkAscension(root)); // REMOVE 29; UNBALANCED TREE BEFORE ROTATION: // 16 @@ -1193,18 +1194,18 @@ TEST_CASE("MutationManual") // / ` / ` / ` / ` ` // 1 3 5 7 9 11 13 15 23 std::puts("REMOVE 29:"); - REQUIRE(t[29].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); + ASSERT_TRUE(t[29].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); remove(&root, &t[29]); - REQUIRE(&t[16] == root); + ASSERT_TRUE(&t[16] == root); print(root); - REQUIRE(t[21].checkLinkageUpLeftRightBF(&t[16], &t[18], &t[30], +1)); - REQUIRE(t[18].checkLinkageUpLeftRightBF(&t[21], &t[17], &t[19], 00)); - REQUIRE(t[30].checkLinkageUpLeftRightBF(&t[21], &t[22], &t[31], -1)); - REQUIRE(t[22].checkLinkageUpLeftRightBF(&t[30], Zzzzzz, &t[23], +1)); - REQUIRE(t[16].checkLinkageUpLeftRightBF(Zzzzzz, &t[8], &t[21], 00)); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(24 == checkAscension(root)); + ASSERT_TRUE(t[21].checkLinkageUpLeftRightBF(&t[16], &t[18], &t[30], +1)); + ASSERT_TRUE(t[18].checkLinkageUpLeftRightBF(&t[21], &t[17], &t[19], 00)); + ASSERT_TRUE(t[30].checkLinkageUpLeftRightBF(&t[21], &t[22], &t[31], -1)); + ASSERT_TRUE(t[22].checkLinkageUpLeftRightBF(&t[30], Zzzzzz, &t[23], +1)); + ASSERT_TRUE(t[16].checkLinkageUpLeftRightBF(Zzzzzz, &t[8], &t[21], 00)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(24 == checkAscension(root)); // REMOVE 8 // 16 @@ -1217,15 +1218,15 @@ TEST_CASE("MutationManual") // / ` / ` ` / ` ` // 1 3 5 7 11 13 15 23 std::puts("REMOVE 8:"); - REQUIRE(t[8].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); + ASSERT_TRUE(t[8].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); remove(&root, &t[8]); - REQUIRE(&t[16] == root); + ASSERT_TRUE(&t[16] == root); print(root); - REQUIRE(t[9].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); - REQUIRE(t[10].checkLinkageUpLeftRightBF(&t[12], Zzzzz, &t[11], +1)); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(23 == checkAscension(root)); + ASSERT_TRUE(t[9].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); + ASSERT_TRUE(t[10].checkLinkageUpLeftRightBF(&t[12], Zzzzz, &t[11], +1)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(23 == checkAscension(root)); // REMOVE 9 // 16 @@ -1238,15 +1239,15 @@ TEST_CASE("MutationManual") // / ` / ` / ` ` // 1 3 5 7 13 15 23 std::puts("REMOVE 9:"); - REQUIRE(t[9].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); + ASSERT_TRUE(t[9].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); remove(&root, &t[9]); - REQUIRE(&t[16] == root); + ASSERT_TRUE(&t[16] == root); print(root); - REQUIRE(t[10].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); - REQUIRE(t[12].checkLinkageUpLeftRightBF(&t[10], &t[11], &t[14], +1)); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(22 == checkAscension(root)); + ASSERT_TRUE(t[10].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); + ASSERT_TRUE(t[12].checkLinkageUpLeftRightBF(&t[10], &t[11], &t[14], +1)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(22 == checkAscension(root)); // REMOVE 1 // 16 @@ -1259,14 +1260,14 @@ TEST_CASE("MutationManual") // ` / ` / ` ` // 3 5 7 13 15 23 std::puts("REMOVE 1:"); - REQUIRE(t[1].checkLinkageUpLeftRightBF(&t[2], Zzzzz, Zzzzz, 00)); + ASSERT_TRUE(t[1].checkLinkageUpLeftRightBF(&t[2], Zzzzz, Zzzzz, 00)); remove(&root, &t[1]); - REQUIRE(&t[16] == root); + ASSERT_TRUE(&t[16] == root); print(root); - REQUIRE(t[2].checkLinkageUpLeftRightBF(&t[4], Zzzzz, &t[3], +1)); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(21 == checkAscension(root)); + ASSERT_TRUE(t[2].checkLinkageUpLeftRightBF(&t[4], Zzzzz, &t[3], +1)); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(21 == checkAscension(root)); } auto getRandomByte() @@ -1274,7 +1275,7 @@ auto getRandomByte() return static_cast((0xFFLL * std::rand()) / RAND_MAX); } -TEST_CASE("MutationRandomized") +TEST(Cavl, MutationRandomized) { using N = Node; std::array t{}; @@ -1289,16 +1290,16 @@ TEST_CASE("MutationRandomized") std::uint64_t cnt_addition = 0; std::uint64_t cnt_removal = 0; - const auto validate = [&]() { - REQUIRE(size == std::accumulate(mask.begin(), mask.end(), 0U, [](const std::size_t a, const std::size_t b) { - return a + b; - })); - REQUIRE(nullptr == findBrokenBalanceFactor(root)); - REQUIRE(nullptr == findBrokenAncestry(root)); - REQUIRE(size == checkAscension(root)); + const auto validate = [&] { + ASSERT_TRUE(size == std::accumulate(mask.begin(), mask.end(), 0U, [](const std::size_t a, const std::size_t b) { + return a + b; + })); + ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); + ASSERT_TRUE(nullptr == findBrokenAncestry(root)); + ASSERT_TRUE(size == checkAscension(root)); std::array new_mask{}; traverse(root, [&](const N* node) { new_mask.at(node->value) = true; }); - REQUIRE(mask == new_mask); // Otherwise, the contents of the tree does not match our expectations. + ASSERT_TRUE(mask == new_mask); // Otherwise, the contents of the tree does not match our expectations. }; validate(); @@ -1306,22 +1307,21 @@ TEST_CASE("MutationRandomized") const auto predicate = [&](const N& v) { return x - v.value; }; if (N* const existing = search(&root, predicate)) { - REQUIRE(mask.at(x)); - REQUIRE(x == existing->value); - REQUIRE(x == search(&root, predicate, []() -> N* { - FAIL("Attempted to create a new node when there is one already"); - return nullptr; - })->value); + ASSERT_TRUE(mask.at(x)); + ASSERT_TRUE(x == existing->value); + ASSERT_TRUE(x == search(&root, predicate, []() -> N* { + throw std::logic_error("Attempted to create a new node when there is one already"); + })->value); } else { - REQUIRE(!mask.at(x)); + ASSERT_TRUE(!mask.at(x)); bool factory_called = false; - REQUIRE(x == search(&root, predicate, [&]() -> N* { - factory_called = true; - return &t.at(x); - })->value); - REQUIRE(factory_called); + ASSERT_TRUE(x == search(&root, predicate, [&]() -> N* { + factory_called = true; + return &t.at(x); + })->value); + ASSERT_TRUE(factory_called); size++; cnt_addition++; mask.at(x) = true; @@ -1332,17 +1332,17 @@ TEST_CASE("MutationRandomized") const auto predicate = [&](const N& v) { return x - v.value; }; if (N* const existing = search(&root, predicate)) { - REQUIRE(mask.at(x)); - REQUIRE(x == existing->value); + ASSERT_TRUE(mask.at(x)); + ASSERT_TRUE(x == existing->value); remove(&root, existing); size--; cnt_removal++; mask.at(x) = false; - REQUIRE(nullptr == search(&root, predicate)); + ASSERT_TRUE(nullptr == search(&root, predicate)); } else { - REQUIRE(!mask.at(x)); + ASSERT_TRUE(!mask.at(x)); } }; @@ -1367,7 +1367,8 @@ TEST_CASE("MutationRandomized") << std::endl; if (root != nullptr) { - std::cout << "\tmin/max: " << unsigned(root->min()->value) << "/" << unsigned(root->max()->value) // + std::cout << "\tmin/max: " << static_cast(root->min()->value) // + << "/" << static_cast(root->max()->value) // << std::endl; } validate(); diff --git a/tests/test_private_crc.cpp b/tests/test_private_crc.cpp deleted file mode 100644 index 0a7d03f..0000000 --- a/tests/test_private_crc.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// This software is distributed under the terms of the MIT License. -// Copyright (c) 2016-2020 OpenCyphal Development Team. -// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -#include "exposed.hpp" -#include "catch.hpp" - -TEST_CASE("TransferCRC") -{ - using exposed::crcAdd; - using exposed::crcValue; - std::uint32_t crc = 0xFFFFFFFFU; - - crc = crcAdd(crc, 1, "1"); - REQUIRE(0x90F599E3U == crcValue(crc)); - crc = crcAdd(crc, 1, "2"); - REQUIRE(0x7355C460U == crcValue(crc)); - crc = crcAdd(crc, 1, "3"); - REQUIRE(0x107B2FB2U == crcValue(crc)); - - crc = crcAdd(crc, 6, "456789"); - REQUIRE(0xE3069283U == crcValue(crc)); -} - -TEST_CASE("CyphalHeaderCRC") -{ - using exposed::cyphalHeaderCrcAdd; - - // Standard Cyphal header size = 24. The last 2 bytes are for the CRC. - - // Standard use case. Header size = 24; CRC is calculated from the first 22 bytes. - // The last two bytes (CRC) are ignored in the calculation. - std::uint16_t crc = 0xFFFFU; - const auto* header = reinterpret_cast("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x20\x21\x22\x23\x24"); - crc = cyphalHeaderCrcAdd(crc, 22, header); - REQUIRE(0xB731 == crc); - - // Verify CRC when the size field is equal to the size of the header. - crc = 0xFFFFU; - header = reinterpret_cast("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x20\x21\x22"); - crc = cyphalHeaderCrcAdd(crc, 22, header); - REQUIRE(0xB731 == crc); - crc = 0xFFFFU; - header = reinterpret_cast("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x20\x21\x22\x23\x24"); - crc = cyphalHeaderCrcAdd(crc, 24, header); - REQUIRE(0x96BB == crc); - - // Verify CRC when the size field is less than the size of the header. Extra data is ignored. - crc = 0xFFFFU; - header = reinterpret_cast("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x20\x21\x22\x23\x24"); - crc = cyphalHeaderCrcAdd(crc, 10, header); - REQUIRE(0x9F09 == crc); -} diff --git a/tests/test_private_rx.cpp b/tests/test_private_rx.cpp deleted file mode 100644 index 74981a9..0000000 --- a/tests/test_private_rx.cpp +++ /dev/null @@ -1,753 +0,0 @@ -// This software is distributed under the terms of the MIT License. -// Copyright (c) 2016-2020 OpenCyphal Development Team. -/// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -#include "exposed.hpp" -#include "helpers.hpp" -#include "catch/catch.hpp" -#include - -static const uint32_t CRC_INITIAL = 0xFFFFFFFFU; -static const uint32_t UDPARD_MAX_FRAME_INDEX = 0x7FFFFFFFU; - -TEST_CASE("rxTryParseFrame") -{ - using exposed::RxFrameModel; - using exposed::rxTryParseFrame; - - RxFrameModel model{}; - UdpardSessionSpecifier specifier{}; - UdpardFrameHeader header{}; - - const auto parse = [&](const UdpardMicrosecond timestamp_usec, - const std::vector& payload) { - static std::vector payload_storage; - payload_storage = payload; - UdpardFrame frame{}; - frame.payload_size = std::size(payload); - frame.payload = payload_storage.data(); - model = RxFrameModel{}; - return rxTryParseFrame(timestamp_usec, &frame, &model); - }; - - // Some initial header setup and payload test - header.version = 0x01; - header.priority = 0x07; - header.source_node_id = 0x0000; - header.destination_node_id = 0xFFFF; - header.data_specifier = 0x0000; - header.transfer_id = 0x0000000000000001; - header.frame_index_eot = 1U << 31U; - header._opaque = 0x0000; - header.cyphal_header_checksum = 0x0000; - - - const std::vector& test_payload = { - 0x01, // Version - 0x07, // Priority - 0x00, 0x00, // Source Node ID - 0xFF, 0xFF, // Destination Node ID - 0x00, 0x00, // Data Specifier - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transfer ID - 0x00, 0x00, 0x00, 0x80, // Frame EOT - 0x00, 0x00, // Opaque Data - 0x00, 0x00, // Transfer CRC - }; - - REQUIRE(sizeof(header) == 24U); - REQUIRE(std::size(test_payload) == 24U); - - auto *test_header_ptr = reinterpret_cast(&header); - auto test_payload_storage = std::vector(test_header_ptr, test_header_ptr + sizeof(header)); - REQUIRE(test_payload_storage == test_payload); - - // MESSAGE - REQUIRE(0 == exposed::txMakeMessageSessionSpecifier(0b0, 0b0, 0xc0a80000, &specifier)); - REQUIRE(parse(543210U, - { - 0x01, // Version - 0x00, // Priority - 0x00, 0x00, // Source Node ID - 0xFF, 0xFF, // Destination Node ID - 0x00, 0x00, // Data Specifier - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transfer ID - 0x00, 0x00, 0x00, 0x80, // Frame EOT - 0x00, 0x00, // Opaque Data - 0x00, 0x00, // Transfer CRC - 0, 1, 2, 3, 4, 5, 6, 7 // Payload - })); - REQUIRE(model.timestamp_usec == 543210U); - REQUIRE(model.priority == UdpardPriorityExceptional); - REQUIRE(model.transfer_kind == UdpardTransferKindMessage); - REQUIRE(model.port_id == 0U); - REQUIRE(model.source_node_id == 0U); - REQUIRE(model.destination_node_id == UDPARD_NODE_ID_UNSET); - REQUIRE(model.transfer_id == 1U); - REQUIRE((model.frame_index & UDPARD_MAX_FRAME_INDEX) == 0U); - REQUIRE(model.start_of_transfer); - REQUIRE(model.end_of_transfer); - REQUIRE(model.payload_size == 8); - REQUIRE(model.payload[0] == 0); - REQUIRE(model.payload[1] == 1); - REQUIRE(model.payload[2] == 2); - REQUIRE(model.payload[3] == 3); - REQUIRE(model.payload[4] == 4); - REQUIRE(model.payload[5] == 5); - REQUIRE(model.payload[6] == 6); - REQUIRE(model.payload[7] == 7); - - // SIMILAR BUT INVALID - REQUIRE(0 == exposed::txMakeMessageSessionSpecifier(0b0, 0b0, 0xc0a80000, &specifier)); - REQUIRE(!parse(543210U, - { - 0x01, // Version - 0x00, // Priority - 0x00, 0x00, // Source Node ID - 0xFF, 0xFF, // Destination Node ID - 0x00, 0x00, // Data Specifier - 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transfer ID - 0x02, 0x00, 0x00, 0x80, // Frame EOT - 0x00, 0x00, // Opaque Data - 0x00, 0x00, // Transfer CRC - })); // MFT FRAMES REQUIRE PAYLOAD - - // MESSAGE - REQUIRE(0 == exposed::txMakeMessageSessionSpecifier(0b0110011001100, 0b0100111, 0xc0a80000, &specifier)); - REQUIRE(parse(123456U, - { - 0x01, // Version - 0x01, // Priority - 0x27, 0x00, // Source Node ID - 0xFF, 0xFF, // Destination Node ID - 0xCC, 0x0C, // Data Specifier - 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transfer ID - 0x00, 0x00, 0x00, 0x00, // Frame EOT - 0x00, 0x00, // Opaque Data - 0x00, 0x00, // Transfer CRC - 0, 1, 2, 3, 4, 5, 6 // Payload - })); - REQUIRE(model.timestamp_usec == 123456U); - REQUIRE(model.priority == UdpardPriorityImmediate); - REQUIRE(model.transfer_kind == UdpardTransferKindMessage); - REQUIRE(model.port_id == 0b0110011001100U); - REQUIRE(model.source_node_id == 0b0100111U); - REQUIRE(model.destination_node_id == UDPARD_NODE_ID_UNSET); - REQUIRE(model.transfer_id == 23U); - REQUIRE((model.frame_index & UDPARD_MAX_FRAME_INDEX) == 0U); - REQUIRE(model.start_of_transfer); - REQUIRE(!model.end_of_transfer); - REQUIRE(model.payload_size == 7); - REQUIRE(model.payload[0] == 0); - REQUIRE(model.payload[1] == 1); - REQUIRE(model.payload[2] == 2); - REQUIRE(model.payload[3] == 3); - REQUIRE(model.payload[4] == 4); - REQUIRE(model.payload[5] == 5); - REQUIRE(model.payload[6] == 6); - // SIMILAR BUT INVALID - REQUIRE(0 == exposed::txMakeMessageSessionSpecifier(0b0110011001100, 0b0100111, 0xc0a80000, &specifier)); - // NO HEADER - REQUIRE(!parse(123456U, {})); - // ANON NOT SINGLE FRAME - REQUIRE(0 == exposed::txMakeMessageSessionSpecifier(0b0110011001100, 0b1111111111111111, 0xc0a80000, &specifier)); - REQUIRE(!parse(123456U, - { - 0x01, // Version - 0x01, // Priority - 0xFF, 0xFF, // Source Node ID - 0xFF, 0xFF, // Destination Node ID - 0xCC, 0x0C, // Data Specifier - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transfer ID - 0x17, 0x00, 0x00, 0x80, // Frame EOT - 0x00, 0x00, // Opaque Data - 0x00, 0x00, // Transfer CRC - 0, 1, 2, 3, 4, 5, 6 // Payload - })); - - // ANONYMOUS MESSAGE - REQUIRE(0 == exposed::txMakeMessageSessionSpecifier(0b0110011001101, 0b1111111111111111, 0xc0a80000, &specifier)); - REQUIRE(parse(12345U, - { - 0x01, // Version - 0x02, // Priority - 0xFF, 0xFF, // Source Node ID - 0xFF, 0xFF, // Destination Node ID - 0xCD, 0x0C, // Data Specifier - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transfer ID - 0x00, 0x00, 0x00, 0x80, // Frame EOT - 0x00, 0x00, // Opaque Data - 0x00, 0x00, // Transfer CRC - })); - REQUIRE(model.timestamp_usec == 12345U); - REQUIRE(model.priority == UdpardPriorityFast); - REQUIRE(model.transfer_kind == UdpardTransferKindMessage); - REQUIRE(model.port_id == 0b0110011001101U); - REQUIRE(model.source_node_id == UDPARD_NODE_ID_UNSET); - REQUIRE(model.destination_node_id == UDPARD_NODE_ID_UNSET); - REQUIRE(model.transfer_id == 0U); - REQUIRE((model.frame_index & UDPARD_MAX_FRAME_INDEX) == 0U); - REQUIRE(model.start_of_transfer); - REQUIRE(model.end_of_transfer); - REQUIRE(model.payload_size == 0); - // SIMILAR BUT INVALID - REQUIRE(!parse(12345U, {})); // NO HEADER - - // REQUEST - REQUIRE(0 == - exposed::txMakeServiceSessionSpecifier(0b0000110011, 0b0100111, 0xc0a80000, &specifier)); - REQUIRE(parse(999'999U, - { - 0x01, // Version - 0x03, // Priority - 0x27, 0x00, // Source Node ID - 0x1A, 0x00, // Destination Node ID - 0x33, 0xC0, // Data Specifier - 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transfer ID - 0xFF, 0x00, 0x00, 0x80, // Frame EOT - 0x00, 0x00, // Opaque Data - 0x00, 0x00, // Transfer CRC - 0, 1, 2, 3 // Payload - })); - - REQUIRE(model.timestamp_usec == 999'999U); - REQUIRE(model.priority == UdpardPriorityHigh); - REQUIRE(model.transfer_kind == UdpardTransferKindRequest); - REQUIRE(model.port_id == 0b0000110011U); - REQUIRE(model.source_node_id == 0b0100111U); - REQUIRE(model.destination_node_id == 0b0011010U); - REQUIRE(model.transfer_id == 31U); - REQUIRE(!model.start_of_transfer); - REQUIRE(model.end_of_transfer); - REQUIRE(model.payload_size == 4); - REQUIRE(model.payload[0] == 0); - REQUIRE(model.payload[1] == 1); - REQUIRE(model.payload[2] == 2); - REQUIRE(model.payload[3] == 3); - // SIMILAR BUT INVALID (Source Node ID cant be equal to Destination Node ID) - REQUIRE(!parse(999'999U, {})); // NO HEADER - REQUIRE(0 == - exposed::txMakeServiceSessionSpecifier(0b0000110011, 0b0100111, 0xc0a80000, &specifier)); - REQUIRE(!parse(999'999U, - { - 0x01, // Version - 0x03, // Priority - 0x27, 0x00, // Source Node ID - 0x27, 0x00, // Destination Node ID - 0x33, 0xC0, // Data Specifier - 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transfer ID - 0xFF, 0x00, 0x00, 0x80, // Frame EOT - 0x00, 0x00, // Opaque Data - 0x00, 0x00, // Transfer CRC - 0, 1, 2, 3 // Payload - })); - - // RESPONSE - REQUIRE(0 == - exposed::txMakeServiceSessionSpecifier(0b0000110011, 0b00011010, 0xc0a80000, &specifier)); - REQUIRE(parse(888'888, - { - 0x01, // Version - 0x04, // Priority - 0x1A, 0x00, // Source Node ID - 0x27, 0x00, // Destination Node ID - 0x33, 0x80, // Data Specifier - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transfer ID - 0xFF, 0x00, 0x00, 0x80, // Frame EOT - 0x00, 0x00, // Opaque Data - 0x00, 0x00, // Transfer CRC - 255 // Payload - })); - REQUIRE(model.timestamp_usec == 888'888U); - REQUIRE(model.priority == UdpardPriorityNominal); - REQUIRE(model.transfer_kind == UdpardTransferKindResponse); - REQUIRE(model.port_id == 0b0000110011U); - REQUIRE(model.source_node_id == 0b0011010U); - REQUIRE(model.destination_node_id == 0b0100111U); - REQUIRE(model.transfer_id == 1U); - REQUIRE(!model.start_of_transfer); - REQUIRE(model.end_of_transfer); - REQUIRE(model.payload_size == 1); - REQUIRE(model.payload[0] == 255); - // SIMILAR BUT INVALID (Source Node ID cant be equal to Destination Node ID) - REQUIRE(!parse(888'888U, {})); // NO TAIL BYTE - REQUIRE( - 0 == - exposed::txMakeServiceSessionSpecifier(0b0000110011, 0b00011010, 0xc0a80000, &specifier)); - REQUIRE(!parse(888'888, - { - 0x01, // Version - 0x04, // Priority - 0x1A, 0x00, // Source Node ID - 0x1A, 0x00, // Destination Node ID - 0x33, 0x80, // Data Specifier - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transfer ID - 0xFF, 0x00, 0x00, 0x80, // Frame EOT - 0x00, 0x00, // Opaque Data - 0x00, 0x00, // Transfer CRC - 255 // Payload - })); -} -TEST_CASE("rxSessionWritePayload") -{ - using helpers::Instance; - using exposed::RxSession; - using exposed::rxSessionWritePayload; - using exposed::rxSessionRestart; - - Instance ins; - RxSession rxs; - rxs.transfer_id = 0U; - - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 0); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 0); - - // Regular write, the RX state is uninitialized so a new allocation will take place. - REQUIRE(0 == rxSessionWritePayload(&ins.getInstance(), &rxs, 10, 5, "\x00\x01\x02\x03\x04")); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 10); - REQUIRE(rxs.payload_size == 5); - REQUIRE(rxs.payload != nullptr); - REQUIRE(rxs.payload[0] == 0); - REQUIRE(rxs.payload[1] == 1); - REQUIRE(rxs.payload[2] == 2); - REQUIRE(rxs.payload[3] == 3); - REQUIRE(rxs.payload[4] == 4); - - // Appending the pre-allocated storage. - REQUIRE(0 == rxSessionWritePayload(&ins.getInstance(), &rxs, 10, 4, "\x05\x06\x07\x08")); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 10); - REQUIRE(rxs.payload_size == 9); - REQUIRE(rxs.payload != nullptr); - REQUIRE(rxs.payload[0] == 0); - REQUIRE(rxs.payload[1] == 1); - REQUIRE(rxs.payload[2] == 2); - REQUIRE(rxs.payload[3] == 3); - REQUIRE(rxs.payload[4] == 4); - REQUIRE(rxs.payload[5] == 5); - REQUIRE(rxs.payload[6] == 6); - REQUIRE(rxs.payload[7] == 7); - REQUIRE(rxs.payload[8] == 8); - - // Implicit truncation -- too much payload, excess ignored. - REQUIRE(0 == rxSessionWritePayload(&ins.getInstance(), &rxs, 10, 3, "\x09\x0A\x0B")); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 10); - REQUIRE(rxs.payload_size == 10); - REQUIRE(rxs.payload != nullptr); - REQUIRE(rxs.payload[0] == 0); - REQUIRE(rxs.payload[1] == 1); - REQUIRE(rxs.payload[2] == 2); - REQUIRE(rxs.payload[3] == 3); - REQUIRE(rxs.payload[4] == 4); - REQUIRE(rxs.payload[5] == 5); - REQUIRE(rxs.payload[6] == 6); - REQUIRE(rxs.payload[7] == 7); - REQUIRE(rxs.payload[8] == 8); - REQUIRE(rxs.payload[9] == 9); - - // Storage is already full, write ignored. - REQUIRE(0 == rxSessionWritePayload(&ins.getInstance(), &rxs, 10, 3, "\x0C\x0D\x0E")); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 10); - REQUIRE(rxs.payload_size == 10); - REQUIRE(rxs.payload != nullptr); - REQUIRE(rxs.payload[0] == 0); - REQUIRE(rxs.payload[1] == 1); - REQUIRE(rxs.payload[2] == 2); - REQUIRE(rxs.payload[3] == 3); - REQUIRE(rxs.payload[4] == 4); - REQUIRE(rxs.payload[5] == 5); - REQUIRE(rxs.payload[6] == 6); - REQUIRE(rxs.payload[7] == 7); - REQUIRE(rxs.payload[8] == 8); - REQUIRE(rxs.payload[9] == 9); - - // Restart frees the buffer. The transfer-ID will be incremented, too. - rxSessionRestart(&ins.getInstance(), &rxs); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 0); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 0); - REQUIRE(rxs.payload_size == 0); - REQUIRE(rxs.payload == nullptr); - REQUIRE(rxs.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs.transfer_id == 1); - - // Double restart has no effect on memory. - rxs.calculated_crc = 0x1234U; - rxs.transfer_id = 23; - rxSessionRestart(&ins.getInstance(), &rxs); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 0); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 0); - REQUIRE(rxs.payload_size == 0); - REQUIRE(rxs.payload == nullptr); - REQUIRE(rxs.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs.transfer_id == 24U); - - // Restart with a transfer-ID overflow. - rxs.calculated_crc = 0x1234U; - rxs.transfer_id = 0xFFFFFFFFFFFFFFFF; - rxSessionRestart(&ins.getInstance(), &rxs); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 0); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 0); - REQUIRE(rxs.payload_size == 0); - REQUIRE(rxs.payload == nullptr); - REQUIRE(rxs.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs.transfer_id == 0U); - - // Write into a zero-capacity storage. NULL at the output. - REQUIRE(0 == rxSessionWritePayload(&ins.getInstance(), &rxs, 0, 3, "\x00\x01\x02")); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 0); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 0); - REQUIRE(rxs.payload_size == 0); - REQUIRE(rxs.payload == nullptr); - - // Write with OOM. - ins.getAllocator().setAllocationCeiling(5); - REQUIRE(-UDPARD_ERROR_OUT_OF_MEMORY == rxSessionWritePayload(&ins.getInstance(), &rxs, 10, 3, "\x00\x01\x02")); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 0); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 0); - REQUIRE(rxs.payload_size == 0); - REQUIRE(rxs.payload == nullptr); -} - -TEST_CASE("rxSessionUpdate") -{ - using helpers::Instance; - using exposed::RxSession; - using exposed::RxFrameModel; - using exposed::rxSessionUpdate; - using exposed::crcAdd; - - Instance ins; - ins.getAllocator().setAllocationCeiling(16); - Instance ins_1; - ins_1.getAllocator().setAllocationCeiling(16); - - - RxFrameModel frame; - frame.timestamp_usec = 10'000'000; - frame.priority = UdpardPrioritySlow; - frame.transfer_kind = UdpardTransferKindMessage; - frame.port_id = 2'222; - frame.source_node_id = 55; - frame.destination_node_id = UDPARD_NODE_ID_UNSET; - frame.transfer_id = 11; - frame.start_of_transfer = true; - frame.end_of_transfer = true; - frame.payload_size = 3 + 4; // 3 payload + 4 CRC - frame.payload = reinterpret_cast("\x01\x01\x01\x70\x2A\xEC\x24"); // \x70\x2A\xEC\x24 is CRC - - RxSession rxs; - RxSession rxs_1; - rxs.transfer_id = 31; - rxs.redundant_transport_index = 1; - rxs_1.transfer_id = 32; - rxs_1.redundant_transport_index = 1; - - UdpardRxTransfer transfer{}; - - const auto update = [&](const std::uint8_t redundant_transport_index, - const std::uint64_t tid_timeout_usec, - const std::size_t extent) { - return rxSessionUpdate(&ins.getInstance(), - &rxs, - &frame, - redundant_transport_index, - tid_timeout_usec, - extent, - &transfer); - }; - - const auto updateAnotherSession = [&](const std::uint8_t redundant_transport_index, - const std::uint64_t tid_timeout_usec, - const std::size_t extent) { - return rxSessionUpdate(&ins_1.getInstance(), - &rxs_1, - &frame, - redundant_transport_index, - tid_timeout_usec, - extent, - &transfer); - }; - - const auto crc = [](const char* const string) { return crcAdd(0xFFFFFFFFU, std::strlen(string), string); }; - - // Accept one transfer. - REQUIRE(1 == update(1, 1'000'000, 16)); - REQUIRE(rxs.transfer_timestamp_usec == 10'000'000); - REQUIRE(rxs.payload_size == 0); // Handed over to the output transfer. - REQUIRE(rxs.payload == nullptr); // Handed over to the output transfer. - REQUIRE(rxs.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs.transfer_id == 12U); // Incremented. - REQUIRE(rxs.redundant_transport_index == 1); - REQUIRE(transfer.timestamp_usec == 10'000'000); - REQUIRE(transfer.metadata.priority == UdpardPrioritySlow); - REQUIRE(transfer.metadata.transfer_kind == UdpardTransferKindMessage); - REQUIRE(transfer.metadata.port_id == 2'222); - REQUIRE(transfer.metadata.remote_node_id == 55); - REQUIRE(transfer.metadata.transfer_id == 11); - REQUIRE(transfer.payload_size == 3); // Payload size should not include the CRC (3 byte payload + 4 byte CRC) - REQUIRE(0 == std::memcmp(transfer.payload, "\x01\x01\x01", 3)); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 16); - ins.getAllocator().deallocate(transfer.payload); - - // Valid next transfer, wrong transport. - frame.timestamp_usec = 10'000'100; - frame.transfer_id = 12; - frame.payload = reinterpret_cast("\x02\x02\x02\x6E\xB1\x75\xE9"); - REQUIRE(0 == update(2, 1'000'000, 16)); - REQUIRE(rxs.transfer_timestamp_usec == 10'000'000); - REQUIRE(rxs.payload_size == 0); // Handed over to the output transfer. - REQUIRE(rxs.payload == nullptr); // Handed over to the output transfer. - REQUIRE(rxs.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs.transfer_id == 12U); // Incremented. - REQUIRE(rxs.redundant_transport_index == 1); - - // Correct transport. - frame.timestamp_usec = 10'000'050; - frame.payload = reinterpret_cast("\x03\x03\x03\x64\x38\xFD\xAD"); - REQUIRE(1 == update(1, 1'000'000, 16)); - REQUIRE(rxs.transfer_timestamp_usec == 10'000'050); - REQUIRE(rxs.payload_size == 0); - REQUIRE(rxs.payload == nullptr); - REQUIRE(rxs.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs.transfer_id == 13U); - REQUIRE(rxs.redundant_transport_index == 1); - REQUIRE(transfer.timestamp_usec == 10'000'050); - REQUIRE(transfer.metadata.priority == UdpardPrioritySlow); - REQUIRE(transfer.metadata.transfer_kind == UdpardTransferKindMessage); - REQUIRE(transfer.metadata.port_id == 2'222); - REQUIRE(transfer.metadata.remote_node_id == 55); - REQUIRE(transfer.metadata.transfer_id == 12); - REQUIRE(transfer.payload_size == 3); // Payload size should not include the CRC (3 byte payload + 4 byte CRC) - REQUIRE(0 == std::memcmp(transfer.payload, "\x03\x03\x03", 3)); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 16); - ins.getAllocator().deallocate(transfer.payload); - - // Same TID. - frame.timestamp_usec = 10'000'200; - frame.transfer_id = 12; - frame.payload = reinterpret_cast("\x04\x04\x04\xA3\xF1\xAA\x77"); - REQUIRE(0 == update(1, 1'000'200, 16)); - REQUIRE(rxs.transfer_timestamp_usec == 10'000'050); - REQUIRE(rxs.payload_size == 0); - REQUIRE(rxs.payload == nullptr); - REQUIRE(rxs.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs.transfer_id == 13U); - REQUIRE(rxs.redundant_transport_index == 1); - - // Restart due to TID timeout, switch iface. - frame.timestamp_usec = 20'000'000; - frame.transfer_id = 12; - frame.payload = reinterpret_cast("\x05\x05\x05\xA9\x78\x22\x33"); - REQUIRE(1 == update(0, 1'000'000, 16)); - REQUIRE(rxs.transfer_timestamp_usec == 20'000'000); - REQUIRE(rxs.payload_size == 0); - REQUIRE(rxs.payload == nullptr); - REQUIRE(rxs.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs.transfer_id == 13U); - REQUIRE(rxs.redundant_transport_index == 0); - REQUIRE(transfer.timestamp_usec == 20'000'000); - REQUIRE(transfer.metadata.priority == UdpardPrioritySlow); - REQUIRE(transfer.metadata.transfer_kind == UdpardTransferKindMessage); - REQUIRE(transfer.metadata.port_id == 2'222); - REQUIRE(transfer.metadata.remote_node_id == 55); - REQUIRE(transfer.metadata.transfer_id == 12); - REQUIRE(transfer.payload_size == 3); // Payload size should not include the CRC (3 byte payload + 4 byte CRC) - REQUIRE(0 == std::memcmp(transfer.payload, "\x05\x05\x05", 3)); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 16); - ins.getAllocator().deallocate(transfer.payload); - - // Multi-frame, first frame - frame.timestamp_usec = 20'000'100; - frame.transfer_id = 13; - frame.end_of_transfer = false; - frame.payload_size = 7; - frame.frame_index = 0; - frame.payload = reinterpret_cast("\x06\x06\x06\x06\x06\x06\x06"); - REQUIRE(0 == update(0, 1'000'000, 16)); - - REQUIRE(rxs.transfer_timestamp_usec == 20'000'100); - REQUIRE(rxs.payload_size == 7); - REQUIRE(0 == std::memcmp(rxs.payload, "\x06\x06\x06\x06\x06\x06\x06", 7)); - REQUIRE(rxs.calculated_crc == crc("\x06\x06\x06\x06\x06\x06\x06")); - REQUIRE(rxs.transfer_id == 13U); - REQUIRE(rxs.redundant_transport_index == 0); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 16); - - // Update another session using same frame. - REQUIRE(0 == updateAnotherSession(1, 1'000'000, 16)); - REQUIRE(rxs_1.transfer_timestamp_usec == 20'000'100); - REQUIRE(rxs_1.payload_size == 7); - REQUIRE(0 == std::memcmp(rxs_1.payload, "\x06\x06\x06\x06\x06\x06\x06", 7)); - REQUIRE(rxs_1.calculated_crc == crc("\x06\x06\x06\x06\x06\x06\x06")); - REQUIRE(rxs_1.transfer_id == 13U); - REQUIRE(rxs_1.redundant_transport_index == 1); - REQUIRE(ins_1.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins_1.getAllocator().getTotalAllocatedAmount() == 16); - - // Multi-frame, bad middle, out-of-order - frame.timestamp_usec = 20'000'200; - frame.start_of_transfer = false; - frame.end_of_transfer = false; - frame.frame_index = 2 + static_cast(1U << static_cast(31U)); - frame.payload_size = 2; - frame.payload = reinterpret_cast("\x09\x09"); - REQUIRE(-UDPARD_ERROR_OUT_OF_ORDER == update(0, 1'000'000, 16)); - // The session should be restarted if an out of order frame is received - // and the entire transfer will be dropped. Verify that all variables have - // been set back to defaults by rxSessionRestart. - REQUIRE(rxs.total_payload_size == 0U); - REQUIRE(rxs.payload_size == 0U); - REQUIRE(rxs.payload == NULL); - REQUIRE(rxs.calculated_crc == CRC_INITIAL); - // Update another session using same frame, fail. - REQUIRE(-UDPARD_ERROR_OUT_OF_ORDER == updateAnotherSession(1, 1'000'000, 16)); - REQUIRE(rxs_1.total_payload_size == 0U); - REQUIRE(rxs_1.payload_size == 0U); - REQUIRE(rxs_1.payload == NULL); - REQUIRE(rxs_1.calculated_crc == CRC_INITIAL); - - // Once you get an out-of-order frame in a multiframe transfer, - // the entire payload needs to be resent, so start over. - - // Multi-frame, first frame - frame.timestamp_usec = 20'000'300; - frame.transfer_id = 14; - frame.start_of_transfer = true; - frame.end_of_transfer = false; - frame.payload_size = 7; - frame.frame_index = 0; - frame.payload = reinterpret_cast("\x06\x06\x06\x06\x06\x06\x06"); - REQUIRE(0 == update(0, 1'000'000, 16)); - - REQUIRE(rxs.transfer_timestamp_usec == 20'000'300); - REQUIRE(rxs.payload_size == 7); - REQUIRE(0 == std::memcmp(rxs.payload, "\x06\x06\x06\x06\x06\x06\x06", 7)); - REQUIRE(rxs.calculated_crc == crc("\x06\x06\x06\x06\x06\x06\x06")); - REQUIRE(rxs.transfer_id == 14U); - REQUIRE(rxs.redundant_transport_index == 0); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 16); - - // Update another session using same frame. - REQUIRE(0 == updateAnotherSession(1, 1'000'000, 16)); - REQUIRE(rxs_1.transfer_timestamp_usec == 20'000'300); - REQUIRE(rxs_1.payload_size == 7); - REQUIRE(0 == std::memcmp(rxs_1.payload, "\x06\x06\x06\x06\x06\x06\x06", 7)); - REQUIRE(rxs_1.calculated_crc == crc("\x06\x06\x06\x06\x06\x06\x06")); - REQUIRE(rxs_1.transfer_id == 14U); - REQUIRE(rxs_1.redundant_transport_index == 1); - REQUIRE(ins_1.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins_1.getAllocator().getTotalAllocatedAmount() == 16); - - // Multi-frame, middle. - frame.start_of_transfer = false; - frame.end_of_transfer = false; - frame.frame_index = 1; - frame.payload_size = 7; - frame.payload = reinterpret_cast("\x07\x07\x07\x07\x07\x07\x07"); - REQUIRE(0 == update(0, 1'000'000, 16)); - REQUIRE(rxs.transfer_timestamp_usec == 20'000'300); - REQUIRE(rxs.payload_size == 14); - REQUIRE(0 == std::memcmp(rxs.payload, "\x06\x06\x06\x06\x06\x06\x06\x07\x07\x07\x07\x07\x07\x07", 14)); - REQUIRE(rxs.calculated_crc == crc("\x06\x06\x06\x06\x06\x06\x06\x07\x07\x07\x07\x07\x07\x07")); - REQUIRE(rxs.transfer_id == 14U); - REQUIRE(rxs.redundant_transport_index == 0); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 16); - - // Update another session using same frame. - REQUIRE(0 == updateAnotherSession(1, 1'000'000, 16)); - REQUIRE(rxs_1.transfer_timestamp_usec == 20'000'300); - REQUIRE(rxs_1.payload_size == 14); - REQUIRE(0 == std::memcmp(rxs_1.payload, "\x06\x06\x06\x06\x06\x06\x06\x07\x07\x07\x07\x07\x07\x07", 14)); - REQUIRE(rxs_1.calculated_crc == crc("\x06\x06\x06\x06\x06\x06\x06\x07\x07\x07\x07\x07\x07\x07")); - REQUIRE(rxs_1.transfer_id == 14U); - REQUIRE(rxs_1.redundant_transport_index == 1); - REQUIRE(ins_1.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins_1.getAllocator().getTotalAllocatedAmount() == 16); - - // Multi-frame, last. - frame.start_of_transfer = false; - frame.end_of_transfer = true; - frame.frame_index = 2 + static_cast(1U << static_cast(31U)); - frame.payload_size = 8; // The payload is IMPLICITLY TRUNCATED, and the CRC IS STILL VALIDATED. - frame.payload = reinterpret_cast("\x09\x09\x09\x09\x32\x98\x04\x7B"); - REQUIRE(1 == update(0, 1'000'000, 16)); - REQUIRE(rxs.transfer_timestamp_usec == 20'000'300); // The timestamp is the same as the first frame. - REQUIRE(rxs.payload_size == 0); - REQUIRE(rxs.payload == nullptr); - REQUIRE(rxs.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs.transfer_id == 15U); - REQUIRE(rxs.redundant_transport_index == 0); - REQUIRE(transfer.timestamp_usec == 20'000'300); - REQUIRE(transfer.metadata.priority == UdpardPrioritySlow); - REQUIRE(transfer.metadata.transfer_kind == UdpardTransferKindMessage); - REQUIRE(transfer.metadata.port_id == 2'222); - REQUIRE(transfer.metadata.remote_node_id == 55); - REQUIRE(transfer.metadata.transfer_id == 14); - REQUIRE(transfer.payload_size == 16); - REQUIRE(0 == std::memcmp(transfer.payload, "\x06\x06\x06\x06\x06\x06\x06\x07\x07\x07\x07\x07\x07\x07\x09\x09", 16)); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 16); - ins.getAllocator().deallocate(transfer.payload); - - // Update another session using same frame. - REQUIRE(1 == updateAnotherSession(1, 1'000'000, 16)); - REQUIRE(rxs_1.transfer_timestamp_usec == 20'000'300); // The timestamp is the same as the first frame. - REQUIRE(rxs_1.payload_size == 0); - REQUIRE(rxs_1.payload == nullptr); - REQUIRE(rxs_1.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs_1.transfer_id == 15U); - REQUIRE(rxs_1.redundant_transport_index == 1); - REQUIRE(transfer.timestamp_usec == 20'000'300); - REQUIRE(transfer.metadata.priority == UdpardPrioritySlow); - REQUIRE(transfer.metadata.transfer_kind == UdpardTransferKindMessage); - REQUIRE(transfer.metadata.port_id == 2'222); - REQUIRE(transfer.metadata.remote_node_id == 55); - REQUIRE(transfer.metadata.transfer_id == 14); - REQUIRE(transfer.payload_size == 16); - REQUIRE(0 == std::memcmp(transfer.payload, "\x06\x06\x06\x06\x06\x06\x06\x07\x07\x07\x07\x07\x07\x07\x09\x09", 16)); - REQUIRE(ins_1.getAllocator().getNumAllocatedFragments() == 1); - REQUIRE(ins_1.getAllocator().getTotalAllocatedAmount() == 16); - ins_1.getAllocator().deallocate(transfer.payload); - - // Restart by TID timeout, not the first frame. - frame.timestamp_usec = 30'000'000; - frame.transfer_id = 12; // Goes back. - frame.start_of_transfer = false; - frame.end_of_transfer = false; - frame.payload_size = 7; - frame.payload = reinterpret_cast("\x0A\x0A\x0A\x0A\x0A\x0A\x0A"); - REQUIRE(0 == update(2, 1'000'000, 16)); - REQUIRE(rxs.transfer_timestamp_usec == 20'000'300); // No change. - REQUIRE(rxs.payload_size == 0); - REQUIRE(rxs.payload == nullptr); - REQUIRE(rxs.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs.transfer_id == 13U); - REQUIRE(rxs.redundant_transport_index == 2); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 0); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 0); - - // OOM -- reset on error. - frame.timestamp_usec = 20'000'400; - frame.transfer_id = 30; - frame.start_of_transfer = true; - frame.end_of_transfer = true; - frame.payload_size = 8; - frame.payload = reinterpret_cast("\x0E\x0E\x0E\x0E\x0E\x0E\x0E\xF7"); - REQUIRE((-UDPARD_ERROR_OUT_OF_MEMORY) == update(2, 1'000'000, 17)); // Exceeds the heap quota. - REQUIRE(rxs.transfer_timestamp_usec == 20'000'400); - REQUIRE(rxs.payload_size == 0); - REQUIRE(rxs.payload == nullptr); - REQUIRE(rxs.calculated_crc == 0xFFFFFFFFU); - REQUIRE(rxs.transfer_id == 31U); // Reset. - REQUIRE(rxs.redundant_transport_index == 2); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 0); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 0); -} diff --git a/tests/test_private_tx.cpp b/tests/test_private_tx.cpp deleted file mode 100644 index c35530f..0000000 --- a/tests/test_private_tx.cpp +++ /dev/null @@ -1,142 +0,0 @@ -// This software is distributed under the terms of the MIT License. -// Copyright (c) 2016-2020 OpenCyphal Development Team. -/// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -#include "exposed.hpp" -#include "helpers.hpp" -#include "catch/catch.hpp" - -constexpr uint16_t UDPARD_SUBJECT_ID_PORT = 16383U; -constexpr uint16_t UDPARD_UDP_PORT = 9382U; - -TEST_CASE("SessionSpecifier") -{ - // Message - UdpardSessionSpecifier specifier = {}; - REQUIRE(0 == exposed::txMakeMessageSessionSpecifier(0b0110011001100, 0b0100111, 0xc0a80000, &specifier)); - REQUIRE(UDPARD_UDP_PORT == specifier.data_specifier); - REQUIRE(0b11101111'0'0'00000'0'0'0001100'11001100 == specifier.destination_route_specifier); - REQUIRE(0b11000000'10101000'00000000'00100111 == specifier.source_route_specifier); - // Service Request - REQUIRE(0 == - exposed::txMakeServiceSessionSpecifier(0b0100110011, 0b1010101, 0xc0a80000, &specifier)); - REQUIRE(UDPARD_UDP_PORT == specifier.data_specifier); - REQUIRE(0b11101111'0'0'00000'1'0'0000001'00110011 == specifier.destination_route_specifier); - REQUIRE(0b11000000'10101000'00000000'01010101 == specifier.source_route_specifier); - // Service Response - REQUIRE(0 == - exposed::txMakeServiceSessionSpecifier(0b0100110011, 0b1010101, 0xc0a80000, &specifier)); - REQUIRE(UDPARD_UDP_PORT == specifier.data_specifier); - REQUIRE(0b11101111'0'0'00000'1'0'0000001'00110011 == specifier.destination_route_specifier); - REQUIRE(0b11000000'10101000'00000000'01010101 == specifier.source_route_specifier); -} - -TEST_CASE("adjustPresentationLayerMTU") {} - -TEST_CASE("txMakeSessionSpecifier") -{ - using exposed::txMakeSessionSpecifier; - - UdpardTransferMetadata meta{}; - UdpardSessionSpecifier specifier{}; - - const auto mk_meta = [&](const UdpardPriority priority, - const UdpardTransferKind kind, - const std::uint16_t port_id, - const std::uint16_t remote_node_id) { - meta.priority = priority; - meta.transfer_kind = kind; - meta.port_id = port_id; - meta.remote_node_id = remote_node_id; - return &meta; - }; - - union PriorityAlias - { - std::uint8_t bits; - UdpardPriority prio; - }; - /* - int32_t txMakeSessionSpecifier(const UdpardTransferMetadata* const tr, - const UdpardNodeID local_node_id, - const UdpardIPv4Addr local_node_addr, - const size_t presentation_layer_mtu, - UdpardSessionSpecifier* const spec); - */ - - // MESSAGE TRANSFERS - REQUIRE(0 == // Regular message. - txMakeSessionSpecifier(mk_meta(UdpardPriorityExceptional, - UdpardTransferKindMessage, - 0b1001100110011, - UDPARD_NODE_ID_UNSET), - 0b1010101, - 0xc0a80000, - &specifier)); - - REQUIRE(UDPARD_UDP_PORT == specifier.data_specifier); - REQUIRE(0b11101111'0'0'00000'0'0'0010011'00110011 == specifier.destination_route_specifier); - REQUIRE(0b11000000'10101000'00000000'01010101 == specifier.source_route_specifier); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == // Bad subject-ID. - txMakeSessionSpecifier(mk_meta(UdpardPriorityExceptional, - UdpardTransferKindMessage, - 0xFFFFU, - UDPARD_NODE_ID_UNSET), - 0b1010101, - 0xc0a80000, - &specifier)); - - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == // Bad priority. - txMakeSessionSpecifier(mk_meta(PriorityAlias{123}.prio, - UdpardTransferKindMessage, - 0b1001100110011, - UDPARD_NODE_ID_UNSET), - 0b1010101, - 0xc0a80000, - &specifier)); - - // SERVICE TRANSFERS - REQUIRE( - 0 == // Request. - txMakeSessionSpecifier(mk_meta(UdpardPriorityExceptional, UdpardTransferKindRequest, 0b0100110011, 0b0101010), - 0b1010101, - 0xc0a80000, - &specifier)); - REQUIRE( - 0 == // Response. - txMakeSessionSpecifier(mk_meta(UdpardPriorityExceptional, UdpardTransferKindResponse, 0b0100110011, 0b0101010), - 0b1010101, - 0xc0a80000, - &specifier)); - REQUIRE( - -UDPARD_ERROR_INVALID_ARGUMENT == // Anonymous source service transfers not permitted. - txMakeSessionSpecifier(mk_meta(UdpardPriorityExceptional, UdpardTransferKindRequest, 0b0100110011, 0b0101010), - UDPARD_NODE_ID_UNSET, - 0xc0a80000, - &specifier)); - - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == // Anonymous destination service transfers not permitted. - txMakeSessionSpecifier(mk_meta(UdpardPriorityExceptional, - UdpardTransferKindResponse, - 0b0100110011, - UDPARD_NODE_ID_UNSET), - 0b1010101, - 0xc0a80000, - &specifier)); - - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == // Bad service-ID. - txMakeSessionSpecifier(mk_meta(UdpardPriorityExceptional, UdpardTransferKindResponse, 0xFFFFU, 0b0101010), - 0b1010101, - 0xc0a80000, - &specifier)); - REQUIRE( - -UDPARD_ERROR_INVALID_ARGUMENT == // Bad priority. - txMakeSessionSpecifier(mk_meta(PriorityAlias{123}.prio, UdpardTransferKindResponse, 0b0100110011, 0b0101010), - 0b1010101, - 0xc0a80000, - &specifier)); -} - -TEST_CASE("txMakeTailByte") {} - -TEST_CASE("txRoundFramePayloadSizeUp") {} diff --git a/tests/test_public_roundtrip.cpp b/tests/test_public_roundtrip.cpp deleted file mode 100644 index 2ae628a..0000000 --- a/tests/test_public_roundtrip.cpp +++ /dev/null @@ -1,274 +0,0 @@ -// This software is distributed under the terms of the MIT License. -// Copyright (c) 2016-2020 OpenCyphal Development Team. -/// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -#include "helpers.hpp" -#include "exposed.hpp" -#include "catch/catch.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -TEST_CASE("RoundtripSimple") -{ - using helpers::getRandomNatural; - - struct TxState - { - UdpardTransferKind transfer_kind{}; - UdpardPriority priority{}; - UdpardPortID port_id{}; - std::size_t extent{}; - UdpardTransferID transfer_id{}; - UdpardRxSubscription subscription{}; - }; - - helpers::Instance ins_rx; - ins_rx.setNodeID(111); - - const auto get_random_priority = []() { - return static_cast(getRandomNatural(UDPARD_PRIORITY_MAX + 1U)); - }; - std::array tx_states{ - TxState{UdpardTransferKindMessage, get_random_priority(), 8191, 1000}, - TxState{UdpardTransferKindMessage, get_random_priority(), 511, 0}, - TxState{UdpardTransferKindMessage, get_random_priority(), 0, 13}, - TxState{UdpardTransferKindRequest, get_random_priority(), 511, 900}, - TxState{UdpardTransferKindRequest, get_random_priority(), 0, 0}, - TxState{UdpardTransferKindResponse, get_random_priority(), 0, 1}, - }; - std::size_t rx_worst_case_memory_consumption = 0; - for (auto& s : tx_states) - { - REQUIRE(1 == ins_rx.rxSubscribe(s.transfer_kind, - s.port_id, - s.extent, - UDPARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, - s.subscription)); - // The true worst case is 128 times larger, but there is only one transmitting node. - rx_worst_case_memory_consumption += sizeof(exposed::RxSession) + s.extent; - } - ins_rx.getAllocator().setAllocationCeiling(rx_worst_case_memory_consumption); // This is guaranteed to be enough. - - helpers::Instance ins_tx; - helpers::TxQueue que_tx(1024UL * 1024U * 1024U, UDPARD_MTU_MAX); - ins_tx.setNodeID(99); - ins_tx.setNodeAddr(0xc0a80000); - ins_tx.getAllocator().setAllocationCeiling(1024UL * 1024U * 1024U); - - using Pending = std::tuple; - std::unordered_map pending_transfers; - - std::atomic transfer_counter = 0; - std::atomic frames_in_flight = 0; - std::uint64_t peak_frames_in_flight = 0; - - std::mutex lock; - std::atomic keep_going = true; - - const auto writer_thread_fun = [&]() { - while (keep_going) - { - auto& st = tx_states.at(getRandomNatural(std::size(tx_states))); - - // Generate random payload. - // When multiframe transfer is implemented the size may be larger than expected to test the implicit - // truncation rule. - const auto payload_size = getRandomNatural(st.extent + 100U); - auto* const payload = static_cast(std::malloc(payload_size)); // NOLINT - std::generate_n(payload, payload_size, [&]() { return static_cast(getRandomNatural(256U)); }); - - // Generate the transfer. - const UdpardMicrosecond timestamp_usec = transfer_counter++; - UdpardTransferMetadata tran{}; - tran.priority = st.priority; - tran.transfer_kind = st.transfer_kind; - tran.port_id = st.port_id; - tran.remote_node_id = - (tran.transfer_kind == UdpardTransferKindMessage) ? UDPARD_NODE_ID_UNSET : ins_rx.getNodeID(); - tran.transfer_id = (st.transfer_id++) & UDPARD_TRANSFER_ID_MAX; - - // Use a random MTU. - que_tx.setMTU(static_cast(getRandomNatural(256U - sizeof(UdpardFrameHeader)) + sizeof(UdpardFrameHeader)) + 1U); - - // Push the transfer. - bool sleep = false; - { - std::lock_guard locker(lock); - const auto result = que_tx.push(&ins_tx.getInstance(), timestamp_usec, tran, payload_size, payload); - if (result > 0) - { - if (result > 1) - { - std::cout << "Warning: Multiframe transfer" << std::endl; - } - pending_transfers.emplace(timestamp_usec, Pending{tran, payload_size, payload}); - frames_in_flight += static_cast(result); - peak_frames_in_flight = std::max(peak_frames_in_flight, frames_in_flight); - } - else - { - if (result != -UDPARD_ERROR_OUT_OF_MEMORY) - { - // Can't use REQUIRE because it is not thread-safe. - throw std::logic_error("Unexpected result: " + std::to_string(result)); - } - sleep = true; - } - } - if (sleep) - { - std::cout << "TX OOM" << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - } - }; - - std::thread writer_thread(writer_thread_fun); - - /* - std::ofstream log_file("roundtrip_frames.log", std::ios::trunc | std::ios::out); - REQUIRE(log_file.good()); - */ - - try - { - const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(20); - while (true) - { - UdpardTxQueueItem* ti = nullptr; - { - std::lock_guard locker(lock); - ti = que_tx.pop(que_tx.peek()); - if (ti != nullptr) - { - REQUIRE(frames_in_flight > 0); - --frames_in_flight; - } - } - - if (ti != nullptr) - { - /* - const auto tail = static_cast(ti->frame.payload)[ti->frame.payload_size - 1U]; - log_file << ti->tx_deadline_usec << " " // - << std::hex << std::setfill('0') << std::setw(8) << ti->frame.extended_udp_id // - << " [" << std::dec << std::setfill(' ') << std::setw(2) << ti->frame.payload_size << "] " // - << (bool(tail & 128U) ? 'S' : ' ') // - << (bool(tail & 64U) ? 'E' : ' ') // - << (bool(tail & 32U) ? 'T' : ' ') // - << " " << std::uint16_t(tail & 31U) // - << '\n'; - */ - - UdpardRxTransfer transfer{}; - UdpardRxSubscription* subscription = nullptr; - - std::int8_t result = ins_rx.rxAccept( - ti->tx_deadline_usec, - ti->frame, - 0, - ti->specifier, - transfer, - &subscription); - - if (result == 1) - { - Pending reference{}; // Fetch the reference transfer from the list of pending. - { - std::lock_guard locker(lock); - const auto pt_it = pending_transfers.find(transfer.timestamp_usec); - REQUIRE(pt_it != pending_transfers.end()); - reference = pt_it->second; - pending_transfers.erase(pt_it); - } - const auto [ref_meta, ref_payload_size, ref_payload] = reference; - - REQUIRE(transfer.metadata.priority == ref_meta.priority); - REQUIRE(transfer.metadata.transfer_kind == ref_meta.transfer_kind); - REQUIRE(transfer.metadata.port_id == ref_meta.port_id); - REQUIRE(transfer.metadata.remote_node_id == ins_tx.getNodeID()); - REQUIRE(transfer.metadata.transfer_id == ref_meta.transfer_id); - // The payload size is not checked because the variance is huge due to padding and truncation. - if (transfer.payload != nullptr) - { - REQUIRE(0 == std::memcmp(transfer.payload, - ref_payload, - std::min(transfer.payload_size, ref_payload_size))); - } - else - { - REQUIRE(transfer.payload_size == 0U); - } - - ins_rx.getAllocator().deallocate(transfer.payload); - std::free(ref_payload); // NOLINT - } - else - { - REQUIRE((result == 0 || result == -UDPARD_ERROR_OUT_OF_ORDER)); - } - } - else - { - if (!keep_going) - { - break; - } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - - { - std::lock_guard locker(lock); - ins_tx.getAllocator().deallocate(ti); - } - - if (std::chrono::steady_clock::now() > deadline) - { - keep_going = false; - } - } - } - catch (...) - { - keep_going = false; - writer_thread.detach(); - throw; - } - - writer_thread.join(); - - REQUIRE(0 == frames_in_flight); - - std::cout << "TOTAL TRANSFERS: " << transfer_counter << "; OF THEM LOST: " << std::size(pending_transfers) - << std::endl; - std::cout << "PEAK FRAMES IN FLIGHT: " << peak_frames_in_flight << std::endl; - - std::size_t i = 0; - for (const auto& [k, v] : pending_transfers) - { - - UdpardTransferMetadata ref_meta; - uint64_t ref_payload_size {}; - std::tie(ref_meta, ref_payload_size, std::ignore) = v; - std::cout << "#" << i++ << "/" << std::size(pending_transfers) << ":" // - << " ts=" << k // - << " prio=" << static_cast(ref_meta.priority) // - << " prio=" << static_cast(ref_meta.priority) // - << " kind=" << static_cast(ref_meta.transfer_kind) // - << " port=" << ref_meta.port_id // - << " nid=" << static_cast(ref_meta.remote_node_id) // - << " tid=" << static_cast(ref_meta.transfer_id) // - << " size=" << ref_payload_size // - << std::endl; - } - - REQUIRE(0 == std::size(pending_transfers)); -} diff --git a/tests/test_public_rx.cpp b/tests/test_public_rx.cpp deleted file mode 100644 index 8c8dec2..0000000 --- a/tests/test_public_rx.cpp +++ /dev/null @@ -1,479 +0,0 @@ -// This software is distributed under the terms of the MIT License. -// Copyright (c) 2016-2020 OpenCyphal Development Team. -/// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -#include "exposed.hpp" -#include "helpers.hpp" -#include "catch/catch.hpp" -#include - -constexpr uint16_t UDPARD_SUBJECT_ID_PORT = 16383U; -constexpr uint16_t UDPARD_UDP_PORT = 9382U; - -// clang-tidy mistakenly suggests to avoid C arrays here, which is clearly an error -template -auto ensureAllNullptr(P* const (&arr)[N]) -> bool // NOLINT -{ - return std::all_of(std::begin(arr), std::end(arr), [](const auto* const x) { return x == nullptr; }); -} - -TEST_CASE("RxBasic0") -{ - using helpers::Instance; - using exposed::RxSession; - - Instance ins; - Instance ins_new; - UdpardRxTransfer transfer{}; - UdpardSessionSpecifier specifier{}; - UdpardFrameHeader header{}; - UdpardRxSubscription* subscription = nullptr; - - const auto accept = [&](const std::uint8_t redundant_transport_index, - const UdpardMicrosecond timestamp_usec, - UdpardFrameHeader frame_header, - UdpardSessionSpecifier session_specifier, - const std::vector& payload) { - auto *header_ptr = reinterpret_cast(&frame_header); - auto payload_storage = std::vector(header_ptr, header_ptr + sizeof(frame_header)); - static std::vector payload_buffer; - payload_buffer = payload; - payload_storage.insert(payload_storage.end(), payload_buffer.begin(), payload_buffer.end()); - UdpardFrame frame{}; - frame.payload_size = std::size(payload) + sizeof(frame_header); - frame.payload = payload_storage.data(); - return ins - .rxAccept(timestamp_usec, frame, redundant_transport_index, session_specifier, transfer, &subscription); - }; - - ins.getAllocator().setAllocationCeiling(sizeof(RxSession) + sizeof(UdpardFrameHeader) + - 16); // A session and a 16-byte payload buffer. - - // No subscriptions by default. - REQUIRE(ins.getMessageSubs().empty()); - REQUIRE(ins.getResponseSubs().empty()); - REQUIRE(ins.getRequestSubs().empty()); - - // Some initial header setup - header.version = 1; - - // A valid single-frame transfer for which there is no subscription. - subscription = nullptr; - header.priority = 0b001; - header.source_node_id = 0b0000000000100111; - header.destination_node_id = 0b1111111111111111; - header.data_specifier = 0b0000110011001100; - header.transfer_id = 1; - header.frame_index_eot = 1U << 31U; - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.destination_route_specifier = 0b11101111'00'01000'0'0'000110011001100; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00100111; - // This is an empty payload, the last four bytes are CRC. - REQUIRE(0 == accept(0, 100'000'000, header, specifier, {0, 0, 0, 0})); - REQUIRE(subscription == nullptr); - - // Create a message subscription. - UdpardRxSubscription sub_msg{}; - REQUIRE(1 == ins.rxSubscribe(UdpardTransferKindMessage, 0b0110011001100, 32, 2'000'000, sub_msg)); // New. - REQUIRE(0 == ins.rxSubscribe(UdpardTransferKindMessage, 0b0110011001100, 16, 1'000'000, sub_msg)); // Replaced. - REQUIRE(ins.getMessageSubs().at(0) == &sub_msg); - REQUIRE(ins.getMessageSubs().at(0)->port_id == 0b0110011001100); - REQUIRE(ins.getMessageSubs().at(0)->extent == 16); - REQUIRE(ins.getMessageSubs().at(0)->transfer_id_timeout_usec == 1'000'000); - REQUIRE(ensureAllNullptr(ins.getMessageSubs().at(0)->sessions)); - REQUIRE(ins.getResponseSubs().empty()); - REQUIRE(ins.getRequestSubs().empty()); - - // Create a request subscription. - UdpardRxSubscription sub_req{}; - REQUIRE(1 == ins.rxSubscribe(UdpardTransferKindRequest, 0b0000110011, 20, 3'000'000, sub_req)); - REQUIRE(ins.getMessageSubs().at(0) == &sub_msg); - REQUIRE(ins.getResponseSubs().empty()); - REQUIRE(ins.getRequestSubs().at(0) == &sub_req); - REQUIRE(ins.getRequestSubs().at(0)->port_id == 0b0000110011); - REQUIRE(ins.getRequestSubs().at(0)->extent == 20); - REQUIRE(ins.getRequestSubs().at(0)->transfer_id_timeout_usec == 3'000'000); - REQUIRE(ensureAllNullptr(ins.getRequestSubs().at(0)->sessions)); - - // Create a response subscription. - UdpardRxSubscription sub_res{}; - REQUIRE(1 == ins.rxSubscribe(UdpardTransferKindResponse, 0b0000111100, 10, 100'000, sub_res)); - REQUIRE(ins.getMessageSubs().at(0) == &sub_msg); - REQUIRE(ins.getResponseSubs().at(0) == &sub_res); - REQUIRE(ins.getResponseSubs().at(0)->port_id == 0b0000111100); - REQUIRE(ins.getResponseSubs().at(0)->extent == 10); - REQUIRE(ins.getResponseSubs().at(0)->transfer_id_timeout_usec == 100'000); - REQUIRE(ensureAllNullptr(ins.getResponseSubs().at(0)->sessions)); - REQUIRE(ins.getRequestSubs().at(0) == &sub_req); - - // Create a second response subscription. It will come before the one we added above due to lower port-ID. - UdpardRxSubscription sub_res2{}; - REQUIRE(1 == ins.rxSubscribe(UdpardTransferKindResponse, 0b0000000000, 10, 1'000, sub_res2)); - REQUIRE(ins.getMessageSubs().at(0) == &sub_msg); - REQUIRE(ins.getResponseSubs().at(0) == &sub_res2); - REQUIRE(ins.getResponseSubs().at(0)->port_id == 0b0000000000); - REQUIRE(ins.getResponseSubs().at(0)->extent == 10); - REQUIRE(ins.getResponseSubs().at(0)->transfer_id_timeout_usec == 1'000); - REQUIRE(ins.getResponseSubs().at(1) == &sub_res); // The earlier one. - REQUIRE(ins.getRequestSubs().at(0) == &sub_req); - - // Accepted message. - subscription = nullptr; - header.frame_index_eot = 1U << 31U; - header.priority = 0b001; - header.transfer_id = 0; - header.data_specifier = 0b0000110011001100; // Subject ID = 3276 - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.destination_route_specifier = 0b11101111'00'01000'0'0'000110011001100; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00100111; - // This is an empty payload, the last four bytes are CRC. - REQUIRE(1 == accept(0, 100'000'001, header, specifier, {0, 0, 0, 0})); - REQUIRE(subscription != nullptr); - REQUIRE(subscription->port_id == 0b0110011001100); - REQUIRE(transfer.timestamp_usec == 100'000'001); - REQUIRE(transfer.metadata.priority == UdpardPriorityImmediate); - REQUIRE(transfer.metadata.transfer_kind == UdpardTransferKindMessage); - REQUIRE(transfer.metadata.port_id == 0b0110011001100); - REQUIRE(transfer.metadata.remote_node_id == 0b0100111); - REQUIRE(transfer.metadata.transfer_id == 0); - REQUIRE(transfer.payload_size == 0); // Payload size should not include the CRC (0 byte payload + 4 byte CRC) - REQUIRE(0 == std::memcmp(transfer.payload, "", 0)); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 2); // The SESSION and the PAYLOAD BUFFER. - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == (sizeof(RxSession) + 16)); - REQUIRE(ins.getMessageSubs().at(0)->sessions[0b0100111] != nullptr); - auto* msg_payload = transfer.payload; // Will need it later. - - // Provide the space for an extra session and its payload. - ins.getAllocator().setAllocationCeiling(sizeof(RxSession) * 2 + 16 + 20); - - // Dropped request because the local node does not have a node-ID. - subscription = nullptr; - header.frame_index_eot = 1U << 31U; - header.priority = 0b011; - header.transfer_id = 1; - header.source_node_id = 0b00000000'00100111; - header.destination_node_id = 0b00000000'00011010; - header.data_specifier = 0b1100000000110011; // Service ID = 51 - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.destination_route_specifier = 0b11101111'00'01000'1'00000000'00011010; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00100111; - REQUIRE(0 == accept(0, 100'000'002, header, specifier, {0, 0, 0, 0})); - REQUIRE(subscription == nullptr); - - // Dropped request because the local node has a different node-ID. - ins.setNodeID(0b0011010); - subscription = nullptr; - header.frame_index_eot = 1U << 31U; - header.priority = 0b011; - header.transfer_id = 1; - header.source_node_id = 0b00000000'00100111; - header.destination_node_id = 0b00000000'00011011; - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.destination_route_specifier = 0b11101111'00'01000'1'00000000'00011011; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00100111; - REQUIRE(0 == accept(0, 100'000'002, header, specifier, {0, 0, 0, 0})); - REQUIRE(subscription == nullptr); - - // Same request accepted now. - subscription = nullptr; - header.frame_index_eot = 1U << 31U; - header.priority = 0b011; - header.transfer_id = 4; - header.destination_node_id = 0b00000000'00011010; - header.source_node_id = 0b00000000'00100101; - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.destination_route_specifier = 0b11101111'00'01000'1'00000000'00011010; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00100101; - REQUIRE(1 == accept(0, 100'000'002, header, specifier, {1, 2, 3, 30, 242, 48, 241})); - REQUIRE(subscription != nullptr); - REQUIRE(subscription->port_id == 0b0000110011); - REQUIRE(transfer.timestamp_usec == 100'000'002); - REQUIRE(transfer.metadata.priority == UdpardPriorityHigh); - REQUIRE(transfer.metadata.transfer_kind == UdpardTransferKindRequest); - REQUIRE(transfer.metadata.port_id == 0b0000110011); - REQUIRE(transfer.metadata.remote_node_id == 0b0100101); - REQUIRE(transfer.metadata.transfer_id == 4); - REQUIRE(transfer.payload_size == 3); // Payload size should not include the CRC (3 byte payload + 4 byte CRC) - REQUIRE(0 == std::memcmp(transfer.payload, "\x01\x02\x03\x1E\xF2\x30\xF1", 7)); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 4); // Two SESSIONS and two PAYLOAD BUFFERS. - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == (2 * sizeof(RxSession) + 16 + 20)); - REQUIRE(ins.getRequestSubs().at(0)->sessions[0b0100101] != nullptr); - - // Response transfer not accepted because the local node has a different node-ID. - // There is no dynamic memory available, but it doesn't matter because a rejection does not require allocation. - subscription = nullptr; - header.frame_index_eot = 1U << 31U; - header.priority = 0b100; - header.transfer_id = 1; - header.source_node_id = 0b00000000'00011011; - header.destination_node_id = 0b00000000'00100111; - header.data_specifier = 0b1000000000111100; // Service ID = 60 - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00011011; - specifier.destination_route_specifier = 0b11101111'00'01000'1'00000000'00100111; - REQUIRE(0 == accept(0, 100'000'002, header, specifier, {10, 20, 30, 167, 39, 51, 218})); - REQUIRE(subscription == nullptr); - - // Response transfer not accepted due to OOM -- can't allocate RX session. - subscription = nullptr; - header.frame_index_eot = 1U << 31U; - header.priority = 0b100; - header.transfer_id = 1; - header.source_node_id = 0b00000000'00011011; - header.destination_node_id = 0b00000000'00011010; - header.data_specifier = 0b1000000000111100; - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00011011; - specifier.destination_route_specifier = 0b11101111'00'01000'1'00000000'00011010; - REQUIRE(-UDPARD_ERROR_OUT_OF_MEMORY == accept(0, 100'000'003, header, specifier, {5, 77, 71, 140, 103})); - REQUIRE(subscription != nullptr); // Subscription get assigned before error code - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 4); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == (2 * sizeof(RxSession) + 16 + 20)); - - // Response transfer not accepted due to OOM -- can't allocate the buffer (RX session is allocated OK). - ins.getAllocator().setAllocationCeiling(3 * sizeof(RxSession) + 16 + 20); - subscription = nullptr; - header.frame_index_eot = 1U << 31U; - header.priority = 0b100; - header.transfer_id = 1; - header.source_node_id = 0b00000000'00011011; - header.destination_node_id = 0b00000000'00011010; - header.data_specifier = 0b1000000000111100; - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00011011; - specifier.destination_route_specifier = 0b11101111'00'01000'1'00000000'00011010; - REQUIRE(-UDPARD_ERROR_OUT_OF_MEMORY == accept(0, 100'000'003, header, specifier, {5, 77, 71, 140, 103})); - REQUIRE(subscription != nullptr); // Subscription get assigned before error code - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 5); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == (3 * sizeof(RxSession) + 16 + 20)); - - // Destroy the message subscription and the buffer to free up memory. - REQUIRE(1 == ins.rxUnsubscribe(UdpardTransferKindMessage, 0b0110011001100)); - REQUIRE(0 == ins.rxUnsubscribe(UdpardTransferKindMessage, 0b0110011001100)); // Repeat, nothing to do. - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 4); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == (2 * sizeof(RxSession) + 16 + 20)); - ins.getAllocator().deallocate(msg_payload); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 3); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == (2 * sizeof(RxSession) + 20)); - - // Same response accepted now. We have to keep incrementing the transfer-ID though because it's tracked. - subscription = nullptr; - header.frame_index_eot = 1U << 31U; - header.priority = 0b100; - header.transfer_id = 5; - header.source_node_id = 0b00000000'00011011; - header.destination_node_id = 0b00000000'00011010; - header.data_specifier = 0b1000000000111100; - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00011011; - specifier.destination_route_specifier = 0b11101111'00'01000'1'00000000'00011010; - REQUIRE(1 == accept(0, 100'000'003, header, specifier, {5, 77, 71, 140, 103})); - REQUIRE(subscription != nullptr); - REQUIRE(subscription->port_id == 0b0000111100); - REQUIRE(transfer.timestamp_usec == 100'000'003); - REQUIRE(transfer.metadata.priority == UdpardPriorityNominal); - REQUIRE(transfer.metadata.transfer_kind == UdpardTransferKindResponse); - REQUIRE(transfer.metadata.port_id == 0b0000111100); - REQUIRE(transfer.metadata.remote_node_id == 0b0011011); - REQUIRE(transfer.metadata.transfer_id == 5); - REQUIRE(transfer.payload_size == 1); // Payload size should not include the CRC (1 byte payload + 4 byte CRC) - REQUIRE(0 == std::memcmp(transfer.payload, "\x05\x4D\x47\x8C\x67", 5)); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 4); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == (2 * sizeof(RxSession) + 10 + 20)); - - // Bad frames shall be rejected silently. - /* May need a specicial setup to remove header from frame see private_rx test - subscription = nullptr; - header.frame_index_eot = 0; - header.priority = 0; - header.transfer_id = 0; - specifier.data_specifier = 0; - specifier.source_route_specifier = 0; - specifier.destination_route_specifier = 0; - REQUIRE(0 == accept(0, 900'000'000, header, specifier, {})); - REQUIRE(subscription == nullptr); - */ - - // Unsubscribe. - REQUIRE(1 == ins.rxUnsubscribe(UdpardTransferKindRequest, 0b0000110011)); - REQUIRE(0 == ins.rxUnsubscribe(UdpardTransferKindRequest, 0b0000110011)); - REQUIRE(1 == ins.rxUnsubscribe(UdpardTransferKindResponse, 0b0000111100)); - REQUIRE(0 == ins.rxUnsubscribe(UdpardTransferKindResponse, 0b0000111100)); - REQUIRE(1 == ins.rxUnsubscribe(UdpardTransferKindResponse, 0b0000000000)); - REQUIRE(0 == ins.rxUnsubscribe(UdpardTransferKindResponse, 0b0000000000)); -} - -TEST_CASE("RxAnonymous") -{ - using helpers::Instance; - using exposed::RxSession; - Instance ins; - UdpardRxTransfer transfer{}; - UdpardSessionSpecifier specifier{}; - UdpardFrameHeader header{}; - UdpardRxSubscription* subscription = nullptr; - - const auto accept = [&](const std::uint8_t redundant_transport_index, - const UdpardMicrosecond timestamp_usec, - UdpardFrameHeader frame_header, - UdpardSessionSpecifier session_specifier, - const std::vector& payload) { - auto *header_ptr = reinterpret_cast(&frame_header); - auto payload_storage = std::vector(header_ptr, header_ptr + sizeof(frame_header)); - static std::vector payload_buffer; - payload_buffer = payload; - payload_storage.insert(payload_storage.end(), payload_buffer.begin(), payload_buffer.end()); - UdpardFrame frame{}; - frame.payload_size = std::size(payload) + sizeof(frame_header); - frame.payload = payload_storage.data(); - return ins - .rxAccept(timestamp_usec, frame, redundant_transport_index, session_specifier, transfer, &subscription); - }; - - ins.getAllocator().setAllocationCeiling(16); - - // Some initial header setup - header.version = 1; - - // A valid anonymous transfer for which there is no subscription. - subscription = nullptr; - header.priority = 0b001; - header.source_node_id = 0b1111111111111111; - header.destination_node_id = 0b1111111111111111; - header.data_specifier = 0b0000110011001100; - header.transfer_id = 1; - header.frame_index_eot = 1U << 31U; - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.destination_route_specifier = 0b11101111'00'01000'0'0'000110011001100; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00000000; - // REQUIRE(0 == exposed::txMakeMessageSessionSpecifier(0b0110011001100, 0b0, 0xc0a80000, &specifier)); - REQUIRE(0 == accept(0, 100'000'000, header, specifier, {0, 0, 0, 0})); - REQUIRE(subscription == nullptr); - - // Create a message subscription. - void* const my_user_reference = &ins; - UdpardRxSubscription sub_msg{}; - sub_msg.user_reference = my_user_reference; - REQUIRE(1 == ins.rxSubscribe(UdpardTransferKindMessage, 0b0110011001100, 16, 2'000'000, sub_msg)); // New. - - // Accepted anonymous message. - subscription = nullptr; - header.frame_index_eot = 1U << 31U; - header.priority = 0b001; - header.source_node_id = 0b1111111111111111; - header.destination_node_id = 0b1111111111111111; - header.data_specifier = 0b0000110011001100; - header.transfer_id = 0; - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.destination_route_specifier = 0b11101111'00'01000'0'0'000110011001100; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00000000; - REQUIRE(1 == accept(0, - 100'000'001, - header, - specifier, // - {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 133, 210, 47, 197})); - REQUIRE(subscription != nullptr); - REQUIRE(subscription->port_id == 0b0110011001100); - REQUIRE(subscription->user_reference == my_user_reference); - REQUIRE(transfer.timestamp_usec == 100'000'001); - REQUIRE(transfer.metadata.priority == UdpardPriorityImmediate); - REQUIRE(transfer.metadata.transfer_kind == UdpardTransferKindMessage); - REQUIRE(transfer.metadata.port_id == 0b0110011001100); - REQUIRE(transfer.metadata.remote_node_id == UDPARD_NODE_ID_UNSET); - REQUIRE(transfer.metadata.transfer_id == 0); - REQUIRE(transfer.payload_size == 16); // Truncated. - REQUIRE(0 == std::memcmp(transfer.payload, "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10", 0)); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); // The PAYLOAD BUFFER only! No session for anons. - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 16); - REQUIRE(ensureAllNullptr(ins.getMessageSubs().at(0)->sessions)); // No RX states! - - // Anonymous message not accepted because OOM. The transfer shall remain unmodified by the call, so we re-check it. - header.frame_index_eot = 1U << 31U; - header.priority = 0b001; - header.transfer_id = 1; - specifier.data_specifier = UDPARD_UDP_PORT; - specifier.destination_route_specifier = 0b11101111'00'01000'0'0'000110011001100; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00000000; - // REQUIRE(0 == exposed::txMakeMessageSessionSpecifier(0b0110011001100, 0b0, 0xc0a80000, &specifier)); - REQUIRE(-UDPARD_ERROR_OUT_OF_MEMORY == accept(0, 100'000'001, header, specifier, {3, 2, 1, 228, 208, 100, 95})); - REQUIRE(subscription != nullptr); - REQUIRE(subscription->port_id == 0b0110011001100); - REQUIRE(transfer.timestamp_usec == 100'000'001); - REQUIRE(transfer.metadata.priority == UdpardPriorityImmediate); - REQUIRE(transfer.metadata.transfer_kind == UdpardTransferKindMessage); - REQUIRE(transfer.metadata.port_id == 0b0110011001100); - REQUIRE(transfer.metadata.remote_node_id == UDPARD_NODE_ID_UNSET); - REQUIRE(transfer.metadata.transfer_id == 0); - REQUIRE(transfer.payload_size == 16); // Truncated. - REQUIRE(0 == std::memcmp(transfer.payload, "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10", 0)); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); // The PAYLOAD BUFFER only! No session for anons. - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 16); - REQUIRE(ensureAllNullptr(ins.getMessageSubs().at(0)->sessions)); // No RX states! - - // Release the memory. - ins.getAllocator().deallocate(transfer.payload); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 0); - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 0); - - // Accepted anonymous message with small payload. - subscription = nullptr; - header.frame_index_eot = 1U << 31U; - header.priority = 0b001; - header.transfer_id = 0; - specifier.data_specifier = UDPARD_SUBJECT_ID_PORT; - specifier.destination_route_specifier = 0b11101111'00'01000'0'0'000110011001100; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00000000; - REQUIRE(1 == accept(0, 100'000'001, header, specifier, {1, 2, 3, 4, 5, 6, 171, 251, 77, 79})); - REQUIRE(subscription != nullptr); - REQUIRE(subscription->port_id == 0b0110011001100); - REQUIRE(transfer.timestamp_usec == 100'000'001); - REQUIRE(transfer.metadata.priority == UdpardPriorityImmediate); - REQUIRE(transfer.metadata.transfer_kind == UdpardTransferKindMessage); - REQUIRE(transfer.metadata.port_id == 0b0110011001100); - REQUIRE(transfer.metadata.remote_node_id == UDPARD_NODE_ID_UNSET); - REQUIRE(transfer.metadata.transfer_id == 0); - REQUIRE(transfer.payload_size == 10); // NOT truncated. - REQUIRE(0 == std::memcmp(transfer.payload, "\x01\x02\x03\x04\x05\x06\xAB\xFB\x4D\x4F", 0)); - REQUIRE(ins.getAllocator().getNumAllocatedFragments() == 1); // The PAYLOAD BUFFER only! No session for anons. - REQUIRE(ins.getAllocator().getTotalAllocatedAmount() == 10); // Smaller allocation. - REQUIRE(ensureAllNullptr(ins.getMessageSubs().at(0)->sessions)); // No RX states! - - // Version mismatch will be ignored - header.version = 0; - specifier.destination_route_specifier = 0b11101111'00'01000'0'0'000110011001100; - specifier.source_route_specifier = 0b11000000'10101000'00000000'00000000; - REQUIRE(0 == accept(0, 100'000'001, header, specifier, {1, 2, 3, 4, 5, 6, 171, 251, 77, 79})); -} - -TEST_CASE("RxSubscriptionErrors") -{ - using helpers::Instance; - Instance ins; - UdpardRxSubscription sub{}; - - const union - { - std::uint64_t bits; - UdpardTransferKind value; - } kind{std::numeric_limits::max()}; - - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardRxSubscribe(nullptr, UdpardTransferKindMessage, 0, 0, 0, &sub)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardRxSubscribe(&ins.getInstance(), kind.value, 0, 0, 0, &sub)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == - udpardRxSubscribe(&ins.getInstance(), UdpardTransferKindMessage, 0, 0, 0, nullptr)); - - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardRxUnsubscribe(nullptr, UdpardTransferKindMessage, 0)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardRxUnsubscribe(&ins.getInstance(), kind.value, 0)); - - UdpardFrame frame{}; - frame.payload_size = 1U; - UdpardRxTransfer transfer{}; - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == - udpardRxAccept(&ins.getInstance(), 0, &frame, 0, &transfer, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardRxAccept(nullptr, 0, &frame, 0, &transfer, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == - udpardRxAccept(&ins.getInstance(), 0, nullptr, 0, &transfer, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == - udpardRxAccept(&ins.getInstance(), 0, &frame, 0, nullptr, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardRxAccept(nullptr, 0, nullptr, 0, nullptr, nullptr)); -} diff --git a/tests/test_public_tx.cpp b/tests/test_public_tx.cpp deleted file mode 100644 index c7d7a80..0000000 --- a/tests/test_public_tx.cpp +++ /dev/null @@ -1,430 +0,0 @@ -// This software is distributed under the terms of the MIT License. -// Copyright (c) 2016 OpenCyphal Development Team. -/// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -#include "exposed.hpp" -#include "helpers.hpp" -#include "catch/catch.hpp" -#include - -TEST_CASE("TxBasic0") -{ - using exposed::TxItem; - - helpers::Instance ins; - helpers::TxQueue que(200, UDPARD_MTU_UDP_IPV4); - - auto& alloc = ins.getAllocator(); - - std::array payload{}; - for (std::size_t i = 0; i < std::size(payload); i++) - { - payload.at(i) = static_cast(i & 0xFFU); - } - - REQUIRE(UDPARD_NODE_ID_UNSET == ins.getNodeID()); - ins.setNodeAddr(0xc0a80000); - REQUIRE(0xc0a80000 == ins.getNodeAddr()); - REQUIRE(UDPARD_MTU_UDP_IPV4 == que.getMTU()); - REQUIRE(0 == que.getSize()); - REQUIRE(0 == alloc.getNumAllocatedFragments()); - - alloc.setAllocationCeiling(4000); - - UdpardTransferMetadata meta{}; - - // Single-frame with crc. - meta.priority = UdpardPriorityNominal; - meta.transfer_kind = UdpardTransferKindMessage; - meta.port_id = 321; - meta.remote_node_id = UDPARD_NODE_ID_UNSET; - meta.transfer_id = 21; - REQUIRE(1 == que.push(&ins.getInstance(), 1'000'000'000'000ULL, meta, 8, payload.data())); - REQUIRE(1 == que.getSize()); - REQUIRE(1 == alloc.getNumAllocatedFragments()); - REQUIRE(10 < alloc.getTotalAllocatedAmount()); - REQUIRE(160 > alloc.getTotalAllocatedAmount()); - REQUIRE(que.peek()->tx_deadline_usec == 1'000'000'000'000ULL); - REQUIRE(que.peek()->frame.payload_size == 36); // 8 + 24 header + 4 CRC - REQUIRE(que.peek()->getPayloadByte(0) == 0); // Payload start. (starts after header) - REQUIRE(que.peek()->getPayloadByte(1) == 1); - REQUIRE(que.peek()->getPayloadByte(2) == 2); - REQUIRE(que.peek()->getPayloadByte(3) == 3); - REQUIRE(que.peek()->getPayloadByte(4) == 4); - REQUIRE(que.peek()->getPayloadByte(5) == 5); - REQUIRE(que.peek()->getPayloadByte(6) == 6); - REQUIRE(que.peek()->getPayloadByte(7) == 7); // Payload end. - REQUIRE(que.peek()->isStartOfTransfer()); // Tail byte at the end. - REQUIRE(que.peek()->isEndOfTransfer()); - - meta.priority = UdpardPriorityLow; - meta.transfer_id = 22; - ins.setNodeID(42); - REQUIRE(1 == que.push(&ins.getInstance(), 1'000'000'000'100ULL, meta, 8, payload.data())); // 8 bytes --> 2 frames - REQUIRE(2 == que.getSize()); - REQUIRE(2 == alloc.getNumAllocatedFragments()); - REQUIRE(20 < alloc.getTotalAllocatedAmount()); - REQUIRE(400 > alloc.getTotalAllocatedAmount()); - - // Check the TX queue. - { - const auto q = que.linearize(); - REQUIRE(2 == q.size()); - REQUIRE(q.at(0)->tx_deadline_usec == 1'000'000'000'000ULL); - REQUIRE(q.at(0)->frame.payload_size == 36); - REQUIRE(q.at(0)->isStartOfTransfer()); - REQUIRE(q.at(0)->isEndOfTransfer()); - // - REQUIRE(q.at(1)->tx_deadline_usec == 1'000'000'000'100ULL); - REQUIRE(q.at(1)->frame.payload_size == 36); - REQUIRE(q.at(1)->isStartOfTransfer()); - REQUIRE(q.at(1)->isEndOfTransfer()); - } - - // Single-frame, OOM. - alloc.setAllocationCeiling(alloc.getTotalAllocatedAmount()); // Seal up the heap at this level. - meta.priority = UdpardPriorityLow; - meta.transfer_id = 23; - REQUIRE(-UDPARD_ERROR_OUT_OF_MEMORY == que.push(&ins.getInstance(), 1'000'000'000'200ULL, meta, 1, payload.data())); - REQUIRE(2 == que.getSize()); - REQUIRE(2 == alloc.getNumAllocatedFragments()); - - alloc.setAllocationCeiling(alloc.getTotalAllocatedAmount() + sizeof(TxItem) + 10U); - meta.priority = UdpardPriorityHigh; - meta.transfer_id = 24; - REQUIRE(-UDPARD_ERROR_OUT_OF_MEMORY == - que.push(&ins.getInstance(), 1'000'000'000'300ULL, meta, 100, payload.data())); - REQUIRE(2 == que.getSize()); - REQUIRE(2 == alloc.getNumAllocatedFragments()); - REQUIRE(20 < alloc.getTotalAllocatedAmount()); - REQUIRE(400 > alloc.getTotalAllocatedAmount()); - - // Pop the queue. - const UdpardTxQueueItem* ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 36); - REQUIRE(0 == std::memcmp(reinterpret_cast(ti->frame.payload) + 24, payload.data(), 8)); - REQUIRE(ti->tx_deadline_usec == 1'000'000'000'000ULL); - ti = que.peek(); - REQUIRE(nullptr != ti); // Make sure we get the same frame again. - REQUIRE(ti->frame.payload_size == 36); - REQUIRE(0 == std::memcmp(reinterpret_cast(ti->frame.payload) + 24, payload.data(), 8)); - REQUIRE(ti->tx_deadline_usec == 1'000'000'000'000ULL); - ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(1 == que.getSize()); - REQUIRE(1 == alloc.getNumAllocatedFragments()); - ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 36); - REQUIRE(ti->tx_deadline_usec == 1'000'000'000'100ULL); - ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(0 == que.getSize()); - REQUIRE(0 == alloc.getNumAllocatedFragments()); - ti = que.peek(); - REQUIRE(nullptr == ti); - REQUIRE(nullptr == que.pop(nullptr)); - REQUIRE(0 == que.getSize()); - REQUIRE(0 == alloc.getNumAllocatedFragments()); - ti = que.peek(); - REQUIRE(nullptr == ti); - - alloc.setAllocationCeiling(1000); - // Single-frame empty. - meta.transfer_id = 28; - REQUIRE(1 == que.push(&ins.getInstance(), 1'000'000'004'000ULL, meta, 0, nullptr)); - REQUIRE(1 == que.getSize()); - REQUIRE(1 == alloc.getNumAllocatedFragments()); - REQUIRE(140 > alloc.getTotalAllocatedAmount()); - REQUIRE(que.peek()->tx_deadline_usec == 1'000'000'004'000ULL); - REQUIRE(que.peek()->frame.payload_size == 28); - REQUIRE(que.peek()->isStartOfTransfer()); - REQUIRE(que.peek()->isEndOfTransfer()); - ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 28); - REQUIRE(ti->tx_deadline_usec == 1'000'000'004'000ULL); - ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(0 == que.getSize()); - REQUIRE(0 == alloc.getNumAllocatedFragments()); - - // Nothing left to peek at. - ti = que.peek(); - REQUIRE(nullptr == ti); - - // Invalid transfer. - meta.transfer_kind = UdpardTransferKindMessage; - meta.remote_node_id = 42; - meta.transfer_id = 123; - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == - que.push(&ins.getInstance(), 1'000'000'005'000ULL, meta, 8, payload.data())); - ti = que.peek(); - REQUIRE(nullptr == ti); - // Error handling. - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardTxPush(nullptr, nullptr, 0, nullptr, 0, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardTxPush(nullptr, nullptr, 0, &meta, 0, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardTxPush(nullptr, &ins.getInstance(), 0, &meta, 0, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == - udpardTxPush(&que.getInstance(), &ins.getInstance(), 0, nullptr, 0, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == que.push(&ins.getInstance(), 1'000'000'006'000ULL, meta, 1, nullptr)); - - REQUIRE(nullptr == udpardTxPeek(nullptr)); - REQUIRE(nullptr == udpardTxPop(nullptr, nullptr)); // No effect. - REQUIRE(nullptr == udpardTxPop(&que.getInstance(), nullptr)); // No effect. -} - -TEST_CASE("TxBasic1") -{ - using exposed::TxItem; - using exposed::crcValue; - - helpers::Instance ins; - helpers::TxQueue que(4, UDPARD_MTU_UDP_IPV4); - - auto& alloc = ins.getAllocator(); - - std::array payload{}; - for (std::size_t i = 0; i < std::size(payload); i++) - { - payload.at(i) = static_cast(i & 0xFFU); - } - - REQUIRE(UDPARD_NODE_ID_UNSET == ins.getNodeID()); - ins.setNodeAddr(0xc0a80000); - REQUIRE(0xc0a80000 == ins.getNodeAddr()); - REQUIRE(UDPARD_MTU_UDP_IPV4 == que.getMTU()); - REQUIRE(0 == que.getSize()); - REQUIRE(0 == alloc.getNumAllocatedFragments()); - - alloc.setAllocationCeiling(4000); - - UdpardTransferMetadata meta{}; - - // Single-frame with padding. - meta.priority = UdpardPriorityNominal; - meta.transfer_kind = UdpardTransferKindMessage; - meta.port_id = 321; - meta.remote_node_id = UDPARD_NODE_ID_UNSET; - meta.transfer_id = 21; - REQUIRE(1 == que.push(&ins.getInstance(), 1'000'000'000'000ULL, meta, 8, payload.data())); - REQUIRE(1 == que.getSize()); - REQUIRE(1 == alloc.getNumAllocatedFragments()); - REQUIRE(10 < alloc.getTotalAllocatedAmount()); - REQUIRE(160 > alloc.getTotalAllocatedAmount()); - REQUIRE(que.peek()->tx_deadline_usec == 1'000'000'000'000ULL); - REQUIRE(que.peek()->frame.payload_size == 36); // 8 + 24 header + 4 CRC - REQUIRE(que.peek()->getPayloadByte(0) == 0); // Payload start. (starts after header) - REQUIRE(que.peek()->getPayloadByte(1) == 1); - REQUIRE(que.peek()->getPayloadByte(2) == 2); - REQUIRE(que.peek()->getPayloadByte(3) == 3); - REQUIRE(que.peek()->getPayloadByte(4) == 4); - REQUIRE(que.peek()->getPayloadByte(5) == 5); - REQUIRE(que.peek()->getPayloadByte(6) == 6); - REQUIRE(que.peek()->getPayloadByte(7) == 7); // Payload end. - REQUIRE(que.peek()->isStartOfTransfer()); // Tail byte at the end. - REQUIRE(que.peek()->isEndOfTransfer()); - - // Multi-frame transfer - meta.priority = UdpardPriorityLow; - meta.transfer_id = 22; - que.setMTU(64U); - ins.setNodeID(42); - REQUIRE(64U == que.getMTU()); - REQUIRE(2 == que.push(&ins.getInstance(), 1'000'000'000'100ULL, meta, 68, payload.data())); // 68 bytes --> 2 frames - REQUIRE(3 == que.getSize()); - REQUIRE(3 == alloc.getNumAllocatedFragments()); - REQUIRE(20 < alloc.getTotalAllocatedAmount()); - REQUIRE(600 > alloc.getTotalAllocatedAmount()); - - // Check the TX queue. - { - const auto q = que.linearize(); - REQUIRE(3 == q.size()); - REQUIRE(q.at(0)->tx_deadline_usec == 1'000'000'000'000ULL); - REQUIRE(q.at(0)->frame.payload_size == 36); - REQUIRE(q.at(0)->isStartOfTransfer()); - REQUIRE(q.at(0)->isEndOfTransfer()); - - REQUIRE(q.at(1)->tx_deadline_usec == 1'000'000'000'100ULL); - REQUIRE(q.at(1)->frame.payload_size == 64); //mtu - REQUIRE(q.at(1)->isStartOfTransfer()); - REQUIRE(!q.at(1)->isEndOfTransfer()); - - REQUIRE(q.at(2)->tx_deadline_usec == 1'000'000'000'100ULL); - REQUIRE(q.at(2)->frame.payload_size == 56); // mtu(= 28 data + 24 header + 4 crc) - REQUIRE(!q.at(2)->isStartOfTransfer()); - REQUIRE(q.at(2)->isEndOfTransfer()); - } - - // Single-frame, OOM. - alloc.setAllocationCeiling(alloc.getTotalAllocatedAmount()); // Seal up the heap at this level. - meta.priority = UdpardPriorityLow; - meta.transfer_id = 23; -REQUIRE(-UDPARD_ERROR_OUT_OF_MEMORY == que.push(&ins.getInstance(), 1'000'000'000'200ULL, meta, 1, payload.data())); - REQUIRE(3 == que.getSize()); - REQUIRE(3 == alloc.getNumAllocatedFragments()); - - alloc.setAllocationCeiling(alloc.getTotalAllocatedAmount() + sizeof(TxItem) + 10U); - meta.priority = UdpardPriorityHigh; - meta.transfer_id = 24; - REQUIRE(-UDPARD_ERROR_OUT_OF_MEMORY == - que.push(&ins.getInstance(), 1'000'000'000'300ULL, meta, 100, payload.data())); - REQUIRE(3 == que.getSize()); - REQUIRE(3 == alloc.getNumAllocatedFragments()); - REQUIRE(20 < alloc.getTotalAllocatedAmount()); - REQUIRE(1400 > alloc.getTotalAllocatedAmount()); - - // Pop the queue. - const UdpardTxQueueItem* ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 36); - REQUIRE(0 == std::memcmp(reinterpret_cast(ti->frame.payload) + 24, payload.data(), 8)); - REQUIRE(ti->tx_deadline_usec == 1'000'000'000'000ULL); - - ti = que.peek(); // Make sure we get the same frame again. - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 36); - REQUIRE(0 == std::memcmp(reinterpret_cast(ti->frame.payload) + 24, payload.data(), 8)); - REQUIRE(ti->tx_deadline_usec == 1'000'000'000'000ULL); - ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(2 == que.getSize()); - REQUIRE(2 == alloc.getNumAllocatedFragments()); - - ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 64); - REQUIRE(0 == std::memcmp(reinterpret_cast(ti->frame.payload) + 24, payload.data(), 40)); // 64 - 24 - REQUIRE(ti->tx_deadline_usec == 1'000'000'000'100ULL); - ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(1 == que.getSize()); - REQUIRE(1 == alloc.getNumAllocatedFragments()); - ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 56); - REQUIRE(ti->tx_deadline_usec == 1'000'000'000'100ULL); - ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(0 == que.getSize()); - REQUIRE(0 == alloc.getNumAllocatedFragments()); - REQUIRE(nullptr == que.pop(nullptr)); - REQUIRE(0 == que.getSize()); - REQUIRE(0 == alloc.getNumAllocatedFragments()); - ti = que.peek(); - REQUIRE(nullptr == ti); - - alloc.setAllocationCeiling(1000); - // Single-frame empty. - meta.transfer_id = 28; - REQUIRE(1 == que.push(&ins.getInstance(), 1'000'000'004'000ULL, meta, 0, nullptr)); - REQUIRE(1 == que.getSize()); - REQUIRE(1 == alloc.getNumAllocatedFragments()); - REQUIRE(140 > alloc.getTotalAllocatedAmount()); - REQUIRE(que.peek()->tx_deadline_usec == 1'000'000'004'000ULL); - REQUIRE(que.peek()->frame.payload_size == 28); - REQUIRE(que.peek()->isStartOfTransfer()); - REQUIRE(que.peek()->isEndOfTransfer()); - ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 28); - REQUIRE(ti->tx_deadline_usec == 1'000'000'004'000ULL); - ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(0 == que.getSize()); - REQUIRE(0 == alloc.getNumAllocatedFragments()); - - // Multi-frame, success, CRC in last frame - // crc = pycyphal.transport.commons.crc.CRC32C.new(list(range(68))).value - // (hex)crc = OxDBC9DD7B - constexpr std::uint32_t CRC68 = 0xDBC9DD7BU; - meta.priority = UdpardPriorityLow; - meta.transfer_id = 25; - REQUIRE(2 == que.push(&ins.getInstance(), 1'000'000'001'000ULL, meta, 40 + 28, payload.data())); - REQUIRE(2 == que.getSize()); - REQUIRE(2 == alloc.getNumAllocatedFragments()); - REQUIRE(40 < alloc.getTotalAllocatedAmount()); - REQUIRE(500 > alloc.getTotalAllocatedAmount()); - // Read the generated frame - ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 64); - REQUIRE(0 == std::memcmp(reinterpret_cast(ti->frame.payload) + 24, payload.data(), 40)); - ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(1 == que.getSize()); - REQUIRE(1 == alloc.getNumAllocatedFragments()); - ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 56); - REQUIRE(0 == std::memcmp(reinterpret_cast(ti->frame.payload) + 24, payload.data() + 40U, 24)); - // last 4 bytes are crc - REQUIRE(((uint8_t)(CRC68 & 0xFFU)) == reinterpret_cast(ti->frame.payload)[52]); - REQUIRE(((uint8_t)(CRC68 >> 8U)) == reinterpret_cast(ti->frame.payload)[53]); - REQUIRE(((uint8_t)(CRC68 >> 16U)) == reinterpret_cast(ti->frame.payload)[54]); - REQUIRE(((uint8_t)(CRC68 >> 24U)) == reinterpret_cast(ti->frame.payload)[55]); - ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(0 == que.getSize()); - REQUIRE(0 == alloc.getNumAllocatedFragments()); - // Nothing left to peek at. - ti = que.peek(); - REQUIRE(nullptr == ti); - - - // Multi-frame, success, - // part of the CRC in one frame, rest of it in next frame. - // crc = pycyphal.transport.commons.crc.CRC32C.new(list(range(79))).value - // (hex)crc = OxADBA3FD - constexpr std::uint32_t CRC79 = 0xADBA3FDU; - meta.priority = UdpardPriorityLow; - meta.transfer_id = 26; - REQUIRE(3 == que.push(&ins.getInstance(), 1'000'000'001'000ULL, meta, 79, payload.data())); - REQUIRE(3 == que.getSize()); - REQUIRE(3 == alloc.getNumAllocatedFragments()); - REQUIRE(40 < alloc.getTotalAllocatedAmount()); - REQUIRE(500 > alloc.getTotalAllocatedAmount()); - // Read the generated frame - ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 64); - REQUIRE(0 == std::memcmp(reinterpret_cast(ti->frame.payload) + 24, payload.data(), 40)); - ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(2 == que.getSize()); - REQUIRE(2 == alloc.getNumAllocatedFragments()); - ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 64); - REQUIRE(0 == std::memcmp(reinterpret_cast(ti->frame.payload) + 24, payload.data() + 40U, 39)); - // CRC first byte check - REQUIRE(((uint8_t)(CRC79 & 0xFFU)) == reinterpret_cast(ti->frame.payload)[63]); - ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(1 == que.getSize()); - REQUIRE(1 == alloc.getNumAllocatedFragments()); - ti = que.peek(); - REQUIRE(nullptr != ti); - REQUIRE(ti->frame.payload_size == 27); // 24 header + 3 crc - // rest of the CRC check - REQUIRE(((uint8_t)(CRC79 >> 8U)) == reinterpret_cast(ti->frame.payload)[24]); - REQUIRE(((uint8_t)(CRC79 >> 16U)) == reinterpret_cast(ti->frame.payload)[25]); - REQUIRE(((uint8_t)(CRC79 >> 24U)) == reinterpret_cast(ti->frame.payload)[26]); -ins.getAllocator().deallocate(que.pop(ti)); - REQUIRE(0 == que.getSize()); - REQUIRE(0 == alloc.getNumAllocatedFragments()); - // Nothing left to peek at. - ti = que.peek(); - REQUIRE(nullptr == ti); - - // Invalid transfer. - meta.transfer_kind = UdpardTransferKindMessage; - meta.remote_node_id = 42; - meta.transfer_id = 123; - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == - que.push(&ins.getInstance(), 1'000'000'005'000ULL, meta, 8, payload.data())); - ti = que.peek(); - REQUIRE(nullptr == ti); - - // Error handling. - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardTxPush(nullptr, nullptr, 0, nullptr, 0, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardTxPush(nullptr, nullptr, 0, &meta, 0, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == udpardTxPush(nullptr, &ins.getInstance(), 0, &meta, 0, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == - udpardTxPush(&que.getInstance(), &ins.getInstance(), 0, nullptr, 0, nullptr)); - REQUIRE(-UDPARD_ERROR_INVALID_ARGUMENT == que.push(&ins.getInstance(), 1'000'000'006'000ULL, meta, 1, nullptr)); - REQUIRE(nullptr == udpardTxPeek(nullptr)); - REQUIRE(nullptr == udpardTxPop(nullptr, nullptr)); // No effect. - REQUIRE(nullptr == udpardTxPop(&que.getInstance(), nullptr)); // No effect. -} diff --git a/tests/test_self.cpp b/tests/test_self.cpp index 88bf5f5..b138b0a 100644 --- a/tests/test_self.cpp +++ b/tests/test_self.cpp @@ -1,52 +1,57 @@ // This software is distributed under the terms of the MIT License. // Copyright (c) 2016-2020 OpenCyphal Development Team. -#include "exposed.hpp" #include "helpers.hpp" -#include "catch/catch.hpp" +#include -TEST_CASE("TestAllocator") +TEST(TestAllocator, Basic) { helpers::TestAllocator al; - REQUIRE(0 == al.getNumAllocatedFragments()); - REQUIRE(std::numeric_limits::max() == al.getAllocationCeiling()); + ASSERT_EQ(0, al.getNumAllocatedFragments()); + ASSERT_EQ(std::numeric_limits::max(), al.getAllocationCeiling()); auto* a = al.allocate(123); - REQUIRE(1 == al.getNumAllocatedFragments()); - REQUIRE(123 == al.getTotalAllocatedAmount()); + ASSERT_EQ(1, al.getNumAllocatedFragments()); + ASSERT_EQ(123, al.getTotalAllocatedAmount()); auto* b = al.allocate(456); - REQUIRE(2 == al.getNumAllocatedFragments()); - REQUIRE(579 == al.getTotalAllocatedAmount()); + ASSERT_EQ(2, al.getNumAllocatedFragments()); + ASSERT_EQ(579, al.getTotalAllocatedAmount()); al.setAllocationCeiling(600); - REQUIRE(nullptr == al.allocate(100)); - REQUIRE(2 == al.getNumAllocatedFragments()); - REQUIRE(579 == al.getTotalAllocatedAmount()); + ASSERT_EQ(nullptr, al.allocate(100)); + ASSERT_EQ(2, al.getNumAllocatedFragments()); + ASSERT_EQ(579, al.getTotalAllocatedAmount()); auto* c = al.allocate(21); - REQUIRE(3 == al.getNumAllocatedFragments()); - REQUIRE(600 == al.getTotalAllocatedAmount()); + ASSERT_EQ(3, al.getNumAllocatedFragments()); + ASSERT_EQ(600, al.getTotalAllocatedAmount()); - al.deallocate(a); - REQUIRE(2 == al.getNumAllocatedFragments()); - REQUIRE(477 == al.getTotalAllocatedAmount()); + al.free(123, a); + ASSERT_EQ(2, al.getNumAllocatedFragments()); + ASSERT_EQ(477, al.getTotalAllocatedAmount()); auto* d = al.allocate(100); - REQUIRE(3 == al.getNumAllocatedFragments()); - REQUIRE(577 == al.getTotalAllocatedAmount()); + ASSERT_EQ(3, al.getNumAllocatedFragments()); + ASSERT_EQ(577, al.getTotalAllocatedAmount()); - al.deallocate(c); - REQUIRE(2 == al.getNumAllocatedFragments()); - REQUIRE(556 == al.getTotalAllocatedAmount()); + al.free(21, c); + ASSERT_EQ(2, al.getNumAllocatedFragments()); + ASSERT_EQ(556, al.getTotalAllocatedAmount()); - al.deallocate(d); - REQUIRE(1 == al.getNumAllocatedFragments()); - REQUIRE(456 == al.getTotalAllocatedAmount()); + al.free(100, d); + ASSERT_EQ(1, al.getNumAllocatedFragments()); + ASSERT_EQ(456, al.getTotalAllocatedAmount()); - al.deallocate(b); - REQUIRE(0 == al.getNumAllocatedFragments()); - REQUIRE(0 == al.getTotalAllocatedAmount()); + al.free(456, b); + ASSERT_EQ(0, al.getNumAllocatedFragments()); + ASSERT_EQ(0, al.getTotalAllocatedAmount()); +} + +TEST(Dummy, Dummy) +{ + std::cerr << "Please remove this test\n"; + udpardTxPeek(nullptr); } diff --git a/tools/Dockerfile b/tools/Dockerfile deleted file mode 100644 index c82b66e..0000000 --- a/tools/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -# Development environment for libudpard, based on Ubuntu 20.04 Focal. -# -# This software is distributed under the terms of the MIT License. -# Copyright (c) 2021-2022 OpenCyphal Development Team. -# Author: Kalyan Sriram - -FROM ubuntu:focal - -ENV DEBIAN_FRONTEND noninteractive - -RUN apt-get update && apt-get -y upgrade -RUN apt-get -y --no-install-recommends install \ - build-essential cmake gcc-multilib g++-multilib \ - clang-tidy-12 clang-format-12 \ - gcc-avr avr-libc \ - sudo curl git ca-certificates - -RUN update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-12 10 -RUN update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-12 10 - -# borrowed from MAVSDK https://github.com/mavlink/MAVSDK/blob/main/docker/Dockerfile-Ubuntu-20.04 -RUN curl -L https://github.com/ncopa/su-exec/archive/dddd1567b7c76365e1e0aac561287975020a8fad.tar.gz | tar xvz && \ - cd su-exec-* && make && mv su-exec /usr/local/bin && cd .. && rm -rf su-exec-* - -RUN useradd --shell /bin/bash -u 1001 -c "" -m user - -COPY entrypoint.sh /usr/local/bin/entrypoint.sh - -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] - -WORKDIR "/home/user/libudpard" diff --git a/tools/entrypoint.sh b/tools/entrypoint.sh deleted file mode 100755 index ab25495..0000000 --- a/tools/entrypoint.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -# Utility to use local user, taken from: -# https://github.com/mavlink/MAVSDK/blob/main/docker/entrypoint.sh - -# Use LOCAL_USER_ID if passed in at runtime. - -if [ -n "${LOCAL_USER_ID}" ]; then - echo "Starting with UID: $LOCAL_USER_ID" - usermod -u $LOCAL_USER_ID user - export HOME=/home/user - chown -R user:user $HOME - - exec su-exec user "$@" -else - exec "$@" -fi diff --git a/tools/run-docker.sh b/tools/run-docker.sh deleted file mode 100755 index 8e86c81..0000000 --- a/tools/run-docker.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env sh -# First-time usage: docker build . && docker tag libudpard && ./run-docker.sh - -dockerimage=libudpard - -docker run -it --rm -v $(pwd):/home/user/libudpard:z -e LOCAL_USER_ID=`id -u` $dockerimage "$@" diff --git a/tools/run_sonar.sh b/tools/run_sonar.sh new file mode 100755 index 0000000..3645a63 --- /dev/null +++ b/tools/run_sonar.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env sh +# This helper script is mostly invoked from a CI workflow, +# but it can also be used to submit SonarCloud analysis from a developer's machine directly, +# which is useful during large refactorings. +# +# Usage: install the SonarCloud build wrapper and scanner as explained in the official docs +# (or simply follow the steps defined in the CI workflow that does the same), then run this script. + +set -u + +die() +{ + echo "$@" 1>&2 + exit 1 +} + +ensure_executable() { command -v $1 || die "Executable not found: $1" ; } + +# Check preconditions early. +[ -z "$SONAR_TOKEN" ] && die "SonarCloud token is not set" +ensure_executable build-wrapper-linux-x86-64 +ensure_executable sonar-scanner +ensure_executable llvm-profdata +ensure_executable llvm-cov + +cd "${0%/*}/.." && [ -f libudpard/udpard.c ] && [ -f LICENSE ] || die "Could not cd to the project root" +echo "Working directory: $(pwd)" + +# Set up a clean build directory. This is necessary for the analysis to be correct and complete. +BUILD_DIR=sonar_build +rm -rf $BUILD_DIR >/dev/null 2>&1 +mkdir $BUILD_DIR && cd $BUILD_DIR || die + +# Build everything and run the test suite; merge the coverage data. +# Clang produces better coverage reports than GCC for heavily templated code. +# It is also supposed to work well with optimizations. +# RTFM: https://clang.llvm.org/docs/UsersManual.html#profiling-with-instrumentation +# https://clang.llvm.org/docs/SourceBasedCodeCoverage.html +profile_flags="-fprofile-instr-generate='%p.profraw' -fcoverage-mapping" +cmake ../tests \ +-DNO_STATIC_ANALYSIS=1 \ +-DCMAKE_BUILD_TYPE=Debug \ +-DCMAKE_C_COMPILER=clang \ +-DCMAKE_CXX_COMPILER=clang++ \ +-DCMAKE_C_FLAGS="$profile_flags" \ +-DCMAKE_CXX_FLAGS="$profile_flags" || die "CMake failed" +build-wrapper-linux-x86-64 --out-dir . make VERBOSE=1 -j"$(nproc)" || die "Build wrapper failed" +make test ARGS="--verbose" || die "Test execution failed" +# These tools shall be of the same version as LLVM/Clang. +llvm-profdata merge -sparse *.profraw -o profdata || die + +# Generate coverage reports both for the SonarCloud scanner and for us humans. +llvm_cov_objects="" +for file in test_*_*; do llvm_cov_objects="$llvm_cov_objects -object $file"; done +echo "llvm-cov objects: $llvm_cov_objects" +llvm-cov report $llvm_cov_objects -instr-profile=profdata || die +llvm-cov show $llvm_cov_objects -instr-profile=profdata -format=text > "coverage.txt" || die +llvm-cov show $llvm_cov_objects -instr-profile=profdata -format=html > "coverage.html" || die + +# Run SonarScanner from the project root. +cd .. +[ -d $BUILD_DIR ] || die "Unexpected directory structure" +# Please keep the entire sonar configuration only here to maintain encapsulation and simplicity. +# Related: https://community.sonarsource.com/t/analyzing-a-header-only-c-library/51468 +sonar-scanner \ +--define sonar.host.url="https://sonarcloud.io" \ +--define sonar.projectName=libudpard \ +--define sonar.organization=opencyphal-garage \ +--define sonar.projectKey=libudpard \ +--define sonar.sources=libudpard \ +--define sonar.exclusions=libudpard/_udpard_cavl.h \ +--define sonar.cfamily.build-wrapper-output="$BUILD_DIR" \ +--define sonar.cfamily.llvm-cov.reportPath="$BUILD_DIR/coverage.txt" \ +--define sonar.cfamily.threads="$(nproc)" \ +|| die