diff --git a/.clang-tidy b/.clang-tidy deleted file mode 100644 index d35b8333..00000000 --- a/.clang-tidy +++ /dev/null @@ -1,47 +0,0 @@ -# clang-tidy configuration for WoWee -# Targets C++20. Checks are tuned for a Vulkan/game-engine codebase: -# - reinterpret_cast, pointer arithmetic, and magic numbers are frequent -# in low-level graphics/network code, so the most aggressive -# cppcoreguidelines and readability-magic-numbers checks are disabled. ---- -Checks: > - bugprone-*, - clang-analyzer-*, - performance-*, - modernize-use-nullptr, - modernize-use-override, - modernize-use-default-member-init, - modernize-use-emplace, - modernize-loop-convert, - modernize-deprecated-headers, - modernize-make-unique, - modernize-make-shared, - modernize-use-nodiscard, - modernize-use-designated-initializers, - readability-braces-around-statements, - readability-container-size-empty, - readability-delete-null-pointer, - readability-else-after-return, - readability-misplaced-array-index, - readability-non-const-parameter, - readability-redundant-control-flow, - readability-redundant-declaration, - readability-simplify-boolean-expr, - readability-string-compare, - -bugprone-easily-swappable-parameters, - -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, - -performance-avoid-endl - -WarningsAsErrors: '' - -# Suppress the noise from GCC-only LTO flags in compile_commands.json. -# clang doesn't support -fno-fat-lto-objects; this silences the harmless warning. -ExtraArgs: - - -std=c++20 - - -Wno-ignored-optimization-argument - -HeaderFilterRegex: '^.*/include/.*\.hpp$' - -CheckOptions: -- key: modernize-use-default-member-init.UseAssignment - value: true diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock new file mode 100644 index 00000000..5b970dd9 --- /dev/null +++ b/.claude/scheduled_tasks.lock @@ -0,0 +1 @@ +{"sessionId":"55a28c7e-8043-44c2-9829-702f303c84ba","pid":3880168,"acquiredAt":1773085726967} \ No newline at end of file diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index d2160131..00000000 --- a/.dockerignore +++ /dev/null @@ -1,50 +0,0 @@ -# .dockerignore — Exclude files from the Docker build context. -# Keeps the context small and prevents leaking build artifacts or secrets. - -# Build outputs -build/ -cache/ - -# Git history -.git/ -.gitignore -.github/ - -# Large external directories (fetched at build time inside the container) -extern/FidelityFX-FSR2/ -extern/FidelityFX-SDK/ - -# IDE / editor files -.vscode/ -.idea/ -*.swp -*.swo -*~ -.DS_Store - -# Documentation (not needed for build) -docs/ -*.md -!container/*.md - -# Test / tool outputs -logs/ - -# Host build scripts that run outside the container (not needed inside) -build.sh -build.bat -build.ps1 -rebuild.sh -rebuild.bat -rebuild.ps1 -clean.sh -debug_texture.* -extract_assets.* -extract_warden_rsa.py -restart-worldserver.sh -test.sh - -# macOS SDK tarballs that may be temporarily placed here -*.tar.xz -*.tar.gz -*.tar.bz2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6576844b..6893f717 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,12 +2,11 @@ name: Build on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true WOWEE_AMD_FSR2_REPO: https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git WOWEE_AMD_FSR2_REF: master WOWEE_FFX_SDK_REPO: https://github.com/Kelsidavis/FidelityFX-SDK.git @@ -21,110 +20,105 @@ jobs: fail-fast: false matrix: include: - - arch: x86-64 - runner: ubuntu-24.04 - deb_arch: amd64 - build_jobs: $(nproc) - - arch: arm64 - runner: ubuntu-24.04-arm - deb_arch: arm64 - build_jobs: 2 + - arch: x86-64 + runner: ubuntu-24.04 + deb_arch: amd64 + - arch: arm64 + runner: ubuntu-24.04-arm + deb_arch: arm64 steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true - - name: Cache apt packages - uses: actions/cache@v4 - with: - path: /var/cache/apt/archives/*.deb - key: apt-${{ matrix.arch }}-${{ hashFiles('.github/workflows/build.yml') }} - restore-keys: apt-${{ matrix.arch }}- + - name: Cache apt packages + uses: actions/cache@v4 + with: + path: /var/cache/apt/archives/*.deb + key: apt-${{ matrix.arch }}-${{ hashFiles('.github/workflows/build.yml') }} + restore-keys: apt-${{ matrix.arch }}- - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - cmake \ - build-essential \ - pkg-config \ - libsdl2-dev \ - libglew-dev \ - libglm-dev \ - libssl-dev \ - zlib1g-dev \ - libvulkan-dev \ - vulkan-tools \ - glslc \ - libavformat-dev \ - libavcodec-dev \ - libswscale-dev \ - libavutil-dev \ - libunicorn-dev \ - libx11-dev - sudo apt-get install -y libstorm-dev || true + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + cmake \ + build-essential \ + pkg-config \ + libsdl2-dev \ + libglew-dev \ + libglm-dev \ + libssl-dev \ + zlib1g-dev \ + libvulkan-dev \ + vulkan-tools \ + glslc \ + libavformat-dev \ + libavcodec-dev \ + libswscale-dev \ + libavutil-dev \ + libunicorn-dev \ + libx11-dev + sudo apt-get install -y libstorm-dev || true - - name: Fetch AMD FSR2 SDK - run: | - rm -rf extern/FidelityFX-FSR2 - git clone --depth 1 --branch "${WOWEE_AMD_FSR2_REF}" "${WOWEE_AMD_FSR2_REPO}" extern/FidelityFX-FSR2 - rm -rf extern/FidelityFX-SDK - git clone --depth 1 --branch "${WOWEE_FFX_SDK_REF}" "${WOWEE_FFX_SDK_REPO}" extern/FidelityFX-SDK + - name: Fetch AMD FSR2 SDK + run: | + rm -rf extern/FidelityFX-FSR2 + git clone --depth 1 --branch "${WOWEE_AMD_FSR2_REF}" "${WOWEE_AMD_FSR2_REPO}" extern/FidelityFX-FSR2 + rm -rf extern/FidelityFX-SDK + git clone --depth 1 --branch "${WOWEE_FFX_SDK_REF}" "${WOWEE_FFX_SDK_REPO}" extern/FidelityFX-SDK - - name: Check AMD FSR2 Vulkan permutation headers - run: | - set -euo pipefail - SDK_DIR="$PWD/extern/FidelityFX-FSR2" - OUT_DIR="$SDK_DIR/src/ffx-fsr2-api/vk/shaders" - if [ -f "$OUT_DIR/ffx_fsr2_accumulate_pass_permutations.h" ]; then - echo "AMD FSR2 Vulkan permutation headers detected." - else - echo "AMD FSR2 Vulkan permutation headers not found in SDK checkout." - echo "WoWee CMake will bootstrap vendored headers." - fi + - name: Check AMD FSR2 Vulkan permutation headers + run: | + set -euo pipefail + SDK_DIR="$PWD/extern/FidelityFX-FSR2" + OUT_DIR="$SDK_DIR/src/ffx-fsr2-api/vk/shaders" + if [ -f "$OUT_DIR/ffx_fsr2_accumulate_pass_permutations.h" ]; then + echo "AMD FSR2 Vulkan permutation headers detected." + else + echo "AMD FSR2 Vulkan permutation headers not found in SDK checkout." + echo "WoWee CMake will bootstrap vendored headers." + fi - - name: Check FidelityFX-SDK Kits framegen headers - run: | - set -euo pipefail - KITS_DIR="$PWD/extern/FidelityFX-SDK/Kits/FidelityFX" - test -f "$KITS_DIR/upscalers/fsr3/include/ffx_fsr3upscaler.h" - test -f "$KITS_DIR/framegeneration/fsr3/include/ffx_frameinterpolation.h" - test -f "$KITS_DIR/framegeneration/fsr3/include/ffx_opticalflow.h" - test -f "$KITS_DIR/backend/vk/ffx_vk.h" - echo "FidelityFX-SDK Kits framegen headers detected." + - name: Check FidelityFX-SDK Kits framegen headers + run: | + set -euo pipefail + KITS_DIR="$PWD/extern/FidelityFX-SDK/Kits/FidelityFX" + test -f "$KITS_DIR/upscalers/fsr3/include/ffx_fsr3upscaler.h" + test -f "$KITS_DIR/framegeneration/fsr3/include/ffx_frameinterpolation.h" + test -f "$KITS_DIR/framegeneration/fsr3/include/ffx_opticalflow.h" + test -f "$KITS_DIR/backend/vk/ffx_vk.h" + echo "FidelityFX-SDK Kits framegen headers detected." - - name: Configure (AMD ON) - run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DWOWEE_ENABLE_AMD_FSR2=ON -DWOWEE_BUILD_TESTS=ON + - name: Configure (AMD ON) + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DWOWEE_ENABLE_AMD_FSR2=ON - - name: Assert AMD FSR2 target - run: cmake --build build --target wowee_fsr2_amd_vk --parallel ${{ matrix.build_jobs }} + - name: Assert AMD FSR2 target + run: cmake --build build --target wowee_fsr2_amd_vk --parallel $(nproc) - - name: Assert AMD FSR3 framegen probe target (if present) - run: | - set -euo pipefail - if cmake --build build --target help | grep -q 'wowee_fsr3_framegen_amd_vk_probe'; then - cmake --build build --target wowee_fsr3_framegen_amd_vk_probe --parallel ${{ matrix.build_jobs }} - else - echo "FSR3 framegen probe target not generated for this SDK layout; continuing." - fi + - name: Assert AMD FSR3 framegen probe target (if present) + run: | + set -euo pipefail + if cmake --build build --target help | grep -q 'wowee_fsr3_framegen_amd_vk_probe'; then + cmake --build build --target wowee_fsr3_framegen_amd_vk_probe --parallel $(nproc) + else + echo "FSR3 framegen probe target not generated for this SDK layout; continuing." + fi - - name: Build - run: cmake --build build --parallel ${{ matrix.build_jobs }} + - name: Build + run: cmake --build build --parallel $(nproc) - - name: Run tests - run: cd build && ctest --output-on-failure + - name: Package (DEB) + run: cd build && cpack -G DEB - - name: Package (DEB) - run: cd build && cpack -G DEB - - - name: Upload DEB - uses: actions/upload-artifact@v4 - with: - name: wowee-linux-${{ matrix.arch }}-deb - path: build/wowee-*.deb - if-no-files-found: error + - name: Upload DEB + uses: actions/upload-artifact@v4 + with: + name: wowee-linux-${{ matrix.arch }}-deb + path: build/wowee-*.deb + if-no-files-found: error build-macos: name: Build (macOS ${{ matrix.arch }}) @@ -133,379 +127,311 @@ jobs: fail-fast: false matrix: include: - - arch: arm64 - runner: macos-15 + - arch: arm64 + runner: macos-15 steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true - - name: Install dependencies - run: | - brew install cmake pkg-config sdl2 glew glm openssl@3 zlib ffmpeg unicorn \ - stormlib vulkan-loader vulkan-headers shaderc dylibbundler || true - # dylibbundler may not be in all brew mirrors; install separately to not block others - brew install dylibbundler 2>/dev/null || true + - name: Install dependencies + run: | + brew install cmake pkg-config sdl2 glew glm openssl@3 zlib ffmpeg unicorn \ + stormlib vulkan-loader vulkan-headers shaderc dylibbundler || true + # dylibbundler may not be in all brew mirrors; install separately to not block others + brew install dylibbundler 2>/dev/null || true - - name: Fetch AMD FSR2 SDK - run: | - rm -rf extern/FidelityFX-FSR2 - git clone --depth 1 --branch "${WOWEE_AMD_FSR2_REF}" "${WOWEE_AMD_FSR2_REPO}" extern/FidelityFX-FSR2 - rm -rf extern/FidelityFX-SDK - git clone --depth 1 --branch "${WOWEE_FFX_SDK_REF}" "${WOWEE_FFX_SDK_REPO}" extern/FidelityFX-SDK + - name: Fetch AMD FSR2 SDK + run: | + rm -rf extern/FidelityFX-FSR2 + git clone --depth 1 --branch "${WOWEE_AMD_FSR2_REF}" "${WOWEE_AMD_FSR2_REPO}" extern/FidelityFX-FSR2 + rm -rf extern/FidelityFX-SDK + git clone --depth 1 --branch "${WOWEE_FFX_SDK_REF}" "${WOWEE_FFX_SDK_REPO}" extern/FidelityFX-SDK - - name: Configure - run: | - BREW=$(brew --prefix) - export PKG_CONFIG_PATH="$BREW/lib/pkgconfig:$(brew --prefix ffmpeg)/lib/pkgconfig:$(brew --prefix openssl@3)/lib/pkgconfig:$(brew --prefix vulkan-loader)/lib/pkgconfig:$(brew --prefix shaderc)/lib/pkgconfig" - cmake -S . -B build \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH="$BREW" \ - -DOPENSSL_ROOT_DIR="$(brew --prefix openssl@3)" \ - -DWOWEE_ENABLE_AMD_FSR2=ON \ - -DWOWEE_BUILD_TESTS=ON + - name: Configure + run: | + BREW=$(brew --prefix) + export PKG_CONFIG_PATH="$BREW/lib/pkgconfig:$(brew --prefix ffmpeg)/lib/pkgconfig:$(brew --prefix openssl@3)/lib/pkgconfig:$(brew --prefix vulkan-loader)/lib/pkgconfig:$(brew --prefix shaderc)/lib/pkgconfig" + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="$BREW" \ + -DOPENSSL_ROOT_DIR="$(brew --prefix openssl@3)" \ + -DWOWEE_ENABLE_AMD_FSR2=ON - - name: Assert AMD FSR2 target - run: cmake --build build --target wowee_fsr2_amd_vk --parallel $(sysctl -n hw.logicalcpu) + - name: Assert AMD FSR2 target + run: cmake --build build --target wowee_fsr2_amd_vk --parallel $(sysctl -n hw.logicalcpu) - - name: Assert AMD FSR3 framegen probe target (if present) - run: | - if cmake --build build --target help | grep -q 'wowee_fsr3_framegen_amd_vk_probe'; then - cmake --build build --target wowee_fsr3_framegen_amd_vk_probe --parallel $(sysctl -n hw.logicalcpu) - else - echo "FSR3 framegen probe target not generated for this SDK layout; continuing." - fi - - - name: Build - run: cmake --build build --parallel $(sysctl -n hw.logicalcpu) - - - name: Run tests - run: cd build && ctest --output-on-failure - - - name: Create .app bundle - run: | - mkdir -p Wowee.app/Contents/{MacOS,Frameworks,Resources} - - # Wrapper launch script — cd to MacOS/ so ./assets/ resolves correctly - printf '#!/bin/bash\ncd "$(dirname "$0")"\nexec ./wowee_bin "$@"\n' \ - > Wowee.app/Contents/MacOS/wowee - chmod +x Wowee.app/Contents/MacOS/wowee - - # Actual binary - cp build/bin/wowee Wowee.app/Contents/MacOS/wowee_bin - - # Assets (exclude proprietary music) - rsync -a --exclude='Original Music' build/bin/assets/ \ - Wowee.app/Contents/MacOS/assets/ - - # Bundle dylibs (if dylibbundler available) - if command -v dylibbundler &>/dev/null; then - dylibbundler -od -b \ - -x Wowee.app/Contents/MacOS/wowee_bin \ - -d Wowee.app/Contents/Frameworks/ \ - -p @executable_path/../Frameworks/ - fi - - # dylibbundler may miss Homebrew's Vulkan loader on some runner images. - # Copy all vulkan-loader dylib names so wowee_bin can resolve whichever - # install_name it was linked against (e.g. libvulkan.1.4.341.dylib). - VULKAN_LIB_DIR="$(brew --prefix vulkan-loader)/lib" - for lib in "${VULKAN_LIB_DIR}"/libvulkan*.dylib; do - [ -e "${lib}" ] || continue - cp -f "${lib}" Wowee.app/Contents/Frameworks/ - done - - if ! ls Wowee.app/Contents/Frameworks/libvulkan*.dylib >/dev/null 2>&1; then - echo "Missing Vulkan loader dylib(s) in app bundle Frameworks/" >&2 - exit 1 - fi - - # dylibbundler may miss Homebrew's OpenSSL on some runner images. - # Copy libssl and libcrypto so wowee_bin can resolve them at runtime. - OPENSSL_LIB_DIR="$(brew --prefix openssl@3)/lib" - for lib in "${OPENSSL_LIB_DIR}"/libssl*.dylib "${OPENSSL_LIB_DIR}"/libcrypto*.dylib; do - [ -e "${lib}" ] || continue - cp -f "${lib}" Wowee.app/Contents/Frameworks/ - done - - if ! ls Wowee.app/Contents/Frameworks/libssl*.dylib >/dev/null 2>&1; then - echo "Missing OpenSSL libssl dylib(s) in app bundle Frameworks/" >&2 - exit 1 - fi - - # Info.plist - cat > Wowee.app/Contents/Info.plist << 'EOF' - - - - CFBundleExecutablewowee - CFBundleIdentifiercom.wowee.app - CFBundleNameWowee - CFBundleVersion1.0.0 - CFBundleShortVersionString1.0.0 - CFBundlePackageTypeAPPL - - EOF - - # Ad-hoc codesign (allows running on the local machine) - codesign --force --deep --sign - Wowee.app - - - name: Verify bundled dylibs - run: | - set -euo pipefail - echo "=== dylib references for wowee_bin ===" - otool -L Wowee.app/Contents/MacOS/wowee_bin - - # Every non-system dylib referenced by the binary must resolve inside - # the bundle. System paths (/usr/lib, /System) are always present on - # macOS and don't need bundling. - missing=0 - while IFS= read -r dep; do - # Strip leading whitespace and version info: " /path/to/lib.dylib (compat ...)" - path=$(echo "$dep" | sed 's/^[[:space:]]*//;s/ (compat.*//') - case "$path" in - /usr/lib/*|/System/*|@executable_path/*|@rpath/*) continue ;; - esac - # Resolve @loader_path relative to the binary - resolved="${path/#@loader_path/Wowee.app/Contents/MacOS}" - if [ ! -f "$resolved" ]; then - basename=$(basename "$path") - if [ ! -f "Wowee.app/Contents/Frameworks/$basename" ]; then - echo "ERROR: unbundled dylib: $path" >&2 - missing=$((missing + 1)) - fi + - name: Assert AMD FSR3 framegen probe target (if present) + run: | + if cmake --build build --target help | grep -q 'wowee_fsr3_framegen_amd_vk_probe'; then + cmake --build build --target wowee_fsr3_framegen_amd_vk_probe --parallel $(sysctl -n hw.logicalcpu) + else + echo "FSR3 framegen probe target not generated for this SDK layout; continuing." fi - done < <(otool -L Wowee.app/Contents/MacOS/wowee_bin | tail -n +2) - if [ "$missing" -gt 0 ]; then - echo "" - echo "=== Frameworks directory ===" - ls -la Wowee.app/Contents/Frameworks/ - echo "" - echo "FAIL: $missing dylib(s) missing from app bundle" >&2 - exit 1 - fi - echo "All non-system dylibs are bundled." + - name: Build + run: cmake --build build --parallel $(sysctl -n hw.logicalcpu) - - name: Create DMG - run: | - set -euo pipefail - rm -f Wowee.dmg - # CI runners can occasionally leave a mounted volume around; detach if present. - if [ -d "/Volumes/Wowee" ]; then - hdiutil detach "/Volumes/Wowee" -force || true - sleep 2 - fi + - name: Create .app bundle + run: | + mkdir -p Wowee.app/Contents/{MacOS,Frameworks,Resources} - ok=0 - for attempt in 1 2 3 4 5; do - if hdiutil create -volname Wowee -srcfolder Wowee.app -ov -format UDZO Wowee.dmg; then - ok=1 - break + # Wrapper launch script — cd to MacOS/ so ./assets/ resolves correctly + printf '#!/bin/bash\ncd "$(dirname "$0")"\nexec ./wowee_bin "$@"\n' \ + > Wowee.app/Contents/MacOS/wowee + chmod +x Wowee.app/Contents/MacOS/wowee + + # Actual binary + cp build/bin/wowee Wowee.app/Contents/MacOS/wowee_bin + + # Assets (exclude proprietary music) + rsync -a --exclude='Original Music' build/bin/assets/ \ + Wowee.app/Contents/MacOS/assets/ + + # Bundle dylibs (if dylibbundler available) + if command -v dylibbundler &>/dev/null; then + dylibbundler -od -b \ + -x Wowee.app/Contents/MacOS/wowee_bin \ + -d Wowee.app/Contents/Frameworks/ \ + -p @executable_path/../Frameworks/ fi - echo "hdiutil create failed on attempt ${attempt}; retrying..." + + # Info.plist + cat > Wowee.app/Contents/Info.plist << 'EOF' + + + + CFBundleExecutablewowee + CFBundleIdentifiercom.wowee.app + CFBundleNameWowee + CFBundleVersion1.0.0 + CFBundleShortVersionString1.0.0 + CFBundlePackageTypeAPPL + + EOF + + # Ad-hoc codesign (allows running on the local machine) + codesign --force --deep --sign - Wowee.app + + - name: Create DMG + run: | + set -euo pipefail + rm -f Wowee.dmg + # CI runners can occasionally leave a mounted volume around; detach if present. if [ -d "/Volumes/Wowee" ]; then hdiutil detach "/Volumes/Wowee" -force || true + sleep 2 fi - sleep 3 - done - if [ "$ok" -ne 1 ] || [ ! -f Wowee.dmg ]; then - echo "Failed to create Wowee.dmg after retries." - exit 1 - fi + ok=0 + for attempt in 1 2 3 4 5; do + if hdiutil create -volname Wowee -srcfolder Wowee.app -ov -format UDZO Wowee.dmg; then + ok=1 + break + fi + echo "hdiutil create failed on attempt ${attempt}; retrying..." + if [ -d "/Volumes/Wowee" ]; then + hdiutil detach "/Volumes/Wowee" -force || true + fi + sleep 3 + done - - name: Upload DMG - uses: actions/upload-artifact@v4 - with: - name: wowee-macos-${{ matrix.arch }}-dmg - path: Wowee.dmg - if-no-files-found: error + if [ "$ok" -ne 1 ] || [ ! -f Wowee.dmg ]; then + echo "Failed to create Wowee.dmg after retries." + exit 1 + fi + + - name: Upload DMG + uses: actions/upload-artifact@v4 + with: + name: wowee-macos-${{ matrix.arch }}-dmg + path: Wowee.dmg + if-no-files-found: error build-windows-arm: name: Build (windows-arm64) runs-on: windows-11-arm steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true - - name: Set up MSYS2 - uses: msys2/setup-msys2@v2 - with: - msystem: CLANGARM64 - update: true - install: >- - mingw-w64-clang-aarch64-cmake - mingw-w64-clang-aarch64-clang - mingw-w64-clang-aarch64-ninja - mingw-w64-clang-aarch64-pkgconf - mingw-w64-clang-aarch64-SDL2 - mingw-w64-clang-aarch64-glew - mingw-w64-clang-aarch64-glm - mingw-w64-clang-aarch64-openssl - mingw-w64-clang-aarch64-zlib - mingw-w64-clang-aarch64-ffmpeg - mingw-w64-clang-aarch64-unicorn - mingw-w64-clang-aarch64-vulkan-loader - mingw-w64-clang-aarch64-vulkan-headers - mingw-w64-clang-aarch64-shaderc - git + - name: Set up MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: CLANGARM64 + update: true + install: >- + mingw-w64-clang-aarch64-cmake + mingw-w64-clang-aarch64-clang + mingw-w64-clang-aarch64-ninja + mingw-w64-clang-aarch64-pkgconf + mingw-w64-clang-aarch64-SDL2 + mingw-w64-clang-aarch64-glew + mingw-w64-clang-aarch64-glm + mingw-w64-clang-aarch64-openssl + mingw-w64-clang-aarch64-zlib + mingw-w64-clang-aarch64-ffmpeg + mingw-w64-clang-aarch64-unicorn + mingw-w64-clang-aarch64-vulkan-loader + mingw-w64-clang-aarch64-vulkan-headers + mingw-w64-clang-aarch64-shaderc + git - - name: Build StormLib from source - shell: msys2 {0} - run: | - git clone --depth 1 https://github.com/ladislav-zezula/StormLib.git /tmp/StormLib - # Disable x86 inline asm in bundled libtomcrypt (bswapl/movl) — - # __MINGW32__ is defined on CLANGARM64 which incorrectly enables x86 asm - cmake -S /tmp/StormLib -B /tmp/StormLib/build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \ - -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_C_FLAGS="-DLTC_NO_BSWAP" \ - -DCMAKE_CXX_FLAGS="-DLTC_NO_BSWAP" - cmake --build /tmp/StormLib/build --parallel $(nproc) - cmake --install /tmp/StormLib/build + - name: Build StormLib from source + shell: msys2 {0} + run: | + git clone --depth 1 https://github.com/ladislav-zezula/StormLib.git /tmp/StormLib + # Disable x86 inline asm in bundled libtomcrypt (bswapl/movl) — + # __MINGW32__ is defined on CLANGARM64 which incorrectly enables x86 asm + cmake -S /tmp/StormLib -B /tmp/StormLib/build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_C_FLAGS="-DLTC_NO_BSWAP" \ + -DCMAKE_CXX_FLAGS="-DLTC_NO_BSWAP" + cmake --build /tmp/StormLib/build --parallel $(nproc) + cmake --install /tmp/StormLib/build - - name: Fetch AMD FSR2 SDK - shell: msys2 {0} - run: | - rm -rf extern/FidelityFX-FSR2 - git clone --depth 1 --branch "${WOWEE_AMD_FSR2_REF}" "${WOWEE_AMD_FSR2_REPO}" extern/FidelityFX-FSR2 - rm -rf extern/FidelityFX-SDK - git clone --depth 1 --branch "${WOWEE_FFX_SDK_REF}" "${WOWEE_FFX_SDK_REPO}" extern/FidelityFX-SDK + - name: Fetch AMD FSR2 SDK + shell: msys2 {0} + run: | + rm -rf extern/FidelityFX-FSR2 + git clone --depth 1 --branch "${WOWEE_AMD_FSR2_REF}" "${WOWEE_AMD_FSR2_REPO}" extern/FidelityFX-FSR2 + rm -rf extern/FidelityFX-SDK + git clone --depth 1 --branch "${WOWEE_FFX_SDK_REF}" "${WOWEE_FFX_SDK_REPO}" extern/FidelityFX-SDK - - name: Configure - shell: msys2 {0} - run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWOWEE_ENABLE_AMD_FSR2=ON + - name: Configure + shell: msys2 {0} + run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWOWEE_ENABLE_AMD_FSR2=ON - - name: Assert AMD FSR2 target - shell: msys2 {0} - run: cmake --build build --target wowee_fsr2_amd_vk --parallel $(nproc) + - name: Assert AMD FSR2 target + shell: msys2 {0} + run: cmake --build build --target wowee_fsr2_amd_vk --parallel $(nproc) - - name: Assert AMD FSR3 framegen probe target (if present) - shell: msys2 {0} - run: | - if cmake --build build --target help | grep -q 'wowee_fsr3_framegen_amd_vk_probe'; then - cmake --build build --target wowee_fsr3_framegen_amd_vk_probe --parallel $(nproc) - else - echo "FSR3 framegen probe target not generated for this SDK layout; continuing." - fi + - name: Assert AMD FSR3 framegen probe target (if present) + shell: msys2 {0} + run: | + if cmake --build build --target help | grep -q 'wowee_fsr3_framegen_amd_vk_probe'; then + cmake --build build --target wowee_fsr3_framegen_amd_vk_probe --parallel $(nproc) + else + echo "FSR3 framegen probe target not generated for this SDK layout; continuing." + fi - - name: Build - shell: msys2 {0} - run: cmake --build build --parallel $(nproc) + - name: Build + shell: msys2 {0} + run: cmake --build build --parallel $(nproc) - - name: Bundle DLLs - shell: msys2 {0} - run: | - ldd build/bin/wowee.exe \ - | awk '/=> \// { print $3 }' \ - | grep -iv '^/c/Windows' \ - | xargs -I{} sh -c 'cp -n "{}" build/bin/ 2>/dev/null || true' + - name: Bundle DLLs + shell: msys2 {0} + run: | + ldd build/bin/wowee.exe \ + | awk '/=> \// { print $3 }' \ + | grep -iv '^/c/Windows' \ + | xargs -I{} sh -c 'cp -n "{}" build/bin/ 2>/dev/null || true' - - name: Package (ZIP) - shell: msys2 {0} - run: cd build && cpack -G ZIP + - name: Package (ZIP) + shell: msys2 {0} + run: cd build && cpack -G ZIP - - name: Upload ZIP - uses: actions/upload-artifact@v4 - with: - name: wowee-windows-arm64-zip - path: build/wowee-*.zip - if-no-files-found: error + - name: Upload ZIP + uses: actions/upload-artifact@v4 + with: + name: wowee-windows-arm64-zip + path: build/wowee-*.zip + if-no-files-found: error build-windows: name: Build (windows-x86-64) runs-on: windows-latest steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true - - name: Set up MSYS2 - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - update: false - install: >- - mingw-w64-x86_64-cmake - mingw-w64-x86_64-gcc - mingw-w64-x86_64-ninja - mingw-w64-x86_64-pkgconf - mingw-w64-x86_64-SDL2 - mingw-w64-x86_64-glew - mingw-w64-x86_64-glm - mingw-w64-x86_64-openssl - mingw-w64-x86_64-zlib - mingw-w64-x86_64-ffmpeg - mingw-w64-x86_64-unicorn - mingw-w64-x86_64-vulkan-loader - mingw-w64-x86_64-vulkan-headers - mingw-w64-x86_64-shaderc - mingw-w64-x86_64-nsis - git + - name: Set up MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: false + install: >- + mingw-w64-x86_64-cmake + mingw-w64-x86_64-gcc + mingw-w64-x86_64-ninja + mingw-w64-x86_64-pkgconf + mingw-w64-x86_64-SDL2 + mingw-w64-x86_64-glew + mingw-w64-x86_64-glm + mingw-w64-x86_64-openssl + mingw-w64-x86_64-zlib + mingw-w64-x86_64-ffmpeg + mingw-w64-x86_64-unicorn + mingw-w64-x86_64-vulkan-loader + mingw-w64-x86_64-vulkan-headers + mingw-w64-x86_64-shaderc + mingw-w64-x86_64-nsis + git - - name: Build StormLib from source - shell: msys2 {0} - run: | - git clone --depth 1 https://github.com/ladislav-zezula/StormLib.git /tmp/StormLib - cmake -S /tmp/StormLib -B /tmp/StormLib/build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \ - -DBUILD_SHARED_LIBS=OFF - cmake --build /tmp/StormLib/build --parallel $(nproc) - cmake --install /tmp/StormLib/build + - name: Build StormLib from source + shell: msys2 {0} + run: | + git clone --depth 1 https://github.com/ladislav-zezula/StormLib.git /tmp/StormLib + cmake -S /tmp/StormLib -B /tmp/StormLib/build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \ + -DBUILD_SHARED_LIBS=OFF + cmake --build /tmp/StormLib/build --parallel $(nproc) + cmake --install /tmp/StormLib/build - - name: Fetch AMD FSR2 SDK - shell: msys2 {0} - run: | - rm -rf extern/FidelityFX-FSR2 - git clone --depth 1 --branch "${WOWEE_AMD_FSR2_REF}" "${WOWEE_AMD_FSR2_REPO}" extern/FidelityFX-FSR2 - rm -rf extern/FidelityFX-SDK - git clone --depth 1 --branch "${WOWEE_FFX_SDK_REF}" "${WOWEE_FFX_SDK_REPO}" extern/FidelityFX-SDK + - name: Fetch AMD FSR2 SDK + shell: msys2 {0} + run: | + rm -rf extern/FidelityFX-FSR2 + git clone --depth 1 --branch "${WOWEE_AMD_FSR2_REF}" "${WOWEE_AMD_FSR2_REPO}" extern/FidelityFX-FSR2 + rm -rf extern/FidelityFX-SDK + git clone --depth 1 --branch "${WOWEE_FFX_SDK_REF}" "${WOWEE_FFX_SDK_REPO}" extern/FidelityFX-SDK - - name: Configure - shell: msys2 {0} - run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWOWEE_ENABLE_AMD_FSR2=ON + - name: Configure + shell: msys2 {0} + run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWOWEE_ENABLE_AMD_FSR2=ON - - name: Assert AMD FSR2 target - shell: msys2 {0} - run: cmake --build build --target wowee_fsr2_amd_vk --parallel $(nproc) + - name: Assert AMD FSR2 target + shell: msys2 {0} + run: cmake --build build --target wowee_fsr2_amd_vk --parallel $(nproc) - - name: Assert AMD FSR3 framegen probe target (if present) - shell: msys2 {0} - run: | - if cmake --build build --target help | grep -q 'wowee_fsr3_framegen_amd_vk_probe'; then - cmake --build build --target wowee_fsr3_framegen_amd_vk_probe --parallel $(nproc) - else - echo "FSR3 framegen probe target not generated for this SDK layout; continuing." - fi + - name: Assert AMD FSR3 framegen probe target (if present) + shell: msys2 {0} + run: | + if cmake --build build --target help | grep -q 'wowee_fsr3_framegen_amd_vk_probe'; then + cmake --build build --target wowee_fsr3_framegen_amd_vk_probe --parallel $(nproc) + else + echo "FSR3 framegen probe target not generated for this SDK layout; continuing." + fi - - name: Build - shell: msys2 {0} - run: cmake --build build --parallel $(nproc) + - name: Build + shell: msys2 {0} + run: cmake --build build --parallel $(nproc) - - name: Bundle DLLs - shell: msys2 {0} - run: | - ldd build/bin/wowee.exe \ - | awk '/=> \// { print $3 }' \ - | grep -iv '^/c/Windows' \ - | xargs -I{} sh -c 'cp -n "{}" build/bin/ 2>/dev/null || true' + - name: Bundle DLLs + shell: msys2 {0} + run: | + ldd build/bin/wowee.exe \ + | awk '/=> \// { print $3 }' \ + | grep -iv '^/c/Windows' \ + | xargs -I{} sh -c 'cp -n "{}" build/bin/ 2>/dev/null || true' - - name: Package (NSIS) - shell: msys2 {0} - run: cd build && cpack -G NSIS + - name: Package (NSIS) + shell: msys2 {0} + run: cd build && cpack -G NSIS - - name: Upload installer - uses: actions/upload-artifact@v4 - with: - name: wowee-windows-x86-64-installer - path: build/wowee-*.exe - if-no-files-found: error + - name: Upload installer + uses: actions/upload-artifact@v4 + with: + name: wowee-windows-x86-64-installer + path: build/wowee-*.exe + if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c0b9ec23..0698607a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,10 +2,7 @@ name: Release on: push: - tags: [ 'v*' ] - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + tags: ['v*'] permissions: contents: write @@ -18,257 +15,201 @@ jobs: fail-fast: false matrix: include: - - arch: x86-64 - runner: ubuntu-24.04 - - arch: arm64 - runner: ubuntu-24.04-arm + - arch: x86-64 + runner: ubuntu-24.04 + - arch: arm64 + runner: ubuntu-24.04-arm steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true - - name: Cache apt packages - uses: actions/cache@v4 - with: - path: /var/cache/apt/archives/*.deb - key: apt-${{ matrix.arch }}-${{ hashFiles('.github/workflows/release.yml') }} - restore-keys: apt-${{ matrix.arch }}- + - name: Cache apt packages + uses: actions/cache@v4 + with: + path: /var/cache/apt/archives/*.deb + key: apt-${{ matrix.arch }}-${{ hashFiles('.github/workflows/release.yml') }} + restore-keys: apt-${{ matrix.arch }}- - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - cmake \ - build-essential \ - pkg-config \ - libsdl2-dev \ - libglew-dev \ - libglm-dev \ - libssl-dev \ - zlib1g-dev \ - libvulkan-dev \ - vulkan-tools \ - glslc \ - libavformat-dev \ - libavcodec-dev \ - libswscale-dev \ - libavutil-dev \ - libunicorn-dev \ - libx11-dev - sudo apt-get install -y libstorm-dev || true + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + cmake \ + build-essential \ + pkg-config \ + libsdl2-dev \ + libglew-dev \ + libglm-dev \ + libssl-dev \ + zlib1g-dev \ + libvulkan-dev \ + vulkan-tools \ + glslc \ + libavformat-dev \ + libavcodec-dev \ + libswscale-dev \ + libavutil-dev \ + libunicorn-dev \ + libx11-dev + sudo apt-get install -y libstorm-dev || true - - name: Configure - run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + - name: Configure + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release - - name: Verify Release Config - run: | - cmake -LA -N build | grep -E '^CMAKE_BUILD_TYPE:STRING=Release$' + - name: Verify Release Config + run: | + cmake -LA -N build | grep -E '^CMAKE_BUILD_TYPE:STRING=Release$' - - name: Build - run: cmake --build build --parallel $(nproc) + - name: Build + run: cmake --build build --parallel $(nproc) - - name: Package - run: | - TAG="${GITHUB_REF_NAME}" - STAGING="wowee-${TAG}-linux-${{ matrix.arch }}" - mkdir -p "${STAGING}" + - name: Package + run: | + TAG="${GITHUB_REF_NAME}" + STAGING="wowee-${TAG}-linux-${{ matrix.arch }}" + mkdir -p "${STAGING}" - # Binary - cp build/bin/wowee "${STAGING}/" + # Binary + cp build/bin/wowee "${STAGING}/" - # Asset extraction tool (if built — requires StormLib) - if [ -f build/bin/asset_extract ]; then - cp build/bin/asset_extract "${STAGING}/" - fi + # Asset extraction tool (if built — requires StormLib) + if [ -f build/bin/asset_extract ]; then + cp build/bin/asset_extract "${STAGING}/" + fi - # Extraction scripts and GUI - cp extract_assets.sh "${STAGING}/" - cp tools/asset_pipeline_gui.py "${STAGING}/" + # Extraction scripts and GUI + cp extract_assets.sh "${STAGING}/" + cp tools/asset_pipeline_gui.py "${STAGING}/" - # Assets (exclude proprietary music) - rsync -a --exclude='Original Music' build/bin/assets/ "${STAGING}/assets/" + # Assets (exclude proprietary music) + rsync -a --exclude='Original Music' build/bin/assets/ "${STAGING}/assets/" - # Data directory (git-tracked files only) - git ls-files Data/ | while read -r f; do - mkdir -p "${STAGING}/$(dirname "$f")" - cp "$f" "${STAGING}/$f" - done + # Data directory (git-tracked files only) + git ls-files Data/ | while read -r f; do + mkdir -p "${STAGING}/$(dirname "$f")" + cp "$f" "${STAGING}/$f" + done - tar czf "${STAGING}.tar.gz" "${STAGING}" + tar czf "${STAGING}.tar.gz" "${STAGING}" - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: wowee-linux-${{ matrix.arch }} - path: wowee-*.tar.gz - if-no-files-found: error + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: wowee-linux-${{ matrix.arch }} + path: wowee-*.tar.gz + if-no-files-found: error build-macos: name: Build (macOS arm64) runs-on: macos-15 steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true - - name: Install dependencies - run: | - brew install cmake pkg-config sdl2 glew glm openssl@3 zlib ffmpeg unicorn \ - stormlib vulkan-loader vulkan-headers shaderc dylibbundler || true - brew install dylibbundler 2>/dev/null || true + - name: Install dependencies + run: | + brew install cmake pkg-config sdl2 glew glm openssl@3 zlib ffmpeg unicorn \ + stormlib vulkan-loader vulkan-headers shaderc dylibbundler || true + brew install dylibbundler 2>/dev/null || true - - name: Configure - run: | - BREW=$(brew --prefix) - export PKG_CONFIG_PATH="$BREW/lib/pkgconfig:$(brew --prefix ffmpeg)/lib/pkgconfig:$(brew --prefix openssl@3)/lib/pkgconfig:$(brew --prefix vulkan-loader)/lib/pkgconfig:$(brew --prefix shaderc)/lib/pkgconfig" - cmake -S . -B build \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH="$BREW" \ - -DOPENSSL_ROOT_DIR="$(brew --prefix openssl@3)" + - name: Configure + run: | + BREW=$(brew --prefix) + export PKG_CONFIG_PATH="$BREW/lib/pkgconfig:$(brew --prefix ffmpeg)/lib/pkgconfig:$(brew --prefix openssl@3)/lib/pkgconfig:$(brew --prefix vulkan-loader)/lib/pkgconfig:$(brew --prefix shaderc)/lib/pkgconfig" + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="$BREW" \ + -DOPENSSL_ROOT_DIR="$(brew --prefix openssl@3)" - - name: Verify Release Config - run: | - cmake -LA -N build | grep -E '^CMAKE_BUILD_TYPE:STRING=Release$' + - name: Verify Release Config + run: | + cmake -LA -N build | grep -E '^CMAKE_BUILD_TYPE:STRING=Release$' - - name: Build - run: cmake --build build --parallel $(sysctl -n hw.logicalcpu) + - name: Build + run: cmake --build build --parallel $(sysctl -n hw.logicalcpu) - - name: Create .app bundle - run: | - TAG="${GITHUB_REF_NAME}" - mkdir -p Wowee.app/Contents/{MacOS,Frameworks,Resources} + - name: Create .app bundle + run: | + TAG="${GITHUB_REF_NAME}" + mkdir -p Wowee.app/Contents/{MacOS,Frameworks,Resources} - # Wrapper launch script - printf '#!/bin/bash\ncd "$(dirname "$0")"\nexec ./wowee_bin "$@"\n' \ - > Wowee.app/Contents/MacOS/wowee - chmod +x Wowee.app/Contents/MacOS/wowee + # Wrapper launch script + printf '#!/bin/bash\ncd "$(dirname "$0")"\nexec ./wowee_bin "$@"\n' \ + > Wowee.app/Contents/MacOS/wowee + chmod +x Wowee.app/Contents/MacOS/wowee - # Actual binary - cp build/bin/wowee Wowee.app/Contents/MacOS/wowee_bin + # Actual binary + cp build/bin/wowee Wowee.app/Contents/MacOS/wowee_bin - # Asset extraction tool (if built — requires StormLib) - if [ -f build/bin/asset_extract ]; then - cp build/bin/asset_extract Wowee.app/Contents/MacOS/ - fi + # Asset extraction tool (if built — requires StormLib) + if [ -f build/bin/asset_extract ]; then + cp build/bin/asset_extract Wowee.app/Contents/MacOS/ + fi - # Extraction scripts and GUI - cp extract_assets.sh Wowee.app/Contents/MacOS/ - cp tools/asset_pipeline_gui.py Wowee.app/Contents/MacOS/ + # Extraction scripts and GUI + cp extract_assets.sh Wowee.app/Contents/MacOS/ + cp tools/asset_pipeline_gui.py Wowee.app/Contents/MacOS/ - # Assets (exclude proprietary music) - rsync -a --exclude='Original Music' build/bin/assets/ \ - Wowee.app/Contents/MacOS/assets/ + # Assets (exclude proprietary music) + rsync -a --exclude='Original Music' build/bin/assets/ \ + Wowee.app/Contents/MacOS/assets/ - # Data directory (git-tracked files only) - git ls-files Data/ | while read -r f; do - mkdir -p "Wowee.app/Contents/MacOS/$(dirname "$f")" - cp "$f" "Wowee.app/Contents/MacOS/$f" - done + # Data directory (git-tracked files only) + git ls-files Data/ | while read -r f; do + mkdir -p "Wowee.app/Contents/MacOS/$(dirname "$f")" + cp "$f" "Wowee.app/Contents/MacOS/$f" + done - # Bundle dylibs - if command -v dylibbundler &>/dev/null; then - dylibbundler -od -b \ - -x Wowee.app/Contents/MacOS/wowee_bin \ - -d Wowee.app/Contents/Frameworks/ \ - -p @executable_path/../Frameworks/ - if [ -f Wowee.app/Contents/MacOS/asset_extract ]; then + # Bundle dylibs + if command -v dylibbundler &>/dev/null; then dylibbundler -od -b \ - -x Wowee.app/Contents/MacOS/asset_extract \ + -x Wowee.app/Contents/MacOS/wowee_bin \ -d Wowee.app/Contents/Frameworks/ \ -p @executable_path/../Frameworks/ - fi - fi - - # dylibbundler may miss Homebrew's Vulkan loader on some runner images. - # Copy all vulkan-loader dylib names so wowee_bin can resolve whichever - # install_name it was linked against (e.g. libvulkan.1.4.341.dylib). - VULKAN_LIB_DIR="$(brew --prefix vulkan-loader)/lib" - for lib in "${VULKAN_LIB_DIR}"/libvulkan*.dylib; do - [ -e "${lib}" ] || continue - cp -f "${lib}" Wowee.app/Contents/Frameworks/ - done - - if ! ls Wowee.app/Contents/Frameworks/libvulkan*.dylib >/dev/null 2>&1; then - echo "Missing Vulkan loader dylib(s) in app bundle Frameworks/" >&2 - exit 1 - fi - - # dylibbundler may miss Homebrew's OpenSSL on some runner images. - # Copy libssl and libcrypto so wowee_bin can resolve them at runtime. - OPENSSL_LIB_DIR="$(brew --prefix openssl@3)/lib" - for lib in "${OPENSSL_LIB_DIR}"/libssl*.dylib "${OPENSSL_LIB_DIR}"/libcrypto*.dylib; do - [ -e "${lib}" ] || continue - cp -f "${lib}" Wowee.app/Contents/Frameworks/ - done - - if ! ls Wowee.app/Contents/Frameworks/libssl*.dylib >/dev/null 2>&1; then - echo "Missing OpenSSL libssl dylib(s) in app bundle Frameworks/" >&2 - exit 1 - fi - - # Info.plist - cat > Wowee.app/Contents/Info.plist << EOF - - - - CFBundleExecutablewowee - CFBundleIdentifiercom.wowee.app - CFBundleNameWowee - CFBundleVersion${TAG} - CFBundleShortVersionString${TAG} - CFBundlePackageTypeAPPL - - EOF - - # Ad-hoc codesign - codesign --force --deep --sign - Wowee.app - - - name: Verify bundled dylibs - run: | - set -euo pipefail - echo "=== dylib references for wowee_bin ===" - otool -L Wowee.app/Contents/MacOS/wowee_bin - - missing=0 - while IFS= read -r dep; do - path=$(echo "$dep" | sed 's/^[[:space:]]*//;s/ (compat.*//') - case "$path" in - /usr/lib/*|/System/*|@executable_path/*|@rpath/*) continue ;; - esac - resolved="${path/#@loader_path/Wowee.app/Contents/MacOS}" - if [ ! -f "$resolved" ]; then - basename=$(basename "$path") - if [ ! -f "Wowee.app/Contents/Frameworks/$basename" ]; then - echo "ERROR: unbundled dylib: $path" >&2 - missing=$((missing + 1)) + if [ -f Wowee.app/Contents/MacOS/asset_extract ]; then + dylibbundler -od -b \ + -x Wowee.app/Contents/MacOS/asset_extract \ + -d Wowee.app/Contents/Frameworks/ \ + -p @executable_path/../Frameworks/ fi fi - done < <(otool -L Wowee.app/Contents/MacOS/wowee_bin | tail -n +2) - if [ "$missing" -gt 0 ]; then - ls -la Wowee.app/Contents/Frameworks/ - echo "FAIL: $missing dylib(s) missing from app bundle" >&2 - exit 1 - fi - echo "All non-system dylibs are bundled." + # Info.plist + cat > Wowee.app/Contents/Info.plist << EOF + + + + CFBundleExecutablewowee + CFBundleIdentifiercom.wowee.app + CFBundleNameWowee + CFBundleVersion${TAG} + CFBundleShortVersionString${TAG} + CFBundlePackageTypeAPPL + + EOF - - name: Create DMG - run: | - TAG="${GITHUB_REF_NAME}" - hdiutil create -volname Wowee -srcfolder Wowee.app -ov -format UDZO \ - "wowee-${TAG}-macos-arm64.dmg" + # Ad-hoc codesign + codesign --force --deep --sign - Wowee.app - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: wowee-macos-arm64 - path: wowee-*.dmg - if-no-files-found: error + - name: Create DMG + run: | + TAG="${GITHUB_REF_NAME}" + hdiutil create -volname Wowee -srcfolder Wowee.app -ov -format UDZO \ + "wowee-${TAG}-macos-arm64.dmg" + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: wowee-macos-arm64 + path: wowee-*.dmg + if-no-files-found: error build-windows: name: Build (windows-${{ matrix.arch }}) @@ -277,145 +218,159 @@ jobs: fail-fast: false matrix: include: - - arch: x86-64 - runner: windows-latest - msystem: MINGW64 - prefix: mingw-w64-x86_64 - - arch: arm64 - runner: windows-11-arm - msystem: CLANGARM64 - prefix: mingw-w64-clang-aarch64 + - arch: x86-64 + runner: windows-latest + msystem: MINGW64 + prefix: mingw-w64-x86_64 + - arch: arm64 + runner: windows-11-arm + msystem: CLANGARM64 + prefix: mingw-w64-clang-aarch64 steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true - - name: Set up MSYS2 - uses: msys2/setup-msys2@v2 - with: - msystem: ${{ matrix.msystem }} - update: ${{ matrix.arch == 'arm64' }} - install: >- - ${{ matrix.prefix }}-cmake ${{ matrix.arch == 'x86-64' && format('{0}-gcc', matrix.prefix) || format('{0}-clang', matrix.prefix) }} ${{ matrix.prefix }}-ninja ${{ matrix.prefix }}-pkgconf ${{ matrix.prefix }}-SDL2 ${{ matrix.prefix }}-glew ${{ matrix.prefix }}-glm ${{ matrix.prefix }}-openssl ${{ matrix.prefix }}-zlib ${{ matrix.prefix }}-ffmpeg ${{ matrix.prefix }}-unicorn ${{ matrix.prefix }}-vulkan-loader ${{ matrix.prefix }}-vulkan-headers ${{ matrix.prefix }}-shaderc git + - name: Set up MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.msystem }} + update: ${{ matrix.arch == 'arm64' }} + install: >- + ${{ matrix.prefix }}-cmake + ${{ matrix.arch == 'x86-64' && format('{0}-gcc', matrix.prefix) || format('{0}-clang', matrix.prefix) }} + ${{ matrix.prefix }}-ninja + ${{ matrix.prefix }}-pkgconf + ${{ matrix.prefix }}-SDL2 + ${{ matrix.prefix }}-glew + ${{ matrix.prefix }}-glm + ${{ matrix.prefix }}-openssl + ${{ matrix.prefix }}-zlib + ${{ matrix.prefix }}-ffmpeg + ${{ matrix.prefix }}-unicorn + ${{ matrix.prefix }}-vulkan-loader + ${{ matrix.prefix }}-vulkan-headers + ${{ matrix.prefix }}-shaderc + git - - name: Install optional packages - shell: msys2 {0} - run: | - pacman -S --noconfirm --needed zip + - name: Install optional packages + shell: msys2 {0} + run: | + pacman -S --noconfirm --needed zip - - name: Build StormLib from source - shell: msys2 {0} - run: | - git clone --depth 1 https://github.com/ladislav-zezula/StormLib.git /tmp/StormLib - # Disable x86 inline asm in bundled libtomcrypt (bswapl/movl) — - # __MINGW32__ is defined on CLANGARM64 which incorrectly enables x86 asm - cmake -S /tmp/StormLib -B /tmp/StormLib/build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \ - -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_C_FLAGS="-DLTC_NO_BSWAP" \ - -DCMAKE_CXX_FLAGS="-DLTC_NO_BSWAP" - cmake --build /tmp/StormLib/build --parallel $(nproc) - cmake --install /tmp/StormLib/build + - name: Build StormLib from source + shell: msys2 {0} + run: | + git clone --depth 1 https://github.com/ladislav-zezula/StormLib.git /tmp/StormLib + # Disable x86 inline asm in bundled libtomcrypt (bswapl/movl) — + # __MINGW32__ is defined on CLANGARM64 which incorrectly enables x86 asm + cmake -S /tmp/StormLib -B /tmp/StormLib/build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_C_FLAGS="-DLTC_NO_BSWAP" \ + -DCMAKE_CXX_FLAGS="-DLTC_NO_BSWAP" + cmake --build /tmp/StormLib/build --parallel $(nproc) + cmake --install /tmp/StormLib/build - - name: Configure - shell: msys2 {0} - run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + - name: Configure + shell: msys2 {0} + run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release - - name: Verify Release Config - shell: msys2 {0} - run: | - cmake -LA -N build | grep -E '^CMAKE_BUILD_TYPE:STRING=Release$' + - name: Verify Release Config + shell: msys2 {0} + run: | + cmake -LA -N build | grep -E '^CMAKE_BUILD_TYPE:STRING=Release$' - - name: Build - shell: msys2 {0} - run: cmake --build build --parallel $(nproc) + - name: Build + shell: msys2 {0} + run: cmake --build build --parallel $(nproc) - - name: Bundle DLLs - shell: msys2 {0} - run: | - for exe in build/bin/wowee.exe build/bin/asset_extract.exe; do - [ -f "$exe" ] || continue - ldd "$exe" \ - | awk '/=> \// { print $3 }' \ - | grep -iv '^/c/Windows' \ - | xargs -I{} sh -c 'cp -n "{}" build/bin/ 2>/dev/null || true' - done + - name: Bundle DLLs + shell: msys2 {0} + run: | + for exe in build/bin/wowee.exe build/bin/asset_extract.exe; do + [ -f "$exe" ] || continue + ldd "$exe" \ + | awk '/=> \// { print $3 }' \ + | grep -iv '^/c/Windows' \ + | xargs -I{} sh -c 'cp -n "{}" build/bin/ 2>/dev/null || true' + done - - name: Package - shell: msys2 {0} - run: | - TAG="${GITHUB_REF_NAME}" - STAGING="wowee-${TAG}-windows-${{ matrix.arch }}" - mkdir -p "${STAGING}" + - name: Package + shell: msys2 {0} + run: | + TAG="${GITHUB_REF_NAME}" + STAGING="wowee-${TAG}-windows-${{ matrix.arch }}" + mkdir -p "${STAGING}" - # Binary and DLLs - cp build/bin/wowee.exe "${STAGING}/" - cp build/bin/*.dll "${STAGING}/" 2>/dev/null || true + # Binary and DLLs + cp build/bin/wowee.exe "${STAGING}/" + cp build/bin/*.dll "${STAGING}/" 2>/dev/null || true - # Asset extraction tool (if built — requires StormLib) - if [ -f build/bin/asset_extract.exe ]; then - cp build/bin/asset_extract.exe "${STAGING}/" - fi + # Asset extraction tool (if built — requires StormLib) + if [ -f build/bin/asset_extract.exe ]; then + cp build/bin/asset_extract.exe "${STAGING}/" + fi - # Extraction scripts and GUI - cp extract_assets.ps1 "${STAGING}/" - cp extract_assets.bat "${STAGING}/" - cp tools/asset_pipeline_gui.py "${STAGING}/" + # Extraction scripts and GUI + cp extract_assets.ps1 "${STAGING}/" + cp extract_assets.bat "${STAGING}/" + cp tools/asset_pipeline_gui.py "${STAGING}/" - # Assets (exclude proprietary music) - mkdir -p "${STAGING}/assets" - for d in build/bin/assets/*/; do - dirname="$(basename "$d")" - [ "$dirname" = "Original Music" ] && continue - cp -r "$d" "${STAGING}/assets/" - done - # Copy top-level asset files - cp build/bin/assets/* "${STAGING}/assets/" 2>/dev/null || true + # Assets (exclude proprietary music) + mkdir -p "${STAGING}/assets" + for d in build/bin/assets/*/; do + dirname="$(basename "$d")" + [ "$dirname" = "Original Music" ] && continue + cp -r "$d" "${STAGING}/assets/" + done + # Copy top-level asset files + cp build/bin/assets/* "${STAGING}/assets/" 2>/dev/null || true - # Data directory (git-tracked files only) - git ls-files Data/ | while read -r f; do - mkdir -p "${STAGING}/$(dirname "$f")" - cp "$f" "${STAGING}/$f" - done + # Data directory (git-tracked files only) + git ls-files Data/ | while read -r f; do + mkdir -p "${STAGING}/$(dirname "$f")" + cp "$f" "${STAGING}/$f" + done - # Create ZIP - zip -r "${STAGING}.zip" "${STAGING}" + # Create ZIP + zip -r "${STAGING}.zip" "${STAGING}" - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: wowee-windows-${{ matrix.arch }} - path: wowee-*.zip - if-no-files-found: error + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: wowee-windows-${{ matrix.arch }} + path: wowee-*.zip + if-no-files-found: error release: name: Create Release - needs: [ build-linux, build-macos, build-windows ] + needs: [build-linux, build-macos, build-windows] runs-on: ubuntu-latest steps: - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts/ + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts/ - - name: Create GitHub Release - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - run: | - TAG="${GITHUB_REF_NAME}" + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + TAG="${GITHUB_REF_NAME}" - # Collect all release files - FILES=() - for f in artifacts/*/*; do - FILES+=("$f") - done + # Collect all release files + FILES=() + for f in artifacts/*/*; do + FILES+=("$f") + done - gh release create "${TAG}" \ - --title "Wowee ${TAG}" \ - --generate-notes \ - "${FILES[@]}" + gh release create "${TAG}" \ + --title "Wowee ${TAG}" \ + --generate-notes \ + "${FILES[@]}" diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 6e5f63af..4709225b 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -7,9 +7,6 @@ on: branches: [master] workflow_dispatch: -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - permissions: contents: read diff --git a/.gitignore b/.gitignore index 7df4f225..4ddf59ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ # Build directories build/ -build_asan/ -build-debug/ build-sanitize/ bin/ lib/ @@ -36,9 +34,6 @@ Makefile *.app wowee -# Claude Code internal state -.claude/ - # IDE files .vscode/ .idea/ @@ -47,31 +42,12 @@ wowee *~ .DS_Store -# Compilation database (regenerated by cmake) -compile_commands.json - -# Language server caches -.ccls -.ccls-cache/ -.cache/clangd/ - -# Tags -tags -TAGS -.tags -cscope.out - # External dependencies (except submodules and vendored headers) extern/* !extern/.gitkeep -!extern/catch2 !extern/imgui !extern/vk-bootstrap -!extern/FidelityFX-SDK -!extern/FidelityFX-FSR2 !extern/vk_mem_alloc.h -!extern/lua-5.1.5 -!extern/VERSIONS.md # ImGui state imgui.ini @@ -91,9 +67,27 @@ saves/ wowee_[0-9][0-9][0-9][0-9] # Extracted assets (run ./extract_assets.sh or .\extract_assets.ps1 to generate) -Data/* -!Data/opcodes - +Data/db/ +Data/character/ +Data/creature/ +Data/terrain/ +Data/world/ +Data/interface/ +Data/item/ +Data/sound/ +Data/spell/ +Data/environment/ +Data/misc/ +Data/enUS/ +Data/Character/ +Data/Creature/ +Data/World/ +Data/manifest.json +Data/expansions/*/manifest.json +Data/expansions/*/assets/ +Data/expansions/*/overlay/ +Data/expansions/*/db/*.csv +Data/hd/ ingest/ # Asset pipeline state and texture packs @@ -106,10 +100,3 @@ node_modules/ # Python cache artifacts tools/__pycache__/ *.pyc - -# artifacts -.codex-loop/ - -# Local agent instructions -AGENTS.md -codex-loop.sh diff --git a/.gitmodules b/.gitmodules index 713e63aa..84acc55c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,13 +6,3 @@ path = extern/vk-bootstrap url = https://github.com/charles-lunarg/vk-bootstrap.git shallow = true -[submodule "extern/FidelityFX-SDK"] - path = extern/FidelityFX-SDK - url = https://github.com/Kelsidavis/FidelityFX-SDK.git - shallow = true - update = none -[submodule "extern/FidelityFX-FSR2"] - path = extern/FidelityFX-FSR2 - url = https://github.com/Kelsidavis/FidelityFX-FSR2.git - shallow = true - ignore = dirty diff --git a/.semgrepignore b/.semgrepignore deleted file mode 100644 index eb36847a..00000000 --- a/.semgrepignore +++ /dev/null @@ -1,8 +0,0 @@ -# Vendored third-party code (frozen releases, not ours to modify) -extern/lua-5.1.5/ -extern/imgui/ -extern/stb_image.h -extern/stb_image_write.h -extern/vk-bootstrap/ -extern/FidelityFX-FSR2/ -extern/FidelityFX-SDK/ diff --git a/BUILD_INSTRUCTIONS.md b/BUILD_INSTRUCTIONS.md index c06296d8..207fbbcd 100644 --- a/BUILD_INSTRUCTIONS.md +++ b/BUILD_INSTRUCTIONS.md @@ -12,7 +12,7 @@ This document provides platform-specific build instructions for WoWee. sudo apt update sudo apt install -y \ build-essential cmake pkg-config git \ - libsdl2-dev libglm-dev \ + libsdl2-dev libglew-dev libglm-dev \ libssl-dev zlib1g-dev \ libvulkan-dev vulkan-tools glslc \ libavcodec-dev libavformat-dev libavutil-dev libswscale-dev \ @@ -28,7 +28,7 @@ sudo apt install -y \ ```bash sudo pacman -S --needed \ base-devel cmake pkgconf git \ - sdl2 glm openssl zlib \ + sdl2 glew glm openssl zlib \ vulkan-headers vulkan-icd-loader vulkan-tools shaderc \ ffmpeg unicorn stormlib ``` @@ -83,7 +83,7 @@ Vulkan on macOS is provided via MoltenVK (a Vulkan-to-Metal translation layer), which is included in the `vulkan-loader` Homebrew package. ```bash -brew install cmake pkg-config sdl2 glm openssl@3 zlib ffmpeg unicorn \ +brew install cmake pkg-config sdl2 glew glm openssl@3 zlib ffmpeg unicorn \ stormlib vulkan-loader vulkan-headers shaderc ``` @@ -137,6 +137,7 @@ pacman -S --needed \ mingw-w64-x86_64-ninja \ mingw-w64-x86_64-pkgconf \ mingw-w64-x86_64-SDL2 \ + mingw-w64-x86_64-glew \ mingw-w64-x86_64-glm \ mingw-w64-x86_64-openssl \ mingw-w64-x86_64-zlib \ @@ -173,7 +174,7 @@ For users who prefer Visual Studio over MSYS2. ### vcpkg Dependencies ```powershell -vcpkg install sdl2 glm openssl zlib ffmpeg stormlib --triplet x64-windows +vcpkg install sdl2 glew glm openssl zlib ffmpeg stormlib --triplet x64-windows ``` ### Clone diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 866f0918..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,85 +0,0 @@ -# Changelog - -## [Unreleased] — changes since v1.8.9-preview - -### Architecture -- Break Application::getInstance() singleton from GameHandler via GameServices struct -- EntityController refactoring (SOLID decomposition) -- Extract 8 domain handler classes from GameHandler -- Replace 3,300-line switch with dispatch table -- Multi-platform Docker build system (Linux, macOS arm64/x86_64, Windows cross-compilation) - -### Bug Fixes (v1.8.2–v1.8.9) -- Fix VkTexture ownsSampler_ flag after move/destroy (prevented double-free) -- Fix unsigned underflow in Warden PE section loading (buffer overflow on malformed modules) -- Add bounds checks to Warden readLE32/readLE16 (out-of-bounds on untrusted PE data) -- Fix undefined behavior: SDL_BUTTON(0) computed 1 << -1 (negative shift) -- Fix BigNum::toHex/toDecimal null dereference on OpenSSL allocation failure -- Remove duplicate zone weather entry silently overwriting Dustwallow Marsh -- Fix LLVM apt repo codename (jammy→noble) in macOS Docker build -- Add missing mkdir in Linux Docker build script -- Clamp player percentage stats (block/dodge/parry/crit) to prevent NaN from corrupted packets -- Guard fsPath underflow in tryLoadPngOverride - -### Code Quality (v1.8.2–v1.8.9) -- 30+ named constants replacing magic numbers across game, rendering, and pipeline code -- 55+ why-comments documenting WoW protocol quirks, format specifics, and design rationale -- 8 DRY extractions (findOnUseSpellId, createFallbackTextures, finalizeSampler, - renderClassRestriction/renderRaceRestriction, and more) -- Scope macOS -undefined dynamic_lookup linker flag to wowee target only -- Replace goto patterns with structured control flow (do/while(false), lambdas) -- Zero out GameServices in Application::shutdown to prevent dangling pointers - ---- - -## [v1.8.1-preview] — 2026-03-23 - -### Performance -- Eliminate ~70 unnecessary sqrt ops per frame; constexpr reciprocals and cache optimizations -- Skip bone animation for LOD3 models; frustum-cull water surfaces -- Eliminate per-frame heap allocations in M2 renderer -- Convert entity/skill/DBC/warden maps to unordered_map; fix 3x contacts scan -- Eliminate double map lookups and dynamic_cast in render loops -- Use second GPU queue for parallel texture/buffer uploads -- Time-budget tile finalization to prevent 1+ second main-loop stalls -- Add Vulkan pipeline cache persistence for faster startup - -### Bug Fixes -- Fix spline parsing with expansion context; preload DBC caches at world entry -- Fix NPC/player attack animation to use weapon-appropriate anim ID -- Fix equipment visibility and follow-target run speed -- Fix inspect (packed GUID) and client-side auto-walk for follow -- Fix mail money uint64, other-player cape textures, zone toast dedup, TCP_NODELAY -- Guard spline point loop against unsigned underflow; guard hexDecode/stoi/stof -- Fix infinite recursion in toLowerInPlace and operator precedence bugs -- Fix 3D audio coords for PLAY_OBJECT_SOUND; correct melee swing sound paths -- Prevent Vulkan sampler exhaustion crash; skip pipeline cache on NVIDIA -- Skip FSR3 frame gen on non-AMD GPUs to prevent driver crash -- Fix chest GO interaction (send GAMEOBJ_USE+LOOT together) -- Restore WMO wall collision threshold; fix off-screen bag positions -- Guard texture log dedup sets with mutex for thread safety -- Fix lua_pcall return check in ACTIONBAR_PAGE_CHANGED - -### Features -- Render equipment on other players (helmets, weapons, belts, wrists, shoulders) -- Target frame right-click context menu -- Crafting sounds and Create All button -- Server-synced bag sort -- Log GPU vendor/name at init - -### Security -- Add path traversal rejection and packet length validation - -### Code Quality -- Packet API: add readPackedGuid, writePackedGuid, writeFloat, getRemainingSize, - hasRemaining, hasData, skipAll (replacing 1300+ verbose expressions) -- GameHandler helpers: isInWorld, isPreWotlk, guidToUnitId, lookupName, - getUnitByGuid, fireAddonEvent, withSoundManager -- Dispatch table: registerHandler, registerSkipHandler, registerWorldHandler, - registerErrorHandler (replacing 120+ lambda wrappers) -- Shared ui_colors.hpp with named constants replacing 200+ inline color literals -- Promote 50+ static const arrays to constexpr across audio/core/rendering/UI -- Deduplicate class name/color functions, enchantment cache, item-set DBC keys -- Extract settings tabs, GameHandler::update() phases, loadWeaponM2 into methods -- Remove 12 duplicate dispatch registrations and C-style casts -- Extract toHexString, toLowerInPlace, duration formatting, Lua return helpers diff --git a/CMakeLists.txt b/CMakeLists.txt index 84e84b62..54f39283 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.15) -project(wowee VERSION 1.0.0 LANGUAGES C CXX) +project(wowee VERSION 1.0.0 LANGUAGES CXX) include(GNUInstallDirs) set(CMAKE_CXX_STANDARD 20) @@ -25,9 +25,8 @@ endif() # Options option(BUILD_SHARED_LIBS "Build shared libraries" OFF) -option(WOWEE_BUILD_TESTS "Build tests" ON) +option(WOWEE_BUILD_TESTS "Build tests" OFF) option(WOWEE_ENABLE_ASAN "Enable AddressSanitizer (Debug builds)" OFF) -option(WOWEE_ENABLE_TRACY "Enable Tracy profiler instrumentation" OFF) option(WOWEE_ENABLE_AMD_FSR2 "Enable AMD FidelityFX FSR2 backend when SDK is present" ON) option(WOWEE_ENABLE_AMD_FSR3_FRAMEGEN "Enable AMD FidelityFX SDK FSR3 frame generation interface probe when SDK is present" ON) option(WOWEE_BUILD_AMD_FSR3_RUNTIME "Build native AMD FidelityFX VK runtime (Path A) from extern/FidelityFX-SDK/Kits" ON) @@ -249,98 +248,34 @@ endif() find_package(SDL2 REQUIRED) find_package(Vulkan QUIET) if(NOT Vulkan_FOUND) - # For Windows cross-compilation the host pkg-config finds the Linux libvulkan-dev - # and injects /usr/include as an INTERFACE_INCLUDE_DIRECTORY, which causes - # MinGW clang to pull in glibc headers (bits/libc-header-start.h) instead of - # the MinGW sysroot headers. Skip the host pkg-config path entirely and instead - # locate Vulkan via vcpkg-installed vulkan-headers or the MinGW toolchain. - if(CMAKE_CROSSCOMPILING AND WIN32) - # The cross-compile build script generates a Vulkan import library - # (libvulkan-1.a) in ${CMAKE_BINARY_DIR}/vulkan-import from the headers. - set(_VULKAN_IMPORT_DIR "${CMAKE_BINARY_DIR}/vulkan-import") - - find_package(VulkanHeaders CONFIG QUIET) - if(VulkanHeaders_FOUND) - if(NOT TARGET Vulkan::Vulkan) - add_library(Vulkan::Vulkan INTERFACE IMPORTED) - endif() - # Vulkan::Headers is provided by vcpkg's vulkan-headers port and carries - # the correct MinGW include path — no Linux system headers involved. - set_property(TARGET Vulkan::Vulkan APPEND PROPERTY - INTERFACE_LINK_LIBRARIES Vulkan::Headers) - # Link against the Vulkan loader import library (vulkan-1.dll). - if(EXISTS "${_VULKAN_IMPORT_DIR}/libvulkan-1.a") + # Fallback: some distros / CMake versions need pkg-config to locate Vulkan. + find_package(PkgConfig QUIET) + if(PkgConfig_FOUND) + pkg_check_modules(VULKAN_PKG vulkan) + if(VULKAN_PKG_FOUND) + add_library(Vulkan::Vulkan INTERFACE IMPORTED) + set_target_properties(Vulkan::Vulkan PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${VULKAN_PKG_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${VULKAN_PKG_LIBRARIES}" + ) + if(VULKAN_PKG_LIBRARY_DIRS) set_property(TARGET Vulkan::Vulkan APPEND PROPERTY - INTERFACE_LINK_DIRECTORIES "${_VULKAN_IMPORT_DIR}") - set_property(TARGET Vulkan::Vulkan APPEND PROPERTY - INTERFACE_LINK_LIBRARIES vulkan-1) + INTERFACE_LINK_DIRECTORIES "${VULKAN_PKG_LIBRARY_DIRS}") endif() set(Vulkan_FOUND TRUE) - message(STATUS "Found Vulkan headers for Windows cross-compile via vcpkg VulkanHeaders") - else() - # Last-resort: check the LLVM-MinGW toolchain sysroot directly. - find_path(_VULKAN_MINGW_INCLUDE NAMES vulkan/vulkan.h - PATHS /opt/llvm-mingw/x86_64-w64-mingw32/include NO_DEFAULT_PATH) - if(_VULKAN_MINGW_INCLUDE) - if(NOT TARGET Vulkan::Vulkan) - add_library(Vulkan::Vulkan INTERFACE IMPORTED) - endif() - set_target_properties(Vulkan::Vulkan PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_VULKAN_MINGW_INCLUDE}") - # Link against the Vulkan loader import library (vulkan-1.dll). - if(EXISTS "${_VULKAN_IMPORT_DIR}/libvulkan-1.a") - set_property(TARGET Vulkan::Vulkan APPEND PROPERTY - INTERFACE_LINK_DIRECTORIES "${_VULKAN_IMPORT_DIR}") - set_property(TARGET Vulkan::Vulkan APPEND PROPERTY - INTERFACE_LINK_LIBRARIES vulkan-1) - endif() - set(Vulkan_FOUND TRUE) - message(STATUS "Found Vulkan headers in LLVM-MinGW sysroot: ${_VULKAN_MINGW_INCLUDE}") - endif() - endif() - elseif(CMAKE_CROSSCOMPILING AND CMAKE_SYSTEM_NAME STREQUAL "Darwin") - # macOS cross-compilation: use vcpkg-installed vulkan-headers. - # The host pkg-config would find Linux libvulkan-dev headers which the - # macOS cross-compiler cannot use (different sysroot). - find_package(VulkanHeaders CONFIG QUIET) - if(VulkanHeaders_FOUND) - if(NOT TARGET Vulkan::Vulkan) - add_library(Vulkan::Vulkan INTERFACE IMPORTED) - endif() - set_property(TARGET Vulkan::Vulkan APPEND PROPERTY - INTERFACE_LINK_LIBRARIES Vulkan::Headers) - set(Vulkan_FOUND TRUE) - message(STATUS "Found Vulkan headers for macOS cross-compile via vcpkg VulkanHeaders") - endif() - else() - # Fallback: some distros / CMake versions need pkg-config to locate Vulkan. - find_package(PkgConfig QUIET) - if(PkgConfig_FOUND) - pkg_check_modules(VULKAN_PKG vulkan) - if(VULKAN_PKG_FOUND) - add_library(Vulkan::Vulkan INTERFACE IMPORTED) - set_target_properties(Vulkan::Vulkan PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${VULKAN_PKG_INCLUDE_DIRS}" - INTERFACE_LINK_LIBRARIES "${VULKAN_PKG_LIBRARIES}" - ) - if(VULKAN_PKG_LIBRARY_DIRS) - set_property(TARGET Vulkan::Vulkan APPEND PROPERTY - INTERFACE_LINK_DIRECTORIES "${VULKAN_PKG_LIBRARY_DIRS}") - endif() - set(Vulkan_FOUND TRUE) - message(STATUS "Found Vulkan via pkg-config: ${VULKAN_PKG_LIBRARIES}") - endif() + message(STATUS "Found Vulkan via pkg-config: ${VULKAN_PKG_LIBRARIES}") endif() endif() if(NOT Vulkan_FOUND) message(FATAL_ERROR "Could not find Vulkan. Install libvulkan-dev (Linux), vulkan-loader (macOS), or the Vulkan SDK (Windows).") endif() endif() -# macOS cross-compilation: the Vulkan loader (MoltenVK) is not available at link -# time. Allow unresolved Vulkan symbols — they are resolved at runtime. -if(CMAKE_CROSSCOMPILING AND CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(WOWEE_MACOS_CROSS_COMPILE TRUE) -endif() +# GL/GLEW kept temporarily for unconverted sub-renderers during Vulkan migration. +# These files compile against GL types but their code is never called — the Vulkan +# path is the only active rendering backend. Remove in Phase 7 when all renderers +# are converted and grep confirms zero GL references. +find_package(OpenGL QUIET) +find_package(GLEW QUIET) find_package(OpenSSL REQUIRED) find_package(Threads REQUIRED) find_package(ZLIB REQUIRED) @@ -487,9 +422,6 @@ endif() set(WOWEE_SOURCES # Core src/core/application.cpp - src/core/entity_spawner.cpp - src/core/appearance_composer.cpp - src/core/world_loader.cpp src/core/window.cpp src/core/input.cpp src/core/logger.cpp @@ -518,15 +450,6 @@ set(WOWEE_SOURCES src/game/opcode_table.cpp src/game/update_field_table.cpp src/game/game_handler.cpp - src/game/chat_handler.cpp - src/game/movement_handler.cpp - src/game/combat_handler.cpp - src/game/spell_handler.cpp - src/game/inventory_handler.cpp - src/game/social_handler.cpp - src/game/quest_handler.cpp - src/game/entity_controller.cpp - src/game/warden_handler.cpp src/game/warden_crypto.cpp src/game/warden_module.cpp src/game/warden_emulator.cpp @@ -545,7 +468,6 @@ set(WOWEE_SOURCES # Audio src/audio/audio_engine.cpp - src/audio/audio_coordinator.cpp src/audio/music_manager.cpp src/audio/footstep_manager.cpp src/audio/activity_sound_manager.cpp @@ -583,9 +505,12 @@ set(WOWEE_SOURCES # Rendering src/rendering/renderer.cpp src/rendering/amd_fsr3_runtime.cpp + src/rendering/shader.cpp + src/rendering/mesh.cpp src/rendering/camera.cpp src/rendering/camera_controller.cpp src/rendering/material.cpp + src/rendering/scene.cpp src/rendering/terrain_renderer.cpp src/rendering/terrain_manager.cpp src/rendering/frustum.cpp @@ -604,8 +529,6 @@ set(WOWEE_SOURCES src/rendering/character_preview.cpp src/rendering/wmo_renderer.cpp src/rendering/m2_renderer.cpp - src/rendering/m2_model_classifier.cpp - src/rendering/render_graph.cpp src/rendering/quest_marker_renderer.cpp src/rendering/minimap.cpp src/rendering/world_map.cpp @@ -613,21 +536,8 @@ set(WOWEE_SOURCES src/rendering/mount_dust.cpp src/rendering/levelup_effect.cpp src/rendering/charge_effect.cpp - src/rendering/spell_visual_system.cpp - src/rendering/post_process_pipeline.cpp - src/rendering/animation_controller.cpp - src/rendering/animation/animation_ids.cpp - src/rendering/animation/emote_registry.cpp - src/rendering/animation/footstep_driver.cpp - src/rendering/animation/sfx_state_driver.cpp - src/rendering/animation/anim_capability_probe.cpp - src/rendering/animation/locomotion_fsm.cpp - src/rendering/animation/combat_fsm.cpp - src/rendering/animation/activity_fsm.cpp - src/rendering/animation/mount_fsm.cpp - src/rendering/animation/character_animator.cpp # Renamed from player_animator.cpp; npc_animator.cpp removed - src/rendering/animation/animation_manager.cpp src/rendering/loading_screen.cpp + $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/rendering/video_player.cpp> # UI src/ui/ui_manager.cpp @@ -636,31 +546,10 @@ set(WOWEE_SOURCES src/ui/character_create_screen.cpp src/ui/character_screen.cpp src/ui/game_screen.cpp - src/ui/chat_panel.cpp - src/ui/toast_manager.cpp - src/ui/dialog_manager.cpp - src/ui/settings_panel.cpp - src/ui/combat_ui.cpp - src/ui/social_panel.cpp - src/ui/action_bar_panel.cpp - src/ui/window_manager.cpp src/ui/inventory_screen.cpp src/ui/quest_log_screen.cpp src/ui/spellbook_screen.cpp src/ui/talent_screen.cpp - src/ui/keybinding_manager.cpp - - # Addons - src/addons/addon_manager.cpp - src/addons/lua_engine.cpp - src/addons/lua_unit_api.cpp - src/addons/lua_spell_api.cpp - src/addons/lua_inventory_api.cpp - src/addons/lua_quest_api.cpp - src/addons/lua_social_api.cpp - src/addons/lua_system_api.cpp - src/addons/lua_action_api.cpp - src/addons/toc_parser.cpp # Main src/main.cpp @@ -729,9 +618,12 @@ set(WOWEE_HEADERS include/rendering/vk_pipeline.hpp include/rendering/vk_render_target.hpp include/rendering/renderer.hpp + include/rendering/shader.hpp + include/rendering/mesh.hpp include/rendering/camera.hpp include/rendering/camera_controller.hpp include/rendering/material.hpp + include/rendering/scene.hpp include/rendering/terrain_renderer.hpp include/rendering/terrain_manager.hpp include/rendering/frustum.hpp @@ -750,6 +642,7 @@ set(WOWEE_HEADERS include/rendering/character_preview.hpp include/rendering/wmo_renderer.hpp include/rendering/loading_screen.hpp + include/rendering/video_player.hpp include/ui/ui_manager.hpp include/ui/auth_screen.hpp @@ -760,64 +653,24 @@ set(WOWEE_HEADERS include/ui/inventory_screen.hpp include/ui/spellbook_screen.hpp include/ui/talent_screen.hpp - include/ui/keybinding_manager.hpp ) set(WOWEE_PLATFORM_SOURCES) if(WIN32) - # Copy icon into build tree so windres can find it via the relative path - # in wowee.rc ("assets\\wowee.ico"). Tell the RC compiler to also search - # the build directory — GNU windres uses cwd (already the build dir) but - # llvm-windres resolves relative to the .rc file, so it needs the hint. + # Copy icon into build tree so llvm-rc can find it via the relative path in wowee.rc configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/assets/Wowee.ico ${CMAKE_CURRENT_BINARY_DIR}/assets/wowee.ico COPYONLY ) - set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -I ${CMAKE_CURRENT_BINARY_DIR} -I ${CMAKE_CURRENT_SOURCE_DIR}") list(APPEND WOWEE_PLATFORM_SOURCES resources/wowee.rc) endif() -# ---- Lua 5.1.5 (vendored, static library) ---- -set(LUA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/extern/lua-5.1.5/src) -set(LUA_SOURCES - ${LUA_DIR}/lapi.c ${LUA_DIR}/lcode.c ${LUA_DIR}/ldebug.c - ${LUA_DIR}/ldo.c ${LUA_DIR}/ldump.c ${LUA_DIR}/lfunc.c - ${LUA_DIR}/lgc.c ${LUA_DIR}/llex.c ${LUA_DIR}/lmem.c - ${LUA_DIR}/lobject.c ${LUA_DIR}/lopcodes.c ${LUA_DIR}/lparser.c - ${LUA_DIR}/lstate.c ${LUA_DIR}/lstring.c ${LUA_DIR}/ltable.c - ${LUA_DIR}/ltm.c ${LUA_DIR}/lundump.c ${LUA_DIR}/lvm.c - ${LUA_DIR}/lzio.c ${LUA_DIR}/lauxlib.c ${LUA_DIR}/lbaselib.c - ${LUA_DIR}/ldblib.c ${LUA_DIR}/liolib.c ${LUA_DIR}/lmathlib.c - ${LUA_DIR}/loslib.c ${LUA_DIR}/ltablib.c ${LUA_DIR}/lstrlib.c - ${LUA_DIR}/linit.c -) -add_library(lua51 STATIC ${LUA_SOURCES}) -set_target_properties(lua51 PROPERTIES LINKER_LANGUAGE C C_STANDARD 99 POSITION_INDEPENDENT_CODE ON) -target_include_directories(lua51 PUBLIC ${LUA_DIR}) -if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") - target_compile_options(lua51 PRIVATE -w) -endif() - # Create executable add_executable(wowee ${WOWEE_SOURCES} ${WOWEE_HEADERS} ${WOWEE_PLATFORM_SOURCES}) - -# Tracy profiler — zero overhead when WOWEE_ENABLE_TRACY is OFF -if(WOWEE_ENABLE_TRACY) - target_sources(wowee PRIVATE ${CMAKE_SOURCE_DIR}/extern/tracy/public/TracyClient.cpp) - target_compile_definitions(wowee PRIVATE TRACY_ENABLE) - target_include_directories(wowee SYSTEM PRIVATE ${CMAKE_SOURCE_DIR}/extern/tracy/public) - message(STATUS "Tracy profiler: ENABLED") -endif() - if(TARGET opcodes-generate) add_dependencies(wowee opcodes-generate) endif() -# macOS cross-compilation: MoltenVK is not available at link time. -# Allow unresolved Vulkan symbols — resolved at runtime. Scoped to wowee only. -if(WOWEE_MACOS_CROSS_COMPILE) - target_link_options(wowee PRIVATE "-undefined" "dynamic_lookup") -endif() # FidelityFX-SDK headers can trigger compiler-specific pragma/unused-static noise # when included through the runtime bridge; keep suppression scoped to that TU. @@ -854,10 +707,17 @@ target_link_libraries(wowee PRIVATE OpenSSL::Crypto Threads::Threads ZLIB::ZLIB - lua51 ${CMAKE_DL_LIBS} ) +# GL/GLEW linked temporarily for unconverted sub-renderers (removed in Phase 7) +if(TARGET OpenGL::GL) + target_link_libraries(wowee PRIVATE OpenGL::GL) +endif() +if(TARGET GLEW::GLEW) + target_link_libraries(wowee PRIVATE GLEW::GLEW) +endif() + if(HAVE_FFMPEG) target_compile_definitions(wowee PRIVATE HAVE_FFMPEG) target_link_libraries(wowee PRIVATE ${FFMPEG_LIBRARIES}) @@ -947,20 +807,12 @@ else() # -g3 — maximum DWARF debug info (includes macro definitions) # -Og — optimise for debugging (better than -O0, keeps most frames) # -fno-omit-frame-pointer — preserve frame pointers so stack traces are clean - # Frame pointers in all configs (negligible perf cost, critical for crash backtraces) target_compile_options(wowee PRIVATE - -fno-omit-frame-pointer - $<$:-g3 -Og> - $<$:-g> + $<$:-g3 -Og -fno-omit-frame-pointer> + $<$:-g -fno-omit-frame-pointer> ) endif() -# ── Unit tests (Catch2) ────────────────────────────────────── -if(WOWEE_BUILD_TESTS) - enable_testing() - add_subdirectory(tests) -endif() - # AddressSanitizer — catch buffer overflows, use-after-free, etc. # Enable with: cmake ... -DWOWEE_ENABLE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug if(WOWEE_ENABLE_ASAN) @@ -972,10 +824,10 @@ if(WOWEE_ENABLE_ASAN) $<$:/MD> ) else() - target_compile_options(wowee PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer) - target_link_options(wowee PRIVATE -fsanitize=address,undefined) + target_compile_options(wowee PRIVATE -fsanitize=address -fno-omit-frame-pointer) + target_link_options(wowee PRIVATE -fsanitize=address) endif() - message(STATUS "AddressSanitizer + UBSan: ENABLED") + message(STATUS "AddressSanitizer: ENABLED") endif() # Release build optimizations @@ -1002,17 +854,6 @@ add_custom_command(TARGET wowee POST_BUILD COMMENT "Syncing assets to $/assets" ) -# Symlink Data/ next to the executable so expansion profiles, opcode tables, -# and other runtime data files are found when running from the build directory. -if(NOT WIN32) - add_custom_command(TARGET wowee POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink - ${CMAKE_CURRENT_SOURCE_DIR}/Data - $/Data - COMMENT "Symlinking Data to $/Data" - ) -endif() - # On Windows, SDL 2.28+ uses LoadLibraryExW with LOAD_LIBRARY_SEARCH_DEFAULT_DIRS # which does NOT include System32. Copy vulkan-1.dll into the output directory so # SDL_Vulkan_LoadLibrary can locate it without needing a full system PATH search. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index aac6cdf0..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,72 +0,0 @@ -# Contributing to Wowee - -## Build Setup - -See [BUILD_INSTRUCTIONS.md](BUILD_INSTRUCTIONS.md) for full platform-specific details. -The short version: CMake + Make on Linux/macOS, MSYS2 on Windows. - -``` -cmake -B build -DCMAKE_BUILD_TYPE=Debug -make -C build -j$(nproc) -``` - -## Code Style - -- **C++20**. Use `#pragma once` for include guards. -- Namespaces: `wowee::game`, `wowee::rendering`, `wowee::ui`, `wowee::core`, `wowee::network`. -- Conventional commit messages in imperative mood: - - `feat:` new feature - - `fix:` bug fix - - `refactor:` code restructuring with no behavior change - - `perf:` performance improvement -- Prefer `constexpr` over `static const` for compile-time data. -- Mark functions whose return value should not be ignored with `[[nodiscard]]`. - -## Pull Request Process - -1. Branch from `master`. -2. Keep commits focused -- one logical change per commit. -3. Describe *what* changed and *why* in the PR description. -4. Ensure the project compiles cleanly before submitting. -5. Manual testing against a WoW 3.3.5a server (e.g. AzerothCore/ChromieCraft) is expected - for gameplay-affecting changes. - -## Architecture Overview - -See [docs/architecture.md](docs/architecture.md) for the full picture. Key namespaces: - -| Namespace | Responsibility | -|---|---| -| `wowee::game` | Game state, packet handling (`GameHandler`), opcode dispatch | -| `wowee::rendering` | Vulkan renderer, M2/WMO/terrain, sky system | -| `wowee::ui` | ImGui windows and HUD (`GameScreen`) | -| `wowee::core` | Coordinates, math, utilities | -| `wowee::network` | Connection, `Packet` read/write API | - -## Packet Handlers - -The standard pattern for adding a new server packet handler: - -1. Define a `struct FooData` holding the parsed fields. -2. Write `void GameHandler::handleFoo(network::Packet& packet)` to parse into `FooData`. -3. Register it in the dispatch table: `registerHandler(LogicalOpcode::SMSG_FOO, &GameHandler::handleFoo)`. - -Helper variants: `registerWorldHandler` (requires `isInWorld()`), `registerSkipHandler` (discard), -`registerErrorHandler` (log warning). - -## Testing - -There is no automated test suite. Changes are verified by manual testing against -WoW 3.3.5a private servers (primarily ChromieCraft/AzerothCore). Classic and TBC -expansion paths are tested against their respective server builds. - -## Key Files for New Contributors - -| File | What it does | -|---|---| -| `include/game/game_handler.hpp` | Central game state and all packet handler declarations | -| `src/game/game_handler.cpp` | Packet dispatch registration and handler implementations | -| `include/network/packet.hpp` | `Packet` class -- the read/write API every handler uses | -| `include/ui/game_screen.hpp` | Main gameplay UI screen (ImGui) | -| `src/rendering/m2_renderer.cpp` | M2 model loading and rendering | -| `docs/architecture.md` | High-level system architecture reference | diff --git a/Data/expansions/classic/dbc_layouts.json b/Data/expansions/classic/dbc_layouts.json index 459c9046..4ec229d5 100644 --- a/Data/expansions/classic/dbc_layouts.json +++ b/Data/expansions/classic/dbc_layouts.json @@ -1,260 +1,96 @@ { - "AreaTable": { - "ExploreFlag": 3, - "ID": 0, - "MapID": 1, - "ParentAreaNum": 2 - }, - "CharHairGeosets": { - "GeosetID": 4, - "RaceID": 1, - "SexID": 2, - "Variation": 3 - }, - "CharSections": { - "BaseSection": 3, - "ColorIndex": 5, - "Flags": 9, - "RaceID": 1, - "SexID": 2, - "Texture1": 6, - "Texture2": 7, - "Texture3": 8, - "VariationIndex": 4 - }, - "CharacterFacialHairStyles": { - "Geoset100": 3, - "Geoset200": 5, - "Geoset300": 4, - "RaceID": 0, - "SexID": 1, - "Variation": 2 - }, - "CreatureDisplayInfo": { - "ExtraDisplayId": 3, - "ID": 0, - "ModelID": 1, - "Skin1": 6, - "Skin2": 7, - "Skin3": 8 - }, - "CreatureDisplayInfoExtra": { - "BakeName": 20, - "EquipDisplay0": 8, - "EquipDisplay1": 9, - "EquipDisplay10": 18, - "EquipDisplay2": 10, - "EquipDisplay3": 11, - "EquipDisplay4": 12, - "EquipDisplay5": 13, - "EquipDisplay6": 14, - "EquipDisplay7": 15, - "EquipDisplay8": 16, - "EquipDisplay9": 17, - "FaceID": 4, - "FacialHairID": 7, - "HairColorID": 6, - "HairStyleID": 5, - "ID": 0, - "RaceID": 1, - "SexID": 2, - "SkinID": 3 - }, - "CreatureModelData": { - "ID": 0, - "ModelPath": 2 - }, - "Emotes": { - "AnimID": 2, - "ID": 0 - }, - "EmotesText": { - "Command": 1, - "EmoteRef": 2, - "ID": 0, - "OthersNoTargetTextID": 7, - "OthersTargetTextID": 3, - "SenderNoTargetTextID": 9, - "SenderTargetTextID": 5 - }, - "EmotesTextData": { - "ID": 0, - "Text": 1 - }, - "Faction": { - "ID": 0, - "ReputationBase0": 10, - "ReputationBase1": 11, - "ReputationBase2": 12, - "ReputationBase3": 13, - "ReputationRaceMask0": 2, - "ReputationRaceMask1": 3, - "ReputationRaceMask2": 4, - "ReputationRaceMask3": 5 - }, - "FactionTemplate": { - "Enemy0": 6, - "Enemy1": 7, - "Enemy2": 8, - "Enemy3": 9, - "EnemyGroup": 5, - "Faction": 1, - "FactionGroup": 3, - "FriendGroup": 4, - "ID": 0 - }, - "GameObjectDisplayInfo": { - "ID": 0, - "ModelName": 1 + "Spell": { + "ID": 0, "Attributes": 5, "IconID": 117, + "Name": 120, "Tooltip": 147, "Rank": 129, "SchoolEnum": 1 }, "ItemDisplayInfo": { - "GeosetGroup1": 7, - "GeosetGroup3": 9, - "ID": 0, - "InventoryIcon": 5, - "LeftModel": 1, - "LeftModelTexture": 3, - "TextureArmLower": 15, - "TextureArmUpper": 14, - "TextureFoot": 21, - "TextureHand": 16, - "TextureLegLower": 20, - "TextureLegUpper": 19, - "TextureTorsoLower": 18, - "TextureTorsoUpper": 17 + "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, + "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9, + "TextureArmUpper": 14, "TextureArmLower": 15, "TextureHand": 16, + "TextureTorsoUpper": 17, "TextureTorsoLower": 18, + "TextureLegUpper": 19, "TextureLegLower": 20, "TextureFoot": 21 }, - "Light": { - "ID": 0, - "InnerRadius": 5, - "LightParamsID": 7, - "LightParamsIDRain": 8, - "LightParamsIDUnderwater": 9, - "MapID": 1, - "OuterRadius": 6, - "X": 2, - "Y": 4, - "Z": 3 + "CharSections": { + "RaceID": 1, "SexID": 2, "BaseSection": 3, + "VariationIndex": 4, "ColorIndex": 5, + "Texture1": 6, "Texture2": 7, "Texture3": 8, + "Flags": 9 }, - "LightFloatBand": { - "BlockIndex": 1, - "NumKeyframes": 2, - "TimeKey0": 3, - "Value0": 19 + "SpellIcon": { "ID": 0, "Path": 1 }, + "FactionTemplate": { + "ID": 0, "Faction": 1, "FactionGroup": 3, + "FriendGroup": 4, "EnemyGroup": 5, + "Enemy0": 6, "Enemy1": 7, "Enemy2": 8, "Enemy3": 9 }, - "LightIntBand": { - "BlockIndex": 1, - "NumKeyframes": 2, - "TimeKey0": 3, - "Value0": 19 + "Faction": { + "ID": 0, "ReputationRaceMask0": 2, "ReputationRaceMask1": 3, + "ReputationRaceMask2": 4, "ReputationRaceMask3": 5, + "ReputationBase0": 10, "ReputationBase1": 11, + "ReputationBase2": 12, "ReputationBase3": 13 }, - "LightParams": { - "LightParamsID": 0 + "AreaTable": { "ID": 0, "ExploreFlag": 3 }, + "CreatureDisplayInfoExtra": { + "ID": 0, "RaceID": 1, "SexID": 2, "SkinID": 3, "FaceID": 4, + "HairStyleID": 5, "HairColorID": 6, "FacialHairID": 7, + "EquipDisplay0": 8, "EquipDisplay1": 9, "EquipDisplay2": 10, + "EquipDisplay3": 11, "EquipDisplay4": 12, "EquipDisplay5": 13, + "EquipDisplay6": 14, "EquipDisplay7": 15, "EquipDisplay8": 16, + "EquipDisplay9": 17, "EquipDisplay10": 18, "BakeName": 20 }, - "Map": { - "ID": 0, - "InternalName": 1 - }, - "SkillLine": { - "Category": 1, - "ID": 0, - "Name": 3 - }, - "SkillLineAbility": { - "SkillLineID": 1, - "SpellID": 2 - }, - "Spell": { - "Attributes": 5, - "AttributesEx": 6, - "CastingTimeIndex": 15, - "DispelType": 4, - "DurationIndex": 40, - "EffectBasePoints0": 80, - "EffectBasePoints1": 81, - "EffectBasePoints2": 82, - "ID": 0, - "IconID": 117, - "ManaCost": 29, - "Name": 120, - "PowerType": 28, - "RangeIndex": 33, - "Rank": 129, - "SchoolEnum": 1, - "Tooltip": 147 - }, - "SpellIcon": { - "ID": 0, - "Path": 1 - }, - "SpellRange": { - "MaxRange": 2 - }, - "SpellVisual": { - "CastKit": 2, - "ID": 0, - "ImpactKit": 3, - "MissileModel": 8 - }, - "SpellVisualEffectName": { - "FilePath": 2, - "ID": 0 - }, - "SpellVisualKit": { - "BaseEffect": 5, - "ID": 0, - "SpecialEffect0": 11, - "SpecialEffect1": 12, - "SpecialEffect2": 13 - }, - "Talent": { - "Column": 3, - "ID": 0, - "PrereqRank0": 12, - "PrereqTalent0": 9, - "RankSpell0": 4, - "Row": 2, - "TabID": 1 - }, - "TalentTab": { - "BackgroundFile": 15, - "ClassMask": 12, - "ID": 0, - "Name": 1, - "OrderIndex": 14 + "CreatureDisplayInfo": { + "ID": 0, "ModelID": 1, "ExtraDisplayId": 3, + "Skin1": 6, "Skin2": 7, "Skin3": 8 }, "TaxiNodes": { - "ID": 0, - "MapID": 1, - "Name": 5, - "X": 2, - "Y": 3, - "Z": 4 - }, - "TaxiPath": { - "Cost": 3, - "FromNode": 1, - "ID": 0, - "ToNode": 2 + "ID": 0, "MapID": 1, "X": 2, "Y": 3, "Z": 4, "Name": 5 }, + "TaxiPath": { "ID": 0, "FromNode": 1, "ToNode": 2, "Cost": 3 }, "TaxiPathNode": { - "ID": 0, - "MapID": 3, - "NodeIndex": 2, - "PathID": 1, - "X": 4, - "Y": 5, - "Z": 6 + "ID": 0, "PathID": 1, "NodeIndex": 2, "MapID": 3, + "X": 4, "Y": 5, "Z": 6 + }, + "TalentTab": { + "ID": 0, "Name": 1, "ClassMask": 12, + "OrderIndex": 14, "BackgroundFile": 15 + }, + "Talent": { + "ID": 0, "TabID": 1, "Row": 2, "Column": 3, + "RankSpell0": 4, "PrereqTalent0": 9, "PrereqRank0": 12 + }, + "SkillLineAbility": { "SkillLineID": 1, "SpellID": 2 }, + "SkillLine": { "ID": 0, "Category": 1, "Name": 3 }, + "Map": { "ID": 0, "InternalName": 1 }, + "CreatureModelData": { "ID": 0, "ModelPath": 2 }, + "CharHairGeosets": { + "RaceID": 1, "SexID": 2, "Variation": 3, "GeosetID": 4 + }, + "CharacterFacialHairStyles": { + "RaceID": 0, "SexID": 1, "Variation": 2, + "Geoset100": 3, "Geoset300": 4, "Geoset200": 5 + }, + "GameObjectDisplayInfo": { "ID": 0, "ModelName": 1 }, + "Emotes": { "ID": 0, "AnimID": 2 }, + "EmotesText": { + "ID": 0, "Command": 1, "EmoteRef": 2, + "OthersTargetTextID": 3, "SenderTargetTextID": 5, + "OthersNoTargetTextID": 7, "SenderNoTargetTextID": 9 + }, + "EmotesTextData": { "ID": 0, "Text": 1 }, + "Light": { + "ID": 0, "MapID": 1, "X": 2, "Z": 3, "Y": 4, + "InnerRadius": 5, "OuterRadius": 6, "LightParamsID": 7, + "LightParamsIDRain": 8, "LightParamsIDUnderwater": 9 + }, + "LightParams": { "LightParamsID": 0 }, + "LightIntBand": { + "BlockIndex": 1, "NumKeyframes": 2, "TimeKey0": 3, "Value0": 19 + }, + "LightFloatBand": { + "BlockIndex": 1, "NumKeyframes": 2, "TimeKey0": 3, "Value0": 19 }, "WorldMapArea": { - "AreaID": 2, - "AreaName": 3, - "DisplayMapID": 8, - "ID": 0, - "LocBottom": 7, - "LocLeft": 4, - "LocRight": 5, - "LocTop": 6, - "MapID": 1, - "ParentWorldMapID": 10 + "ID": 0, "MapID": 1, "AreaID": 2, "AreaName": 3, + "LocLeft": 4, "LocRight": 5, "LocTop": 6, "LocBottom": 7, + "DisplayMapID": 8, "ParentWorldMapID": 10 } } diff --git a/Data/expansions/classic/opcodes.json b/Data/expansions/classic/opcodes.json index 760647d9..b99e4223 100644 --- a/Data/expansions/classic/opcodes.json +++ b/Data/expansions/classic/opcodes.json @@ -273,7 +273,7 @@ "SMSG_INVENTORY_CHANGE_FAILURE": "0x112", "SMSG_OPEN_CONTAINER": "0x113", "CMSG_INSPECT": "0x114", - "SMSG_INSPECT_RESULTS_UPDATE": "0x115", + "SMSG_INSPECT": "0x115", "CMSG_INITIATE_TRADE": "0x116", "CMSG_BEGIN_TRADE": "0x117", "CMSG_BUSY_TRADE": "0x118", @@ -300,7 +300,7 @@ "CMSG_NEW_SPELL_SLOT": "0x12D", "CMSG_CAST_SPELL": "0x12E", "CMSG_CANCEL_CAST": "0x12F", - "SMSG_CAST_FAILED": "0x130", + "SMSG_CAST_RESULT": "0x130", "SMSG_SPELL_START": "0x131", "SMSG_SPELL_GO": "0x132", "SMSG_SPELL_FAILURE": "0x133", @@ -504,7 +504,8 @@ "CMSG_GM_SET_SECURITY_GROUP": "0x1F9", "CMSG_GM_NUKE": "0x1FA", "MSG_RANDOM_ROLL": "0x1FB", - "SMSG_ENVIRONMENTAL_DAMAGE_LOG": "0x1FC", + "SMSG_ENVIRONMENTALDAMAGELOG": "0x1FC", + "CMSG_RWHOIS_OBSOLETE": "0x1FD", "SMSG_RWHOIS": "0x1FE", "MSG_LOOKING_FOR_GROUP": "0x1FF", "CMSG_SET_LOOKING_FOR_GROUP": "0x200", @@ -527,6 +528,7 @@ "CMSG_GMTICKET_GETTICKET": "0x211", "SMSG_GMTICKET_GETTICKET": "0x212", "CMSG_UNLEARN_TALENTS": "0x213", + "SMSG_GAMEOBJECT_SPAWN_ANIM_OBSOLETE": "0x214", "SMSG_GAMEOBJECT_DESPAWN_ANIM": "0x215", "MSG_CORPSE_QUERY": "0x216", "CMSG_GMTICKET_DELETETICKET": "0x217", @@ -536,7 +538,7 @@ "SMSG_GMTICKET_SYSTEMSTATUS": "0x21B", "CMSG_SPIRIT_HEALER_ACTIVATE": "0x21C", "CMSG_SET_STAT_CHEAT": "0x21D", - "SMSG_QUEST_FORCE_REMOVE": "0x21E", + "SMSG_SET_REST_START": "0x21E", "CMSG_SKILL_BUY_STEP": "0x21F", "CMSG_SKILL_BUY_RANK": "0x220", "CMSG_XP_CHEAT": "0x221", @@ -569,6 +571,8 @@ "CMSG_BATTLEFIELD_LIST": "0x23C", "SMSG_BATTLEFIELD_LIST": "0x23D", "CMSG_BATTLEFIELD_JOIN": "0x23E", + "SMSG_BATTLEFIELD_WIN_OBSOLETE": "0x23F", + "SMSG_BATTLEFIELD_LOSE_OBSOLETE": "0x240", "CMSG_TAXICLEARNODE": "0x241", "CMSG_TAXIENABLENODE": "0x242", "CMSG_ITEM_TEXT_QUERY": "0x243", @@ -601,6 +605,7 @@ "SMSG_AUCTION_BIDDER_NOTIFICATION": "0x25E", "SMSG_AUCTION_OWNER_NOTIFICATION": "0x25F", "SMSG_PROCRESIST": "0x260", + "SMSG_STANDSTATE_CHANGE_FAILURE_OBSOLETE": "0x261", "SMSG_DISPEL_FAILED": "0x262", "SMSG_SPELLORDAMAGE_IMMUNE": "0x263", "CMSG_AUCTION_LIST_BIDDER_ITEMS": "0x264", @@ -688,8 +693,8 @@ "SMSG_SCRIPT_MESSAGE": "0x2B6", "SMSG_DUEL_COUNTDOWN": "0x2B7", "SMSG_AREA_TRIGGER_MESSAGE": "0x2B8", - "CMSG_SHOWING_HELM": "0x2B9", - "CMSG_SHOWING_CLOAK": "0x2BA", + "CMSG_TOGGLE_HELM": "0x2B9", + "CMSG_TOGGLE_CLOAK": "0x2BA", "SMSG_MEETINGSTONE_JOINFAILED": "0x2BB", "SMSG_PLAYER_SKINNED": "0x2BC", "SMSG_DURABILITY_DAMAGE_DEATH": "0x2BD", @@ -816,5 +821,6 @@ "SMSG_LOTTERY_RESULT_OBSOLETE": "0x337", "SMSG_CHARACTER_PROFILE": "0x338", "SMSG_CHARACTER_PROFILE_REALM_CONNECTED": "0x339", + "SMSG_UNK": "0x33A", "SMSG_DEFENSE_MESSAGE": "0x33B" } diff --git a/Data/expansions/classic/update_fields.json b/Data/expansions/classic/update_fields.json index 5b214f59..5f97f29f 100644 --- a/Data/expansions/classic/update_fields.json +++ b/Data/expansions/classic/update_fields.json @@ -1,50 +1,38 @@ { - "CONTAINER_FIELD_NUM_SLOTS": 48, - "CONTAINER_FIELD_SLOT_1": 50, - "GAMEOBJECT_DISPLAYID": 8, - "GAMEOBJECT_BYTES_1": 14, - "ITEM_FIELD_DURABILITY": 48, - "ITEM_FIELD_MAXDURABILITY": 49, - "ITEM_FIELD_STACK_COUNT": 14, "OBJECT_FIELD_ENTRY": 3, - "OBJECT_FIELD_SCALE_X": 4, - "PLAYER_BYTES": 191, - "PLAYER_BYTES_2": 192, - "PLAYER_END": 1282, - "PLAYER_EXPLORED_ZONES_START": 1111, - "PLAYER_FIELD_BANKBAG_SLOT_1": 612, - "PLAYER_FIELD_BANK_SLOT_1": 564, - "PLAYER_FIELD_COINAGE": 1176, - "PLAYER_FIELD_INV_SLOT_HEAD": 486, - "PLAYER_FIELD_PACK_SLOT_1": 532, - "PLAYER_FLAGS": 190, - "PLAYER_NEXT_LEVEL_XP": 717, - "PLAYER_QUEST_LOG_START": 198, - "PLAYER_REST_STATE_EXPERIENCE": 1175, - "PLAYER_SKILL_INFO_START": 718, - "PLAYER_XP": 716, - "UNIT_DYNAMIC_FLAGS": 143, - "UNIT_END": 188, - "UNIT_FIELD_AURAFLAGS": 98, - "UNIT_FIELD_AURAS": 50, + "UNIT_FIELD_TARGET_LO": 16, + "UNIT_FIELD_TARGET_HI": 17, "UNIT_FIELD_BYTES_0": 36, - "UNIT_FIELD_BYTES_1": 133, - "UNIT_FIELD_DISPLAYID": 131, - "UNIT_FIELD_FACTIONTEMPLATE": 35, - "UNIT_FIELD_FLAGS": 46, "UNIT_FIELD_HEALTH": 22, - "UNIT_FIELD_LEVEL": 34, + "UNIT_FIELD_POWER1": 23, "UNIT_FIELD_MAXHEALTH": 28, "UNIT_FIELD_MAXPOWER1": 29, + "UNIT_FIELD_LEVEL": 34, + "UNIT_FIELD_FACTIONTEMPLATE": 35, + "UNIT_FIELD_FLAGS": 46, + "UNIT_FIELD_DISPLAYID": 131, "UNIT_FIELD_MOUNTDISPLAYID": 133, - "UNIT_FIELD_POWER1": 23, + "UNIT_FIELD_AURAS": 50, + "UNIT_NPC_FLAGS": 147, + "UNIT_DYNAMIC_FLAGS": 143, "UNIT_FIELD_RESISTANCES": 154, - "UNIT_FIELD_STAT0": 138, - "UNIT_FIELD_STAT1": 139, - "UNIT_FIELD_STAT2": 140, - "UNIT_FIELD_STAT3": 141, - "UNIT_FIELD_STAT4": 142, - "UNIT_FIELD_TARGET_HI": 17, - "UNIT_FIELD_TARGET_LO": 16, - "UNIT_NPC_FLAGS": 147 + "UNIT_END": 188, + "PLAYER_FLAGS": 190, + "PLAYER_BYTES": 191, + "PLAYER_BYTES_2": 192, + "PLAYER_XP": 716, + "PLAYER_NEXT_LEVEL_XP": 717, + "PLAYER_FIELD_COINAGE": 1176, + "PLAYER_QUEST_LOG_START": 198, + "PLAYER_FIELD_INV_SLOT_HEAD": 486, + "PLAYER_FIELD_PACK_SLOT_1": 532, + "PLAYER_FIELD_BANK_SLOT_1": 564, + "PLAYER_FIELD_BANKBAG_SLOT_1": 612, + "PLAYER_SKILL_INFO_START": 718, + "PLAYER_EXPLORED_ZONES_START": 1111, + "PLAYER_END": 1282, + "GAMEOBJECT_DISPLAYID": 8, + "ITEM_FIELD_STACK_COUNT": 14, + "CONTAINER_FIELD_NUM_SLOTS": 48, + "CONTAINER_FIELD_SLOT_1": 50 } diff --git a/Data/expansions/tbc/dbc_layouts.json b/Data/expansions/tbc/dbc_layouts.json index e11682cf..d40a5766 100644 --- a/Data/expansions/tbc/dbc_layouts.json +++ b/Data/expansions/tbc/dbc_layouts.json @@ -1,307 +1,98 @@ { - "AreaTable": { - "ExploreFlag": 3, - "ID": 0, - "MapID": 1, - "ParentAreaNum": 2 - }, - "CharHairGeosets": { - "GeosetID": 4, - "RaceID": 1, - "SexID": 2, - "Variation": 3 - }, - "CharSections": { - "BaseSection": 3, - "ColorIndex": 5, - "Flags": 9, - "RaceID": 1, - "SexID": 2, - "Texture1": 6, - "Texture2": 7, - "Texture3": 8, - "VariationIndex": 4 - }, - "CharTitles": { - "ID": 0, - "Title": 2, - "TitleBit": 20 - }, - "CharacterFacialHairStyles": { - "Geoset100": 3, - "Geoset200": 5, - "Geoset300": 4, - "RaceID": 0, - "SexID": 1, - "Variation": 2 - }, - "CreatureDisplayInfo": { - "ExtraDisplayId": 3, - "ID": 0, - "ModelID": 1, - "Skin1": 6, - "Skin2": 7, - "Skin3": 8 - }, - "CreatureDisplayInfoExtra": { - "BakeName": 20, - "EquipDisplay0": 8, - "EquipDisplay1": 9, - "EquipDisplay10": 18, - "EquipDisplay2": 10, - "EquipDisplay3": 11, - "EquipDisplay4": 12, - "EquipDisplay5": 13, - "EquipDisplay6": 14, - "EquipDisplay7": 15, - "EquipDisplay8": 16, - "EquipDisplay9": 17, - "FaceID": 4, - "FacialHairID": 7, - "HairColorID": 6, - "HairStyleID": 5, - "ID": 0, - "RaceID": 1, - "SexID": 2, - "SkinID": 3 - }, - "CreatureModelData": { - "ID": 0, - "ModelPath": 2 - }, - "Emotes": { - "AnimID": 2, - "ID": 0 - }, - "EmotesText": { - "Command": 1, - "EmoteRef": 2, - "ID": 0, - "OthersNoTargetTextID": 7, - "OthersTargetTextID": 3, - "SenderNoTargetTextID": 9, - "SenderTargetTextID": 5 - }, - "EmotesTextData": { - "ID": 0, - "Text": 1 - }, - "Faction": { - "ID": 0, - "ReputationBase0": 10, - "ReputationBase1": 11, - "ReputationBase2": 12, - "ReputationBase3": 13, - "ReputationRaceMask0": 2, - "ReputationRaceMask1": 3, - "ReputationRaceMask2": 4, - "ReputationRaceMask3": 5 - }, - "FactionTemplate": { - "Enemy0": 6, - "Enemy1": 7, - "Enemy2": 8, - "Enemy3": 9, - "EnemyGroup": 5, - "Faction": 1, - "FactionGroup": 3, - "FriendGroup": 4, - "ID": 0 - }, - "GameObjectDisplayInfo": { - "ID": 0, - "ModelName": 1 + "Spell": { + "ID": 0, "Attributes": 5, "IconID": 124, + "Name": 127, "Tooltip": 154, "Rank": 136, "SchoolMask": 215 }, "ItemDisplayInfo": { - "GeosetGroup1": 7, - "GeosetGroup3": 9, - "ID": 0, - "InventoryIcon": 5, - "LeftModel": 1, - "LeftModelTexture": 3, - "TextureArmLower": 15, - "TextureArmUpper": 14, - "TextureFoot": 21, - "TextureHand": 16, - "TextureLegLower": 20, - "TextureLegUpper": 19, - "TextureTorsoLower": 18, - "TextureTorsoUpper": 17 + "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, + "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9, + "TextureArmUpper": 14, "TextureArmLower": 15, "TextureHand": 16, + "TextureTorsoUpper": 17, "TextureTorsoLower": 18, + "TextureLegUpper": 19, "TextureLegLower": 20, "TextureFoot": 21 }, - "ItemSet": { - "ID": 0, - "Item0": 18, - "Item1": 19, - "Item2": 20, - "Item3": 21, - "Item4": 22, - "Item5": 23, - "Item6": 24, - "Item7": 25, - "Item8": 26, - "Item9": 27, - "Name": 1, - "Spell0": 28, - "Spell1": 29, - "Spell2": 30, - "Spell3": 31, - "Spell4": 32, - "Spell5": 33, - "Spell6": 34, - "Spell7": 35, - "Spell8": 36, - "Spell9": 37, - "Threshold0": 38, - "Threshold1": 39, - "Threshold2": 40, - "Threshold3": 41, - "Threshold4": 42, - "Threshold5": 43, - "Threshold6": 44, - "Threshold7": 45, - "Threshold8": 46, - "Threshold9": 47 + "CharSections": { + "RaceID": 1, "SexID": 2, "BaseSection": 3, + "VariationIndex": 4, "ColorIndex": 5, + "Texture1": 6, "Texture2": 7, "Texture3": 8, + "Flags": 9 }, - "Light": { - "ID": 0, - "InnerRadius": 5, - "LightParamsID": 7, - "LightParamsIDRain": 8, - "LightParamsIDUnderwater": 9, - "MapID": 1, - "OuterRadius": 6, - "X": 2, - "Y": 4, - "Z": 3 + "SpellIcon": { "ID": 0, "Path": 1 }, + "FactionTemplate": { + "ID": 0, "Faction": 1, "FactionGroup": 3, + "FriendGroup": 4, "EnemyGroup": 5, + "Enemy0": 6, "Enemy1": 7, "Enemy2": 8, "Enemy3": 9 }, - "LightFloatBand": { - "BlockIndex": 1, - "NumKeyframes": 2, - "TimeKey0": 3, - "Value0": 19 + "Faction": { + "ID": 0, "ReputationRaceMask0": 2, "ReputationRaceMask1": 3, + "ReputationRaceMask2": 4, "ReputationRaceMask3": 5, + "ReputationBase0": 10, "ReputationBase1": 11, + "ReputationBase2": 12, "ReputationBase3": 13 }, - "LightIntBand": { - "BlockIndex": 1, - "NumKeyframes": 2, - "TimeKey0": 3, - "Value0": 19 + "AreaTable": { "ID": 0, "ExploreFlag": 3 }, + "CreatureDisplayInfoExtra": { + "ID": 0, "RaceID": 1, "SexID": 2, "SkinID": 3, "FaceID": 4, + "HairStyleID": 5, "HairColorID": 6, "FacialHairID": 7, + "EquipDisplay0": 8, "EquipDisplay1": 9, "EquipDisplay2": 10, + "EquipDisplay3": 11, "EquipDisplay4": 12, "EquipDisplay5": 13, + "EquipDisplay6": 14, "EquipDisplay7": 15, "EquipDisplay8": 16, + "EquipDisplay9": 17, "EquipDisplay10": 18, "BakeName": 20 }, - "LightParams": { - "LightParamsID": 0 - }, - "Map": { - "ID": 0, - "InternalName": 1 - }, - "SkillLine": { - "Category": 1, - "ID": 0, - "Name": 3 - }, - "SkillLineAbility": { - "SkillLineID": 1, - "SpellID": 2 - }, - "Spell": { - "Attributes": 5, - "AttributesEx": 6, - "CastingTimeIndex": 22, - "DispelType": 3, - "DurationIndex": 40, - "EffectBasePoints0": 80, - "EffectBasePoints1": 81, - "EffectBasePoints2": 82, - "ID": 0, - "IconID": 124, - "ManaCost": 36, - "Name": 127, - "PowerType": 35, - "RangeIndex": 40, - "Rank": 136, - "SchoolMask": 215, - "Tooltip": 154 - }, - "SpellIcon": { - "ID": 0, - "Path": 1 - }, - "SpellItemEnchantment": { - "ID": 0, - "Name": 8 - }, - "SpellRange": { - "MaxRange": 4 - }, - "SpellVisual": { - "CastKit": 2, - "ID": 0, - "ImpactKit": 3, - "MissileModel": 8 - }, - "SpellVisualEffectName": { - "FilePath": 2, - "ID": 0 - }, - "SpellVisualKit": { - "BaseEffect": 5, - "ID": 0, - "SpecialEffect0": 11, - "SpecialEffect1": 12, - "SpecialEffect2": 13 - }, - "Talent": { - "Column": 3, - "ID": 0, - "PrereqRank0": 12, - "PrereqTalent0": 9, - "RankSpell0": 4, - "Row": 2, - "TabID": 1 - }, - "TalentTab": { - "BackgroundFile": 15, - "ClassMask": 12, - "ID": 0, - "Name": 1, - "OrderIndex": 14 + "CreatureDisplayInfo": { + "ID": 0, "ModelID": 1, "ExtraDisplayId": 3, + "Skin1": 6, "Skin2": 7, "Skin3": 8 }, "TaxiNodes": { - "ID": 0, - "MapID": 1, - "MountDisplayIdAlliance": 14, - "MountDisplayIdAllianceFallback": 12, - "MountDisplayIdHorde": 15, - "MountDisplayIdHordeFallback": 13, - "Name": 5, - "X": 2, - "Y": 3, - "Z": 4 - }, - "TaxiPath": { - "Cost": 3, - "FromNode": 1, - "ID": 0, - "ToNode": 2 + "ID": 0, "MapID": 1, "X": 2, "Y": 3, "Z": 4, "Name": 5, + "MountDisplayIdAllianceFallback": 12, "MountDisplayIdHordeFallback": 13, + "MountDisplayIdAlliance": 14, "MountDisplayIdHorde": 15 }, + "TaxiPath": { "ID": 0, "FromNode": 1, "ToNode": 2, "Cost": 3 }, "TaxiPathNode": { - "ID": 0, - "MapID": 3, - "NodeIndex": 2, - "PathID": 1, - "X": 4, - "Y": 5, - "Z": 6 + "ID": 0, "PathID": 1, "NodeIndex": 2, "MapID": 3, + "X": 4, "Y": 5, "Z": 6 + }, + "TalentTab": { + "ID": 0, "Name": 1, "ClassMask": 12, + "OrderIndex": 14, "BackgroundFile": 15 + }, + "Talent": { + "ID": 0, "TabID": 1, "Row": 2, "Column": 3, + "RankSpell0": 4, "PrereqTalent0": 9, "PrereqRank0": 12 + }, + "SkillLineAbility": { "SkillLineID": 1, "SpellID": 2 }, + "SkillLine": { "ID": 0, "Category": 1, "Name": 3 }, + "Map": { "ID": 0, "InternalName": 1 }, + "CreatureModelData": { "ID": 0, "ModelPath": 2 }, + "CharHairGeosets": { + "RaceID": 1, "SexID": 2, "Variation": 3, "GeosetID": 4 + }, + "CharacterFacialHairStyles": { + "RaceID": 0, "SexID": 1, "Variation": 2, + "Geoset100": 3, "Geoset300": 4, "Geoset200": 5 + }, + "GameObjectDisplayInfo": { "ID": 0, "ModelName": 1 }, + "Emotes": { "ID": 0, "AnimID": 2 }, + "EmotesText": { + "ID": 0, "Command": 1, "EmoteRef": 2, + "OthersTargetTextID": 3, "SenderTargetTextID": 5, + "OthersNoTargetTextID": 7, "SenderNoTargetTextID": 9 + }, + "EmotesTextData": { "ID": 0, "Text": 1 }, + "Light": { + "ID": 0, "MapID": 1, "X": 2, "Z": 3, "Y": 4, + "InnerRadius": 5, "OuterRadius": 6, "LightParamsID": 7, + "LightParamsIDRain": 8, "LightParamsIDUnderwater": 9 + }, + "LightParams": { "LightParamsID": 0 }, + "LightIntBand": { + "BlockIndex": 1, "NumKeyframes": 2, "TimeKey0": 3, "Value0": 19 + }, + "LightFloatBand": { + "BlockIndex": 1, "NumKeyframes": 2, "TimeKey0": 3, "Value0": 19 }, "WorldMapArea": { - "AreaID": 2, - "AreaName": 3, - "DisplayMapID": 8, - "ID": 0, - "LocBottom": 7, - "LocLeft": 4, - "LocRight": 5, - "LocTop": 6, - "MapID": 1, - "ParentWorldMapID": 10 + "ID": 0, "MapID": 1, "AreaID": 2, "AreaName": 3, + "LocLeft": 4, "LocRight": 5, "LocTop": 6, "LocBottom": 7, + "DisplayMapID": 8, "ParentWorldMapID": 10 } } diff --git a/Data/expansions/tbc/update_fields.json b/Data/expansions/tbc/update_fields.json index 2e75c268..bbcedec5 100644 --- a/Data/expansions/tbc/update_fields.json +++ b/Data/expansions/tbc/update_fields.json @@ -1,50 +1,37 @@ { - "CONTAINER_FIELD_NUM_SLOTS": 64, - "CONTAINER_FIELD_SLOT_1": 66, - "GAMEOBJECT_DISPLAYID": 8, - "GAMEOBJECT_BYTES_1": 17, - "ITEM_FIELD_DURABILITY": 60, - "ITEM_FIELD_MAXDURABILITY": 61, - "ITEM_FIELD_STACK_COUNT": 14, "OBJECT_FIELD_ENTRY": 3, - "OBJECT_FIELD_SCALE_X": 4, - "PLAYER_BYTES": 237, - "PLAYER_BYTES_2": 238, - "PLAYER_EXPLORED_ZONES_START": 1312, - "PLAYER_FIELD_ARENA_CURRENCY": 1506, - "PLAYER_FIELD_BANKBAG_SLOT_1": 784, - "PLAYER_FIELD_BANK_SLOT_1": 728, - "PLAYER_FIELD_COINAGE": 1441, - "PLAYER_FIELD_HONOR_CURRENCY": 1505, - "PLAYER_FIELD_INV_SLOT_HEAD": 650, - "PLAYER_FIELD_PACK_SLOT_1": 696, - "PLAYER_FLAGS": 236, - "PLAYER_NEXT_LEVEL_XP": 927, - "PLAYER_QUEST_LOG_START": 244, - "PLAYER_REST_STATE_EXPERIENCE": 1440, - "PLAYER_SKILL_INFO_START": 928, - "PLAYER_XP": 926, - "UNIT_DYNAMIC_FLAGS": 164, - "UNIT_END": 234, + "UNIT_FIELD_TARGET_LO": 16, + "UNIT_FIELD_TARGET_HI": 17, "UNIT_FIELD_BYTES_0": 36, - "UNIT_FIELD_BYTES_1": 137, - "UNIT_FIELD_DISPLAYID": 152, + "UNIT_FIELD_HEALTH": 22, + "UNIT_FIELD_POWER1": 23, + "UNIT_FIELD_MAXHEALTH": 28, + "UNIT_FIELD_MAXPOWER1": 29, + "UNIT_FIELD_LEVEL": 34, "UNIT_FIELD_FACTIONTEMPLATE": 35, "UNIT_FIELD_FLAGS": 46, "UNIT_FIELD_FLAGS_2": 47, - "UNIT_FIELD_HEALTH": 22, - "UNIT_FIELD_LEVEL": 34, - "UNIT_FIELD_MAXHEALTH": 28, - "UNIT_FIELD_MAXPOWER1": 29, + "UNIT_FIELD_DISPLAYID": 152, "UNIT_FIELD_MOUNTDISPLAYID": 154, - "UNIT_FIELD_POWER1": 23, + "UNIT_NPC_FLAGS": 168, + "UNIT_DYNAMIC_FLAGS": 164, "UNIT_FIELD_RESISTANCES": 185, - "UNIT_FIELD_STAT0": 159, - "UNIT_FIELD_STAT1": 160, - "UNIT_FIELD_STAT2": 161, - "UNIT_FIELD_STAT3": 162, - "UNIT_FIELD_STAT4": 163, - "UNIT_FIELD_TARGET_HI": 17, - "UNIT_FIELD_TARGET_LO": 16, - "UNIT_NPC_FLAGS": 168 + "UNIT_END": 234, + "PLAYER_FLAGS": 236, + "PLAYER_BYTES": 237, + "PLAYER_BYTES_2": 238, + "PLAYER_XP": 926, + "PLAYER_NEXT_LEVEL_XP": 927, + "PLAYER_FIELD_COINAGE": 1441, + "PLAYER_QUEST_LOG_START": 244, + "PLAYER_FIELD_INV_SLOT_HEAD": 650, + "PLAYER_FIELD_PACK_SLOT_1": 696, + "PLAYER_FIELD_BANK_SLOT_1": 728, + "PLAYER_FIELD_BANKBAG_SLOT_1": 784, + "PLAYER_SKILL_INFO_START": 928, + "PLAYER_EXPLORED_ZONES_START": 1312, + "GAMEOBJECT_DISPLAYID": 8, + "ITEM_FIELD_STACK_COUNT": 14, + "CONTAINER_FIELD_NUM_SLOTS": 64, + "CONTAINER_FIELD_SLOT_1": 66 } diff --git a/Data/expansions/turtle/dbc_layouts.json b/Data/expansions/turtle/dbc_layouts.json index 2f580109..4e86338a 100644 --- a/Data/expansions/turtle/dbc_layouts.json +++ b/Data/expansions/turtle/dbc_layouts.json @@ -1,297 +1,96 @@ { - "AreaTable": { - "ExploreFlag": 3, - "ID": 0, - "MapID": 1, - "ParentAreaNum": 2 - }, - "CharHairGeosets": { - "GeosetID": 4, - "RaceID": 1, - "SexID": 2, - "Variation": 3 - }, - "CharSections": { - "BaseSection": 3, - "ColorIndex": 5, - "Flags": 9, - "RaceID": 1, - "SexID": 2, - "Texture1": 6, - "Texture2": 7, - "Texture3": 8, - "VariationIndex": 4 - }, - "CharacterFacialHairStyles": { - "Geoset100": 3, - "Geoset200": 5, - "Geoset300": 4, - "RaceID": 0, - "SexID": 1, - "Variation": 2 - }, - "CreatureDisplayInfo": { - "ExtraDisplayId": 3, - "ID": 0, - "ModelID": 1, - "Skin1": 6, - "Skin2": 7, - "Skin3": 8 - }, - "CreatureDisplayInfoExtra": { - "BakeName": 18, - "EquipDisplay0": 8, - "EquipDisplay1": 9, - "EquipDisplay2": 10, - "EquipDisplay3": 11, - "EquipDisplay4": 12, - "EquipDisplay5": 13, - "EquipDisplay6": 14, - "EquipDisplay7": 15, - "EquipDisplay8": 16, - "EquipDisplay9": 17, - "FaceID": 4, - "FacialHairID": 7, - "HairColorID": 6, - "HairStyleID": 5, - "ID": 0, - "RaceID": 1, - "SexID": 2, - "SkinID": 3 - }, - "CreatureModelData": { - "ID": 0, - "ModelPath": 2 - }, - "Emotes": { - "AnimID": 2, - "ID": 0 - }, - "EmotesText": { - "Command": 1, - "EmoteRef": 2, - "ID": 0, - "OthersNoTargetTextID": 7, - "OthersTargetTextID": 3, - "SenderNoTargetTextID": 9, - "SenderTargetTextID": 5 - }, - "EmotesTextData": { - "ID": 0, - "Text": 1 - }, - "Faction": { - "ID": 0, - "ReputationBase0": 10, - "ReputationBase1": 11, - "ReputationBase2": 12, - "ReputationBase3": 13, - "ReputationRaceMask0": 2, - "ReputationRaceMask1": 3, - "ReputationRaceMask2": 4, - "ReputationRaceMask3": 5 - }, - "FactionTemplate": { - "Enemy0": 6, - "Enemy1": 7, - "Enemy2": 8, - "Enemy3": 9, - "EnemyGroup": 5, - "Faction": 1, - "FactionGroup": 3, - "FriendGroup": 4, - "ID": 0 - }, - "GameObjectDisplayInfo": { - "ID": 0, - "ModelName": 1 + "Spell": { + "ID": 0, "Attributes": 5, "IconID": 117, + "Name": 120, "Tooltip": 147, "Rank": 129, "SchoolEnum": 1 }, "ItemDisplayInfo": { - "GeosetGroup1": 7, - "GeosetGroup3": 9, - "ID": 0, - "InventoryIcon": 5, - "LeftModel": 1, - "LeftModelTexture": 3, - "TextureArmLower": 15, - "TextureArmUpper": 14, - "TextureFoot": 21, - "TextureHand": 16, - "TextureLegLower": 20, - "TextureLegUpper": 19, - "TextureTorsoLower": 18, - "TextureTorsoUpper": 17 + "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, + "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9, + "TextureArmUpper": 14, "TextureArmLower": 15, "TextureHand": 16, + "TextureTorsoUpper": 17, "TextureTorsoLower": 18, + "TextureLegUpper": 19, "TextureLegLower": 20, "TextureFoot": 21 }, - "ItemSet": { - "ID": 0, - "Item0": 10, - "Item1": 11, - "Item2": 12, - "Item3": 13, - "Item4": 14, - "Item5": 15, - "Item6": 16, - "Item7": 17, - "Item8": 18, - "Item9": 19, - "Name": 1, - "Spell0": 20, - "Spell1": 21, - "Spell2": 22, - "Spell3": 23, - "Spell4": 24, - "Spell5": 25, - "Spell6": 26, - "Spell7": 27, - "Spell8": 28, - "Spell9": 29, - "Threshold0": 30, - "Threshold1": 31, - "Threshold2": 32, - "Threshold3": 33, - "Threshold4": 34, - "Threshold5": 35, - "Threshold6": 36, - "Threshold7": 37, - "Threshold8": 38, - "Threshold9": 39 + "CharSections": { + "RaceID": 1, "SexID": 2, "BaseSection": 3, + "VariationIndex": 4, "ColorIndex": 5, + "Texture1": 6, "Texture2": 7, "Texture3": 8, + "Flags": 9 }, - "Light": { - "ID": 0, - "InnerRadius": 5, - "LightParamsID": 7, - "LightParamsIDRain": 8, - "LightParamsIDUnderwater": 9, - "MapID": 1, - "OuterRadius": 6, - "X": 2, - "Y": 4, - "Z": 3 + "SpellIcon": { "ID": 0, "Path": 1 }, + "FactionTemplate": { + "ID": 0, "Faction": 1, "FactionGroup": 3, + "FriendGroup": 4, "EnemyGroup": 5, + "Enemy0": 6, "Enemy1": 7, "Enemy2": 8, "Enemy3": 9 }, - "LightFloatBand": { - "BlockIndex": 1, - "NumKeyframes": 2, - "TimeKey0": 3, - "Value0": 19 + "Faction": { + "ID": 0, "ReputationRaceMask0": 2, "ReputationRaceMask1": 3, + "ReputationRaceMask2": 4, "ReputationRaceMask3": 5, + "ReputationBase0": 10, "ReputationBase1": 11, + "ReputationBase2": 12, "ReputationBase3": 13 }, - "LightIntBand": { - "BlockIndex": 1, - "NumKeyframes": 2, - "TimeKey0": 3, - "Value0": 19 + "AreaTable": { "ID": 0, "ExploreFlag": 3 }, + "CreatureDisplayInfoExtra": { + "ID": 0, "RaceID": 1, "SexID": 2, "SkinID": 3, "FaceID": 4, + "HairStyleID": 5, "HairColorID": 6, "FacialHairID": 7, + "EquipDisplay0": 8, "EquipDisplay1": 9, "EquipDisplay2": 10, + "EquipDisplay3": 11, "EquipDisplay4": 12, "EquipDisplay5": 13, + "EquipDisplay6": 14, "EquipDisplay7": 15, "EquipDisplay8": 16, + "EquipDisplay9": 17, "BakeName": 18 }, - "LightParams": { - "LightParamsID": 0 - }, - "Map": { - "ID": 0, - "InternalName": 1 - }, - "SkillLine": { - "Category": 1, - "ID": 0, - "Name": 3 - }, - "SkillLineAbility": { - "SkillLineID": 1, - "SpellID": 2 - }, - "Spell": { - "Attributes": 5, - "AttributesEx": 6, - "CastingTimeIndex": 15, - "DispelType": 4, - "DurationIndex": 40, - "EffectBasePoints0": 80, - "EffectBasePoints1": 81, - "EffectBasePoints2": 82, - "ID": 0, - "IconID": 117, - "ManaCost": 29, - "Name": 120, - "PowerType": 28, - "RangeIndex": 33, - "Rank": 129, - "SchoolEnum": 1, - "Tooltip": 147 - }, - "SpellIcon": { - "ID": 0, - "Path": 1 - }, - "SpellItemEnchantment": { - "ID": 0, - "Name": 8 - }, - "SpellRange": { - "MaxRange": 2 - }, - "SpellVisual": { - "CastKit": 2, - "ID": 0, - "ImpactKit": 3, - "MissileModel": 8 - }, - "SpellVisualEffectName": { - "FilePath": 2, - "ID": 0 - }, - "SpellVisualKit": { - "BaseEffect": 5, - "ID": 0, - "SpecialEffect0": 11, - "SpecialEffect1": 12, - "SpecialEffect2": 13 - }, - "Talent": { - "Column": 3, - "ID": 0, - "PrereqRank0": 12, - "PrereqTalent0": 9, - "RankSpell0": 4, - "Row": 2, - "TabID": 1 - }, - "TalentTab": { - "BackgroundFile": 15, - "ClassMask": 12, - "ID": 0, - "Name": 1, - "OrderIndex": 14 + "CreatureDisplayInfo": { + "ID": 0, "ModelID": 1, "ExtraDisplayId": 3, + "Skin1": 6, "Skin2": 7, "Skin3": 8 }, "TaxiNodes": { - "ID": 0, - "MapID": 1, - "Name": 5, - "X": 2, - "Y": 3, - "Z": 4 - }, - "TaxiPath": { - "Cost": 3, - "FromNode": 1, - "ID": 0, - "ToNode": 2 + "ID": 0, "MapID": 1, "X": 2, "Y": 3, "Z": 4, "Name": 5 }, + "TaxiPath": { "ID": 0, "FromNode": 1, "ToNode": 2, "Cost": 3 }, "TaxiPathNode": { - "ID": 0, - "MapID": 3, - "NodeIndex": 2, - "PathID": 1, - "X": 4, - "Y": 5, - "Z": 6 + "ID": 0, "PathID": 1, "NodeIndex": 2, "MapID": 3, + "X": 4, "Y": 5, "Z": 6 + }, + "TalentTab": { + "ID": 0, "Name": 1, "ClassMask": 12, + "OrderIndex": 14, "BackgroundFile": 15 + }, + "Talent": { + "ID": 0, "TabID": 1, "Row": 2, "Column": 3, + "RankSpell0": 4, "PrereqTalent0": 9, "PrereqRank0": 12 + }, + "SkillLineAbility": { "SkillLineID": 1, "SpellID": 2 }, + "SkillLine": { "ID": 0, "Category": 1, "Name": 3 }, + "Map": { "ID": 0, "InternalName": 1 }, + "CreatureModelData": { "ID": 0, "ModelPath": 2 }, + "CharHairGeosets": { + "RaceID": 1, "SexID": 2, "Variation": 3, "GeosetID": 4 + }, + "CharacterFacialHairStyles": { + "RaceID": 0, "SexID": 1, "Variation": 2, + "Geoset100": 3, "Geoset300": 4, "Geoset200": 5 + }, + "GameObjectDisplayInfo": { "ID": 0, "ModelName": 1 }, + "Emotes": { "ID": 0, "AnimID": 2 }, + "EmotesText": { + "ID": 0, "Command": 1, "EmoteRef": 2, + "OthersTargetTextID": 3, "SenderTargetTextID": 5, + "OthersNoTargetTextID": 7, "SenderNoTargetTextID": 9 + }, + "EmotesTextData": { "ID": 0, "Text": 1 }, + "Light": { + "ID": 0, "MapID": 1, "X": 2, "Z": 3, "Y": 4, + "InnerRadius": 5, "OuterRadius": 6, "LightParamsID": 7, + "LightParamsIDRain": 8, "LightParamsIDUnderwater": 9 + }, + "LightParams": { "LightParamsID": 0 }, + "LightIntBand": { + "BlockIndex": 1, "NumKeyframes": 2, "TimeKey0": 3, "Value0": 19 + }, + "LightFloatBand": { + "BlockIndex": 1, "NumKeyframes": 2, "TimeKey0": 3, "Value0": 19 }, "WorldMapArea": { - "AreaID": 2, - "AreaName": 3, - "DisplayMapID": 8, - "ID": 0, - "LocBottom": 7, - "LocLeft": 4, - "LocRight": 5, - "LocTop": 6, - "MapID": 1, - "ParentWorldMapID": 10 + "ID": 0, "MapID": 1, "AreaID": 2, "AreaName": 3, + "LocLeft": 4, "LocRight": 5, "LocTop": 6, "LocBottom": 7, + "DisplayMapID": 8, "ParentWorldMapID": 10 } } diff --git a/Data/expansions/turtle/opcodes.json b/Data/expansions/turtle/opcodes.json index d0f84599..95a22888 100644 --- a/Data/expansions/turtle/opcodes.json +++ b/Data/expansions/turtle/opcodes.json @@ -1,6 +1,300 @@ { - "_extends": "../classic/opcodes.json", - "_remove": [ - "MSG_SET_DUNGEON_DIFFICULTY" - ] + "CMSG_PING": "0x1DC", + "CMSG_AUTH_SESSION": "0x1ED", + "CMSG_CHAR_CREATE": "0x036", + "CMSG_CHAR_ENUM": "0x037", + "CMSG_CHAR_DELETE": "0x038", + "CMSG_PLAYER_LOGIN": "0x03D", + "MSG_MOVE_START_FORWARD": "0x0B5", + "MSG_MOVE_START_BACKWARD": "0x0B6", + "MSG_MOVE_STOP": "0x0B7", + "MSG_MOVE_START_STRAFE_LEFT": "0x0B8", + "MSG_MOVE_START_STRAFE_RIGHT": "0x0B9", + "MSG_MOVE_STOP_STRAFE": "0x0BA", + "MSG_MOVE_JUMP": "0x0BB", + "MSG_MOVE_START_TURN_LEFT": "0x0BC", + "MSG_MOVE_START_TURN_RIGHT": "0x0BD", + "MSG_MOVE_STOP_TURN": "0x0BE", + "MSG_MOVE_SET_FACING": "0x0DA", + "MSG_MOVE_FALL_LAND": "0x0C9", + "MSG_MOVE_START_SWIM": "0x0CA", + "MSG_MOVE_STOP_SWIM": "0x0CB", + "MSG_MOVE_HEARTBEAT": "0x0EE", + "SMSG_AUTH_CHALLENGE": "0x1EC", + "SMSG_AUTH_RESPONSE": "0x1EE", + "SMSG_CHAR_CREATE": "0x03A", + "SMSG_CHAR_ENUM": "0x03B", + "SMSG_CHAR_DELETE": "0x03C", + "SMSG_CHARACTER_LOGIN_FAILED": "0x041", + "SMSG_PONG": "0x1DD", + "SMSG_LOGIN_VERIFY_WORLD": "0x236", + "SMSG_INIT_WORLD_STATES": "0x2C2", + "SMSG_LOGIN_SETTIMESPEED": "0x042", + "SMSG_TUTORIAL_FLAGS": "0x0FD", + "SMSG_INITIALIZE_FACTIONS": "0x122", + "SMSG_WARDEN_DATA": "0x2E6", + "CMSG_WARDEN_DATA": "0x2E7", + "SMSG_NOTIFICATION": "0x1CB", + "SMSG_ACCOUNT_DATA_TIMES": "0x209", + "SMSG_UPDATE_OBJECT": "0x0A9", + "SMSG_COMPRESSED_UPDATE_OBJECT": "0x1F6", + "SMSG_PARTYKILLLOG": "0x1F5", + "SMSG_MONSTER_MOVE_TRANSPORT": "0x2AE", + "SMSG_SPLINE_MOVE_SET_WALK_MODE": "0x30E", + "SMSG_SPLINE_MOVE_SET_RUN_MODE": "0x30D", + "SMSG_SPLINE_SET_RUN_SPEED": "0x2FE", + "SMSG_SPLINE_SET_RUN_BACK_SPEED": "0x2FF", + "SMSG_SPLINE_SET_SWIM_SPEED": "0x300", + "SMSG_DESTROY_OBJECT": "0x0AA", + "CMSG_MESSAGECHAT": "0x095", + "SMSG_MESSAGECHAT": "0x096", + "CMSG_WHO": "0x062", + "SMSG_WHO": "0x063", + "CMSG_PLAYED_TIME": "0x1CC", + "SMSG_PLAYED_TIME": "0x1CD", + "CMSG_QUERY_TIME": "0x1CE", + "SMSG_QUERY_TIME_RESPONSE": "0x1CF", + "SMSG_FRIEND_STATUS": "0x068", + "SMSG_CONTACT_LIST": "0x067", + "CMSG_ADD_FRIEND": "0x069", + "CMSG_DEL_FRIEND": "0x06A", + "CMSG_ADD_IGNORE": "0x06C", + "CMSG_DEL_IGNORE": "0x06D", + "CMSG_PLAYER_LOGOUT": "0x04A", + "CMSG_LOGOUT_REQUEST": "0x04B", + "CMSG_LOGOUT_CANCEL": "0x04E", + "SMSG_LOGOUT_RESPONSE": "0x04C", + "SMSG_LOGOUT_COMPLETE": "0x04D", + "CMSG_STANDSTATECHANGE": "0x101", + "CMSG_SHOWING_HELM": "0x2B9", + "CMSG_SHOWING_CLOAK": "0x2BA", + "CMSG_TOGGLE_PVP": "0x253", + "CMSG_GUILD_INVITE": "0x082", + "CMSG_GUILD_ACCEPT": "0x084", + "CMSG_GUILD_DECLINE": "0x085", + "CMSG_GUILD_INFO": "0x087", + "CMSG_GUILD_ROSTER": "0x089", + "CMSG_GUILD_PROMOTE": "0x08B", + "CMSG_GUILD_DEMOTE": "0x08C", + "CMSG_GUILD_LEAVE": "0x08D", + "CMSG_GUILD_MOTD": "0x091", + "SMSG_GUILD_INFO": "0x088", + "SMSG_GUILD_ROSTER": "0x08A", + "CMSG_GUILD_QUERY": "0x054", + "SMSG_GUILD_QUERY_RESPONSE": "0x055", + "SMSG_GUILD_INVITE": "0x083", + "CMSG_GUILD_REMOVE": "0x08E", + "SMSG_GUILD_EVENT": "0x092", + "SMSG_GUILD_COMMAND_RESULT": "0x093", + "MSG_RAID_READY_CHECK": "0x322", + "SMSG_ITEM_PUSH_RESULT": "0x166", + "CMSG_DUEL_ACCEPTED": "0x16C", + "CMSG_DUEL_CANCELLED": "0x16D", + "SMSG_DUEL_REQUESTED": "0x167", + "CMSG_INITIATE_TRADE": "0x116", + "MSG_RANDOM_ROLL": "0x1FB", + "CMSG_SET_SELECTION": "0x13D", + "CMSG_NAME_QUERY": "0x050", + "SMSG_NAME_QUERY_RESPONSE": "0x051", + "CMSG_CREATURE_QUERY": "0x060", + "SMSG_CREATURE_QUERY_RESPONSE": "0x061", + "CMSG_GAMEOBJECT_QUERY": "0x05E", + "SMSG_GAMEOBJECT_QUERY_RESPONSE": "0x05F", + "CMSG_SET_ACTIVE_MOVER": "0x26A", + "CMSG_BINDER_ACTIVATE": "0x1B5", + "SMSG_LOG_XPGAIN": "0x1D0", + "_NOTE_MONSTER_MOVE": "These look swapped vs vanilla (0x0DD/0x2FB) but may be intentional Turtle WoW changes. Check if NPC movement breaks.", + "SMSG_MONSTER_MOVE": "0x2FB", + "SMSG_COMPRESSED_MOVES": "0x06B", + "CMSG_ATTACKSWING": "0x141", + "CMSG_ATTACKSTOP": "0x142", + "SMSG_ATTACKSTART": "0x143", + "SMSG_ATTACKSTOP": "0x144", + "SMSG_ATTACKERSTATEUPDATE": "0x14A", + "SMSG_AI_REACTION": "0x13C", + "SMSG_SPELLNONMELEEDAMAGELOG": "0x250", + "SMSG_PLAY_SPELL_VISUAL": "0x1F3", + "SMSG_SPELLHEALLOG": "0x150", + "SMSG_SPELLENERGIZELOG": "0x151", + "SMSG_PERIODICAURALOG": "0x24E", + "SMSG_ENVIRONMENTAL_DAMAGE_LOG": "0x1FC", + "CMSG_CAST_SPELL": "0x12E", + "CMSG_CANCEL_CAST": "0x12F", + "CMSG_CANCEL_AURA": "0x136", + "SMSG_CAST_FAILED": "0x130", + "SMSG_SPELL_START": "0x131", + "SMSG_SPELL_GO": "0x132", + "SMSG_SPELL_FAILURE": "0x133", + "SMSG_SPELL_COOLDOWN": "0x134", + "SMSG_COOLDOWN_EVENT": "0x135", + "SMSG_EQUIPMENT_SET_SAVED": "0x137", + "SMSG_INITIAL_SPELLS": "0x12A", + "SMSG_LEARNED_SPELL": "0x12B", + "SMSG_SUPERCEDED_SPELL": "0x12C", + "SMSG_REMOVED_SPELL": "0x203", + "SMSG_SPELL_DELAYED": "0x1E2", + "SMSG_SET_FLAT_SPELL_MODIFIER": "0x266", + "SMSG_SET_PCT_SPELL_MODIFIER": "0x267", + "CMSG_LEARN_TALENT": "0x251", + "MSG_TALENT_WIPE_CONFIRM": "0x2AA", + "CMSG_GROUP_INVITE": "0x06E", + "SMSG_GROUP_INVITE": "0x06F", + "CMSG_GROUP_ACCEPT": "0x072", + "CMSG_GROUP_DECLINE": "0x073", + "SMSG_GROUP_DECLINE": "0x074", + "CMSG_GROUP_UNINVITE_GUID": "0x076", + "SMSG_GROUP_UNINVITE": "0x077", + "CMSG_GROUP_SET_LEADER": "0x078", + "SMSG_GROUP_SET_LEADER": "0x079", + "CMSG_GROUP_DISBAND": "0x07B", + "SMSG_GROUP_LIST": "0x07D", + "SMSG_PARTY_COMMAND_RESULT": "0x07F", + "MSG_RAID_TARGET_UPDATE": "0x321", + "CMSG_REQUEST_RAID_INFO": "0x2CD", + "SMSG_RAID_INSTANCE_INFO": "0x2CC", + "CMSG_AUTOSTORE_LOOT_ITEM": "0x108", + "CMSG_LOOT": "0x15D", + "CMSG_LOOT_MONEY": "0x15E", + "CMSG_LOOT_RELEASE": "0x15F", + "SMSG_LOOT_RESPONSE": "0x160", + "SMSG_LOOT_RELEASE_RESPONSE": "0x161", + "SMSG_LOOT_REMOVED": "0x162", + "SMSG_LOOT_MONEY_NOTIFY": "0x163", + "SMSG_LOOT_CLEAR_MONEY": "0x165", + "CMSG_ACTIVATETAXI": "0x1AD", + "CMSG_GOSSIP_HELLO": "0x17B", + "CMSG_GOSSIP_SELECT_OPTION": "0x17C", + "SMSG_GOSSIP_MESSAGE": "0x17D", + "SMSG_GOSSIP_COMPLETE": "0x17E", + "SMSG_NPC_TEXT_UPDATE": "0x180", + "CMSG_GAMEOBJ_USE": "0x0B1", + "CMSG_QUESTGIVER_STATUS_QUERY": "0x182", + "SMSG_QUESTGIVER_STATUS": "0x183", + "CMSG_QUESTGIVER_HELLO": "0x184", + "SMSG_QUESTGIVER_QUEST_LIST": "0x185", + "CMSG_QUESTGIVER_QUERY_QUEST": "0x186", + "SMSG_QUESTGIVER_QUEST_DETAILS": "0x188", + "CMSG_QUESTGIVER_ACCEPT_QUEST": "0x189", + "CMSG_QUESTGIVER_COMPLETE_QUEST": "0x18A", + "SMSG_QUESTGIVER_REQUEST_ITEMS": "0x18B", + "CMSG_QUESTGIVER_REQUEST_REWARD": "0x18C", + "SMSG_QUESTGIVER_OFFER_REWARD": "0x18D", + "CMSG_QUESTGIVER_CHOOSE_REWARD": "0x18E", + "SMSG_QUESTGIVER_QUEST_INVALID": "0x18F", + "SMSG_QUESTGIVER_QUEST_COMPLETE": "0x191", + "CMSG_QUESTLOG_REMOVE_QUEST": "0x194", + "SMSG_QUESTUPDATE_ADD_KILL": "0x199", + "SMSG_QUESTUPDATE_COMPLETE": "0x198", + "SMSG_QUEST_FORCE_REMOVE": "0x21E", + "CMSG_QUEST_QUERY": "0x05C", + "SMSG_QUEST_QUERY_RESPONSE": "0x05D", + "SMSG_QUESTLOG_FULL": "0x195", + "CMSG_LIST_INVENTORY": "0x19E", + "SMSG_LIST_INVENTORY": "0x19F", + "CMSG_SELL_ITEM": "0x1A0", + "SMSG_SELL_ITEM": "0x1A1", + "CMSG_BUY_ITEM": "0x1A2", + "CMSG_BUYBACK_ITEM": "0x1A6", + "SMSG_BUY_FAILED": "0x1A5", + "CMSG_TRAINER_LIST": "0x1B0", + "SMSG_TRAINER_LIST": "0x1B1", + "CMSG_TRAINER_BUY_SPELL": "0x1B2", + "SMSG_TRAINER_BUY_FAILED": "0x1B4", + "CMSG_ITEM_QUERY_SINGLE": "0x056", + "SMSG_ITEM_QUERY_SINGLE_RESPONSE": "0x058", + "CMSG_USE_ITEM": "0x0AB", + "CMSG_AUTOEQUIP_ITEM": "0x10A", + "CMSG_SWAP_ITEM": "0x10C", + "CMSG_SWAP_INV_ITEM": "0x10D", + "SMSG_INVENTORY_CHANGE_FAILURE": "0x112", + "CMSG_INSPECT": "0x114", + "SMSG_INSPECT_RESULTS_UPDATE": "0x115", + "CMSG_REPOP_REQUEST": "0x15A", + "SMSG_RESURRECT_REQUEST": "0x15B", + "CMSG_RESURRECT_RESPONSE": "0x15C", + "CMSG_SPIRIT_HEALER_ACTIVATE": "0x21C", + "SMSG_SPIRIT_HEALER_CONFIRM": "0x222", + "MSG_MOVE_TELEPORT_ACK": "0x0C7", + "SMSG_TRANSFER_PENDING": "0x03F", + "SMSG_NEW_WORLD": "0x03E", + "MSG_MOVE_WORLDPORT_ACK": "0x0DC", + "SMSG_TRANSFER_ABORTED": "0x040", + "SMSG_FORCE_RUN_SPEED_CHANGE": "0x0E2", + "SMSG_CLIENT_CONTROL_UPDATE": "0x159", + "CMSG_FORCE_RUN_SPEED_CHANGE_ACK": "0x0E3", + "SMSG_SHOWTAXINODES": "0x1A9", + "SMSG_ACTIVATETAXIREPLY": "0x1AE", + "SMSG_NEW_TAXI_PATH": "0x1AF", + "CMSG_ACTIVATETAXIEXPRESS": "0x312", + "CMSG_TAXINODE_STATUS_QUERY": "0x1AA", + "SMSG_TAXINODE_STATUS": "0x1AB", + "SMSG_TRAINER_BUY_SUCCEEDED": "0x1B3", + "SMSG_BINDPOINTUPDATE": "0x155", + "SMSG_SET_PROFICIENCY": "0x127", + "SMSG_ACTION_BUTTONS": "0x129", + "SMSG_LEVELUP_INFO": "0x1D4", + "SMSG_PLAY_SOUND": "0x2D2", + "CMSG_UPDATE_ACCOUNT_DATA": "0x20B", + "CMSG_BATTLEFIELD_LIST": "0x23C", + "SMSG_BATTLEFIELD_LIST": "0x23D", + "CMSG_BATTLEFIELD_JOIN": "0x23E", + "CMSG_BATTLEFIELD_STATUS": "0x2D3", + "SMSG_BATTLEFIELD_STATUS": "0x2D4", + "CMSG_BATTLEFIELD_PORT": "0x2D5", + "CMSG_BATTLEMASTER_HELLO": "0x2D7", + "MSG_PVP_LOG_DATA": "0x2E0", + "CMSG_LEAVE_BATTLEFIELD": "0x2E1", + "SMSG_GROUP_JOINED_BATTLEGROUND": "0x2E8", + "MSG_BATTLEGROUND_PLAYER_POSITIONS": "0x2E9", + "SMSG_BATTLEGROUND_PLAYER_JOINED": "0x2EC", + "SMSG_BATTLEGROUND_PLAYER_LEFT": "0x2ED", + "CMSG_BATTLEMASTER_JOIN": "0x2EE", + "CMSG_EMOTE": "0x102", + "SMSG_EMOTE": "0x103", + "CMSG_TEXT_EMOTE": "0x104", + "SMSG_TEXT_EMOTE": "0x105", + "CMSG_JOIN_CHANNEL": "0x097", + "CMSG_LEAVE_CHANNEL": "0x098", + "SMSG_CHANNEL_NOTIFY": "0x099", + "CMSG_CHANNEL_LIST": "0x09A", + "SMSG_CHANNEL_LIST": "0x09B", + "SMSG_INSPECT_TALENT": "0x3F4", + "SMSG_SHOW_MAILBOX": "0x297", + "CMSG_GET_MAIL_LIST": "0x23A", + "SMSG_MAIL_LIST_RESULT": "0x23B", + "CMSG_SEND_MAIL": "0x238", + "SMSG_SEND_MAIL_RESULT": "0x239", + "CMSG_MAIL_TAKE_MONEY": "0x245", + "CMSG_MAIL_TAKE_ITEM": "0x246", + "CMSG_MAIL_DELETE": "0x249", + "CMSG_MAIL_MARK_AS_READ": "0x247", + "SMSG_RECEIVED_MAIL": "0x285", + "MSG_QUERY_NEXT_MAIL_TIME": "0x284", + "CMSG_BANKER_ACTIVATE": "0x1B7", + "SMSG_SHOW_BANK": "0x1B8", + "CMSG_BUY_BANK_SLOT": "0x1B9", + "SMSG_BUY_BANK_SLOT_RESULT": "0x1BA", + "CMSG_AUTOSTORE_BANK_ITEM": "0x282", + "CMSG_AUTOBANK_ITEM": "0x283", + "MSG_AUCTION_HELLO": "0x255", + "CMSG_AUCTION_SELL_ITEM": "0x256", + "CMSG_AUCTION_REMOVE_ITEM": "0x257", + "CMSG_AUCTION_LIST_ITEMS": "0x258", + "CMSG_AUCTION_LIST_OWNER_ITEMS": "0x259", + "CMSG_AUCTION_PLACE_BID": "0x25A", + "SMSG_AUCTION_COMMAND_RESULT": "0x25B", + "SMSG_AUCTION_LIST_RESULT": "0x25C", + "SMSG_AUCTION_OWNER_LIST_RESULT": "0x25D", + "SMSG_AUCTION_OWNER_NOTIFICATION": "0x25E", + "SMSG_AUCTION_BIDDER_NOTIFICATION": "0x260", + "CMSG_AUCTION_LIST_BIDDER_ITEMS": "0x264", + "SMSG_AUCTION_BIDDER_LIST_RESULT": "0x265", + "MSG_MOVE_TIME_SKIPPED": "0x319", + "SMSG_CANCEL_AUTO_REPEAT": "0x29C", + "SMSG_WEATHER": "0x2F4", + "SMSG_QUESTUPDATE_ADD_ITEM": "0x19A", + "CMSG_GUILD_DISBAND": "0x08F", + "CMSG_GUILD_LEADER": "0x090", + "CMSG_GUILD_SET_PUBLIC_NOTE": "0x234", + "CMSG_GUILD_SET_OFFICER_NOTE": "0x235" } diff --git a/Data/expansions/turtle/update_fields.json b/Data/expansions/turtle/update_fields.json index 5b214f59..5f97f29f 100644 --- a/Data/expansions/turtle/update_fields.json +++ b/Data/expansions/turtle/update_fields.json @@ -1,50 +1,38 @@ { - "CONTAINER_FIELD_NUM_SLOTS": 48, - "CONTAINER_FIELD_SLOT_1": 50, - "GAMEOBJECT_DISPLAYID": 8, - "GAMEOBJECT_BYTES_1": 14, - "ITEM_FIELD_DURABILITY": 48, - "ITEM_FIELD_MAXDURABILITY": 49, - "ITEM_FIELD_STACK_COUNT": 14, "OBJECT_FIELD_ENTRY": 3, - "OBJECT_FIELD_SCALE_X": 4, - "PLAYER_BYTES": 191, - "PLAYER_BYTES_2": 192, - "PLAYER_END": 1282, - "PLAYER_EXPLORED_ZONES_START": 1111, - "PLAYER_FIELD_BANKBAG_SLOT_1": 612, - "PLAYER_FIELD_BANK_SLOT_1": 564, - "PLAYER_FIELD_COINAGE": 1176, - "PLAYER_FIELD_INV_SLOT_HEAD": 486, - "PLAYER_FIELD_PACK_SLOT_1": 532, - "PLAYER_FLAGS": 190, - "PLAYER_NEXT_LEVEL_XP": 717, - "PLAYER_QUEST_LOG_START": 198, - "PLAYER_REST_STATE_EXPERIENCE": 1175, - "PLAYER_SKILL_INFO_START": 718, - "PLAYER_XP": 716, - "UNIT_DYNAMIC_FLAGS": 143, - "UNIT_END": 188, - "UNIT_FIELD_AURAFLAGS": 98, - "UNIT_FIELD_AURAS": 50, + "UNIT_FIELD_TARGET_LO": 16, + "UNIT_FIELD_TARGET_HI": 17, "UNIT_FIELD_BYTES_0": 36, - "UNIT_FIELD_BYTES_1": 133, - "UNIT_FIELD_DISPLAYID": 131, - "UNIT_FIELD_FACTIONTEMPLATE": 35, - "UNIT_FIELD_FLAGS": 46, "UNIT_FIELD_HEALTH": 22, - "UNIT_FIELD_LEVEL": 34, + "UNIT_FIELD_POWER1": 23, "UNIT_FIELD_MAXHEALTH": 28, "UNIT_FIELD_MAXPOWER1": 29, + "UNIT_FIELD_LEVEL": 34, + "UNIT_FIELD_FACTIONTEMPLATE": 35, + "UNIT_FIELD_FLAGS": 46, + "UNIT_FIELD_DISPLAYID": 131, "UNIT_FIELD_MOUNTDISPLAYID": 133, - "UNIT_FIELD_POWER1": 23, + "UNIT_FIELD_AURAS": 50, + "UNIT_NPC_FLAGS": 147, + "UNIT_DYNAMIC_FLAGS": 143, "UNIT_FIELD_RESISTANCES": 154, - "UNIT_FIELD_STAT0": 138, - "UNIT_FIELD_STAT1": 139, - "UNIT_FIELD_STAT2": 140, - "UNIT_FIELD_STAT3": 141, - "UNIT_FIELD_STAT4": 142, - "UNIT_FIELD_TARGET_HI": 17, - "UNIT_FIELD_TARGET_LO": 16, - "UNIT_NPC_FLAGS": 147 + "UNIT_END": 188, + "PLAYER_FLAGS": 190, + "PLAYER_BYTES": 191, + "PLAYER_BYTES_2": 192, + "PLAYER_XP": 716, + "PLAYER_NEXT_LEVEL_XP": 717, + "PLAYER_FIELD_COINAGE": 1176, + "PLAYER_QUEST_LOG_START": 198, + "PLAYER_FIELD_INV_SLOT_HEAD": 486, + "PLAYER_FIELD_PACK_SLOT_1": 532, + "PLAYER_FIELD_BANK_SLOT_1": 564, + "PLAYER_FIELD_BANKBAG_SLOT_1": 612, + "PLAYER_SKILL_INFO_START": 718, + "PLAYER_EXPLORED_ZONES_START": 1111, + "PLAYER_END": 1282, + "GAMEOBJECT_DISPLAYID": 8, + "ITEM_FIELD_STACK_COUNT": 14, + "CONTAINER_FIELD_NUM_SLOTS": 48, + "CONTAINER_FIELD_SLOT_1": 50 } diff --git a/Data/expansions/wotlk/dbc_layouts.json b/Data/expansions/wotlk/dbc_layouts.json index e563d2c9..5b500741 100644 --- a/Data/expansions/wotlk/dbc_layouts.json +++ b/Data/expansions/wotlk/dbc_layouts.json @@ -1,323 +1,99 @@ { - "Achievement": { - "Description": 21, - "ID": 0, - "Points": 39, - "Title": 4 - }, - "AchievementCriteria": { - "AchievementID": 1, - "Description": 9, - "ID": 0, - "Quantity": 4 - }, - "AreaTable": { - "ExploreFlag": 3, - "ID": 0, - "MapID": 1, - "ParentAreaNum": 2 - }, - "CharHairGeosets": { - "GeosetID": 4, - "RaceID": 1, - "SexID": 2, - "Variation": 3 - }, - "CharSections": { - "BaseSection": 3, - "ColorIndex": 5, - "Flags": 9, - "RaceID": 1, - "SexID": 2, - "Texture1": 6, - "Texture2": 7, - "Texture3": 8, - "VariationIndex": 4 - }, - "CharTitles": { - "ID": 0, - "Title": 2, - "TitleBit": 36 - }, - "CharacterFacialHairStyles": { - "Geoset100": 3, - "Geoset200": 5, - "Geoset300": 4, - "RaceID": 0, - "SexID": 1, - "Variation": 2 - }, - "CreatureDisplayInfo": { - "ExtraDisplayId": 3, - "ID": 0, - "ModelID": 1, - "Skin1": 6, - "Skin2": 7, - "Skin3": 8 - }, - "CreatureDisplayInfoExtra": { - "BakeName": 20, - "EquipDisplay0": 8, - "EquipDisplay1": 9, - "EquipDisplay10": 18, - "EquipDisplay2": 10, - "EquipDisplay3": 11, - "EquipDisplay4": 12, - "EquipDisplay5": 13, - "EquipDisplay6": 14, - "EquipDisplay7": 15, - "EquipDisplay8": 16, - "EquipDisplay9": 17, - "FaceID": 4, - "FacialHairID": 7, - "HairColorID": 6, - "HairStyleID": 5, - "ID": 0, - "RaceID": 1, - "SexID": 2, - "SkinID": 3 - }, - "CreatureModelData": { - "ID": 0, - "ModelPath": 2 - }, - "Emotes": { - "AnimID": 2, - "ID": 0 - }, - "EmotesText": { - "Command": 1, - "EmoteRef": 2, - "ID": 0, - "OthersNoTargetTextID": 7, - "OthersTargetTextID": 3, - "SenderNoTargetTextID": 9, - "SenderTargetTextID": 5 - }, - "EmotesTextData": { - "ID": 0, - "Text": 1 - }, - "Faction": { - "ID": 0, - "ReputationBase0": 10, - "ReputationBase1": 11, - "ReputationBase2": 12, - "ReputationBase3": 13, - "ReputationRaceMask0": 2, - "ReputationRaceMask1": 3, - "ReputationRaceMask2": 4, - "ReputationRaceMask3": 5 - }, - "FactionTemplate": { - "Enemy0": 6, - "Enemy1": 7, - "Enemy2": 8, - "Enemy3": 9, - "EnemyGroup": 5, - "Faction": 1, - "FactionGroup": 3, - "FriendGroup": 4, - "ID": 0 - }, - "GameObjectDisplayInfo": { - "ID": 0, - "ModelName": 1 + "Spell": { + "ID": 0, "Attributes": 4, "IconID": 133, + "Name": 136, "Tooltip": 139, "Rank": 153, "SchoolMask": 225 }, "ItemDisplayInfo": { - "GeosetGroup1": 7, - "GeosetGroup3": 9, - "ID": 0, - "InventoryIcon": 5, - "LeftModel": 1, - "LeftModelTexture": 3, - "TextureArmLower": 15, - "TextureArmUpper": 14, - "TextureFoot": 21, - "TextureHand": 16, - "TextureLegLower": 20, - "TextureLegUpper": 19, - "TextureTorsoLower": 18, - "TextureTorsoUpper": 17 + "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, + "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9, + "TextureArmUpper": 14, "TextureArmLower": 15, "TextureHand": 16, + "TextureTorsoUpper": 17, "TextureTorsoLower": 18, + "TextureLegUpper": 19, "TextureLegLower": 20, "TextureFoot": 21 }, - "ItemSet": { - "ID": 0, - "Item0": 18, - "Item1": 19, - "Item2": 20, - "Item3": 21, - "Item4": 22, - "Item5": 23, - "Item6": 24, - "Item7": 25, - "Item8": 26, - "Item9": 27, - "Name": 1, - "Spell0": 28, - "Spell1": 29, - "Spell2": 30, - "Spell3": 31, - "Spell4": 32, - "Spell5": 33, - "Spell6": 34, - "Spell7": 35, - "Spell8": 36, - "Spell9": 37, - "Threshold0": 38, - "Threshold1": 39, - "Threshold2": 40, - "Threshold3": 41, - "Threshold4": 42, - "Threshold5": 43, - "Threshold6": 44, - "Threshold7": 45, - "Threshold8": 46, - "Threshold9": 47 + "CharSections": { + "RaceID": 1, "SexID": 2, "BaseSection": 3, + "VariationIndex": 4, "ColorIndex": 5, + "Texture1": 6, "Texture2": 7, "Texture3": 8, + "Flags": 9 }, - "LFGDungeons": { - "ID": 0, - "Name": 1 + "SpellIcon": { "ID": 0, "Path": 1 }, + "FactionTemplate": { + "ID": 0, "Faction": 1, "FactionGroup": 3, + "FriendGroup": 4, "EnemyGroup": 5, + "Enemy0": 6, "Enemy1": 7, "Enemy2": 8, "Enemy3": 9 }, - "Light": { - "ID": 0, - "InnerRadius": 5, - "LightParamsID": 7, - "LightParamsIDRain": 8, - "LightParamsIDUnderwater": 9, - "MapID": 1, - "OuterRadius": 6, - "X": 2, - "Y": 4, - "Z": 3 + "Faction": { + "ID": 0, "ReputationRaceMask0": 2, "ReputationRaceMask1": 3, + "ReputationRaceMask2": 4, "ReputationRaceMask3": 5, + "ReputationBase0": 10, "ReputationBase1": 11, + "ReputationBase2": 12, "ReputationBase3": 13 }, - "LightFloatBand": { - "BlockIndex": 1, - "NumKeyframes": 2, - "TimeKey0": 3, - "Value0": 19 + "Achievement": { "ID": 0, "Title": 4, "Description": 21 }, + "AreaTable": { "ID": 0, "ExploreFlag": 3 }, + "CreatureDisplayInfoExtra": { + "ID": 0, "RaceID": 1, "SexID": 2, "SkinID": 3, "FaceID": 4, + "HairStyleID": 5, "HairColorID": 6, "FacialHairID": 7, + "EquipDisplay0": 8, "EquipDisplay1": 9, "EquipDisplay2": 10, + "EquipDisplay3": 11, "EquipDisplay4": 12, "EquipDisplay5": 13, + "EquipDisplay6": 14, "EquipDisplay7": 15, "EquipDisplay8": 16, + "EquipDisplay9": 17, "EquipDisplay10": 18, "BakeName": 20 }, - "LightIntBand": { - "BlockIndex": 1, - "NumKeyframes": 2, - "TimeKey0": 3, - "Value0": 19 - }, - "LightParams": { - "LightParamsID": 0 - }, - "Map": { - "ID": 0, - "InternalName": 1 - }, - "SkillLine": { - "Category": 1, - "ID": 0, - "Name": 3 - }, - "SkillLineAbility": { - "SkillLineID": 1, - "SpellID": 2 - }, - "Spell": { - "Attributes": 4, - "AttributesEx": 5, - "CastingTimeIndex": 47, - "DispelType": 2, - "DurationIndex": 40, - "EffectBasePoints0": 80, - "EffectBasePoints1": 81, - "EffectBasePoints2": 82, - "ID": 0, - "IconID": 133, - "ManaCost": 39, - "Name": 136, - "PowerType": 14, - "RangeIndex": 49, - "Rank": 153, - "SchoolMask": 225, - "Tooltip": 139 - }, - "SpellIcon": { - "ID": 0, - "Path": 1 - }, - "SpellItemEnchantment": { - "ID": 0, - "Name": 8 - }, - "SpellRange": { - "MaxRange": 4 - }, - "SpellVisual": { - "CastKit": 2, - "ID": 0, - "ImpactKit": 3, - "MissileModel": 8 - }, - "SpellVisualEffectName": { - "FilePath": 2, - "ID": 0 - }, - "SpellVisualKit": { - "BaseEffect": 5, - "ID": 0, - "SpecialEffect0": 11, - "SpecialEffect1": 12, - "SpecialEffect2": 13 - }, - "Talent": { - "Column": 3, - "ID": 0, - "PrereqRank0": 12, - "PrereqTalent0": 9, - "RankSpell0": 4, - "Row": 2, - "TabID": 1 - }, - "TalentTab": { - "BackgroundFile": 23, - "ClassMask": 20, - "ID": 0, - "Name": 1, - "OrderIndex": 22 + "CreatureDisplayInfo": { + "ID": 0, "ModelID": 1, "ExtraDisplayId": 3, + "Skin1": 6, "Skin2": 7, "Skin3": 8 }, "TaxiNodes": { - "ID": 0, - "MapID": 1, - "MountDisplayIdAlliance": 22, - "MountDisplayIdAllianceFallback": 20, - "MountDisplayIdHorde": 23, - "MountDisplayIdHordeFallback": 21, - "Name": 5, - "X": 2, - "Y": 3, - "Z": 4 - }, - "TaxiPath": { - "Cost": 3, - "FromNode": 1, - "ID": 0, - "ToNode": 2 + "ID": 0, "MapID": 1, "X": 2, "Y": 3, "Z": 4, "Name": 5, + "MountDisplayIdAllianceFallback": 20, "MountDisplayIdHordeFallback": 21, + "MountDisplayIdAlliance": 22, "MountDisplayIdHorde": 23 }, + "TaxiPath": { "ID": 0, "FromNode": 1, "ToNode": 2, "Cost": 3 }, "TaxiPathNode": { - "ID": 0, - "MapID": 3, - "NodeIndex": 2, - "PathID": 1, - "X": 4, - "Y": 5, - "Z": 6 + "ID": 0, "PathID": 1, "NodeIndex": 2, "MapID": 3, + "X": 4, "Y": 5, "Z": 6 + }, + "TalentTab": { + "ID": 0, "Name": 1, "ClassMask": 20, + "OrderIndex": 22, "BackgroundFile": 23 + }, + "Talent": { + "ID": 0, "TabID": 1, "Row": 2, "Column": 3, + "RankSpell0": 4, "PrereqTalent0": 9, "PrereqRank0": 12 + }, + "SkillLineAbility": { "SkillLineID": 1, "SpellID": 2 }, + "SkillLine": { "ID": 0, "Category": 1, "Name": 3 }, + "Map": { "ID": 0, "InternalName": 1 }, + "CreatureModelData": { "ID": 0, "ModelPath": 2 }, + "CharHairGeosets": { + "RaceID": 1, "SexID": 2, "Variation": 3, "GeosetID": 4 + }, + "CharacterFacialHairStyles": { + "RaceID": 0, "SexID": 1, "Variation": 2, + "Geoset100": 3, "Geoset300": 4, "Geoset200": 5 + }, + "GameObjectDisplayInfo": { "ID": 0, "ModelName": 1 }, + "Emotes": { "ID": 0, "AnimID": 2 }, + "EmotesText": { + "ID": 0, "Command": 1, "EmoteRef": 2, + "OthersTargetTextID": 3, "SenderTargetTextID": 5, + "OthersNoTargetTextID": 7, "SenderNoTargetTextID": 9 + }, + "EmotesTextData": { "ID": 0, "Text": 1 }, + "Light": { + "ID": 0, "MapID": 1, "X": 2, "Z": 3, "Y": 4, + "InnerRadius": 5, "OuterRadius": 6, "LightParamsID": 7, + "LightParamsIDRain": 8, "LightParamsIDUnderwater": 9 + }, + "LightParams": { "LightParamsID": 0 }, + "LightIntBand": { + "BlockIndex": 1, "NumKeyframes": 2, "TimeKey0": 3, "Value0": 19 + }, + "LightFloatBand": { + "BlockIndex": 1, "NumKeyframes": 2, "TimeKey0": 3, "Value0": 19 }, "WorldMapArea": { - "AreaID": 2, - "AreaName": 3, - "DisplayMapID": 8, - "ID": 0, - "LocBottom": 7, - "LocLeft": 4, - "LocRight": 5, - "LocTop": 6, - "MapID": 1, - "ParentWorldMapID": 10 + "ID": 0, "MapID": 1, "AreaID": 2, "AreaName": 3, + "LocLeft": 4, "LocRight": 5, "LocTop": 6, "LocBottom": 7, + "DisplayMapID": 8, "ParentWorldMapID": 10 } } diff --git a/Data/expansions/wotlk/update_fields.json b/Data/expansions/wotlk/update_fields.json index 49fcbdd5..f308cf0d 100644 --- a/Data/expansions/wotlk/update_fields.json +++ b/Data/expansions/wotlk/update_fields.json @@ -1,63 +1,37 @@ { - "CONTAINER_FIELD_NUM_SLOTS": 64, - "CONTAINER_FIELD_SLOT_1": 66, - "GAMEOBJECT_DISPLAYID": 8, - "GAMEOBJECT_BYTES_1": 17, - "ITEM_FIELD_DURABILITY": 60, - "ITEM_FIELD_MAXDURABILITY": 61, - "ITEM_FIELD_STACK_COUNT": 14, "OBJECT_FIELD_ENTRY": 3, - "OBJECT_FIELD_SCALE_X": 4, - "PLAYER_BLOCK_PERCENTAGE": 1024, - "PLAYER_BYTES": 153, - "PLAYER_BYTES_2": 154, - "PLAYER_CHOSEN_TITLE": 1349, - "PLAYER_CRIT_PERCENTAGE": 1029, - "PLAYER_DODGE_PERCENTAGE": 1025, - "PLAYER_EXPLORED_ZONES_START": 1041, - "PLAYER_FIELD_ARENA_CURRENCY": 1423, - "PLAYER_FIELD_BANKBAG_SLOT_1": 458, - "PLAYER_FIELD_BANK_SLOT_1": 402, - "PLAYER_FIELD_COINAGE": 1170, - "PLAYER_FIELD_COMBAT_RATING_1": 1231, - "PLAYER_FIELD_HONOR_CURRENCY": 1422, - "PLAYER_FIELD_INV_SLOT_HEAD": 324, - "PLAYER_FIELD_MOD_DAMAGE_DONE_POS": 1171, - "PLAYER_FIELD_MOD_HEALING_DONE_POS": 1192, - "PLAYER_FIELD_PACK_SLOT_1": 370, - "PLAYER_FLAGS": 150, - "PLAYER_NEXT_LEVEL_XP": 635, - "PLAYER_PARRY_PERCENTAGE": 1026, - "PLAYER_QUEST_LOG_START": 158, - "PLAYER_RANGED_CRIT_PERCENTAGE": 1030, - "PLAYER_REST_STATE_EXPERIENCE": 1169, - "PLAYER_SKILL_INFO_START": 636, - "PLAYER_SPELL_CRIT_PERCENTAGE1": 1032, - "PLAYER_XP": 634, - "UNIT_DYNAMIC_FLAGS": 147, - "UNIT_END": 148, - "UNIT_FIELD_ATTACK_POWER": 123, + "UNIT_FIELD_TARGET_LO": 6, + "UNIT_FIELD_TARGET_HI": 7, "UNIT_FIELD_BYTES_0": 23, - "UNIT_FIELD_BYTES_1": 137, - "UNIT_FIELD_DISPLAYID": 67, + "UNIT_FIELD_HEALTH": 24, + "UNIT_FIELD_POWER1": 25, + "UNIT_FIELD_MAXHEALTH": 32, + "UNIT_FIELD_MAXPOWER1": 33, + "UNIT_FIELD_LEVEL": 54, "UNIT_FIELD_FACTIONTEMPLATE": 55, "UNIT_FIELD_FLAGS": 59, "UNIT_FIELD_FLAGS_2": 60, - "UNIT_FIELD_HEALTH": 24, - "UNIT_FIELD_LEVEL": 54, - "UNIT_FIELD_MAXHEALTH": 32, - "UNIT_FIELD_MAXPOWER1": 33, + "UNIT_FIELD_DISPLAYID": 67, "UNIT_FIELD_MOUNTDISPLAYID": 69, - "UNIT_FIELD_POWER1": 25, - "UNIT_FIELD_RANGED_ATTACK_POWER": 126, - "UNIT_FIELD_RESISTANCES": 99, - "UNIT_FIELD_STAT0": 84, - "UNIT_FIELD_STAT1": 85, - "UNIT_FIELD_STAT2": 86, - "UNIT_FIELD_STAT3": 87, - "UNIT_FIELD_STAT4": 88, - "UNIT_FIELD_TARGET_HI": 7, - "UNIT_FIELD_TARGET_LO": 6, "UNIT_NPC_FLAGS": 82, - "UNIT_NPC_EMOTESTATE": 164 + "UNIT_DYNAMIC_FLAGS": 147, + "UNIT_FIELD_RESISTANCES": 99, + "UNIT_END": 148, + "PLAYER_FLAGS": 150, + "PLAYER_BYTES": 153, + "PLAYER_BYTES_2": 154, + "PLAYER_XP": 634, + "PLAYER_NEXT_LEVEL_XP": 635, + "PLAYER_FIELD_COINAGE": 1170, + "PLAYER_QUEST_LOG_START": 158, + "PLAYER_FIELD_INV_SLOT_HEAD": 324, + "PLAYER_FIELD_PACK_SLOT_1": 370, + "PLAYER_FIELD_BANK_SLOT_1": 402, + "PLAYER_FIELD_BANKBAG_SLOT_1": 458, + "PLAYER_SKILL_INFO_START": 636, + "PLAYER_EXPLORED_ZONES_START": 1041, + "GAMEOBJECT_DISPLAYID": 8, + "ITEM_FIELD_STACK_COUNT": 14, + "CONTAINER_FIELD_NUM_SLOTS": 64, + "CONTAINER_FIELD_SLOT_1": 66 } diff --git a/Data/opcodes/aliases.json b/Data/opcodes/aliases.json index 4677cd5d..e3a67348 100644 --- a/Data/opcodes/aliases.json +++ b/Data/opcodes/aliases.json @@ -41,6 +41,7 @@ "SMSG_SPLINE_MOVE_SET_RUN_BACK_SPEED": "SMSG_SPLINE_SET_RUN_BACK_SPEED", "SMSG_SPLINE_MOVE_SET_RUN_SPEED": "SMSG_SPLINE_SET_RUN_SPEED", "SMSG_SPLINE_MOVE_SET_SWIM_SPEED": "SMSG_SPLINE_SET_SWIM_SPEED", + "SMSG_UPDATE_AURA_DURATION": "SMSG_EQUIPMENT_SET_SAVED", "SMSG_VICTIMSTATEUPDATE_OBSOLETE": "SMSG_BATTLEFIELD_PORT_DENIED" } } diff --git a/EXPANSION_GUIDE.md b/EXPANSION_GUIDE.md deleted file mode 100644 index a1257a21..00000000 --- a/EXPANSION_GUIDE.md +++ /dev/null @@ -1,130 +0,0 @@ -# Multi-Expansion Architecture Guide - -WoWee supports three World of Warcraft expansions in a unified codebase using an expansion profile system. This guide explains how the multi-expansion support works. - -## Supported Expansions - -- **Vanilla (Classic) 1.12** - Original World of Warcraft -- **The Burning Crusade (TBC) 2.4.3** - First expansion -- **Wrath of the Lich King (WotLK) 3.3.5a** - Second expansion -- **Turtle WoW 1.17** - Custom Vanilla-based server with extended content - -## Architecture Overview - -The multi-expansion support is built on the **Expansion Profile** system: - -1. **ExpansionProfile** (`include/game/expansion_profile.hpp`) - Metadata about each expansion - - Defines protocol version, data paths, asset locations - - Specifies which packet parsers to use - -2. **Packet Parsers** - Expansion-specific message handling - - `packet_parsers_classic.cpp` - Vanilla 1.12 / Turtle WoW message parsing - - `packet_parsers_tbc.cpp` - TBC 2.4.3 message parsing - - Default (WotLK 3.3.5a) parsers in `game_handler.cpp` and domain handlers - -3. **Update Fields** - Expansion-specific entity data layout - - Loaded from `update_fields.json` in expansion data directory - - Defines UNIT_END, OBJECT_END, field indices for stats/health/mana - -## How to Use Different Expansions - -### At Startup - -WoWee auto-detects the expansion based on: -1. Realm list response (protocol version) -2. Server build number -3. Update field count - -### Manual Selection - -Set environment variable: -```bash -WOWEE_EXPANSION=tbc ./wowee # Force TBC -WOWEE_EXPANSION=classic ./wowee # Force Classic -``` - -## Key Differences Between Expansions - -### Packet Format Differences - -#### SMSG_SPELL_COOLDOWN -- **Classic**: 12 bytes per entry (spellId + itemId + cooldown, no flags) -- **TBC/WotLK**: 8 bytes per entry (spellId + cooldown) + flags byte - -#### SMSG_ACTION_BUTTONS -- **Classic**: 120 slots, no mode byte -- **TBC**: 132 slots, no mode byte -- **WotLK**: 144 slots + uint8 mode byte - -#### SMSG_PARTY_MEMBER_STATS -- **Classic/TBC**: Full uint64 for guid, uint16 health -- **WotLK**: PackedGuid format, uint32 health - -### Data Differences - -- **Talent trees**: Different spell IDs and tree structure per expansion -- **Items**: Different ItemDisplayInfo entries -- **Spells**: Different base stats, cooldowns -- **Character textures**: Expansion-specific variants for races - -## Adding Support for Another Expansion - -1. Create new expansion profile entry in `expansion_profile.cpp` -2. Add packet parser file (`packet_parsers_*.cpp`) for message variants -3. Create update_fields.json with correct field layout -4. Test realm connection and character loading - -## Code Patterns - -### Checking Current Expansion - -```cpp -#include "game/game_utils.hpp" - -// Shared helpers (defined in game_utils.hpp) -if (isActiveExpansion("tbc")) { - // TBC-specific code -} - -if (isClassicLikeExpansion()) { - // Classic or Turtle WoW -} - -if (isPreWotlk()) { - // Classic, Turtle, or TBC (not WotLK) -} -``` - -### Expansion-Specific Packet Parsing - -```cpp -// In packet_parsers_*.cpp, implement expansion-specific logic -bool TbcPacketParsers::parseXxx(network::Packet& packet, XxxData& data) { - // Custom logic for this expansion's packet format -} -``` - -## Common Issues - -### "Update fields mismatch" Error -- Ensure `update_fields.json` matches server's field layout -- Check OBJECT_END and UNIT_END values -- Verify field indices for your target expansion - -### "Unknown packet" Warnings -- Expansion-specific opcodes may not be registered -- Check packet parser registration in `game_handler.cpp` -- Verify expansion profile is active - -### Packet Parsing Failures -- Each expansion has different struct layouts -- Always read data size first, then upfront validate -- Use size capping (e.g., max 100 items in list) - -## References - -- `include/game/expansion_profile.hpp` - Expansion metadata -- `include/game/game_utils.hpp` - `isActiveExpansion()`, `isClassicLikeExpansion()`, `isPreWotlk()` -- `src/game/packet_parsers_classic.cpp` / `packet_parsers_tbc.cpp` - Expansion-specific parsing -- `docs/status.md` - Current feature support -- `docs/` directory - Additional protocol documentation diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md deleted file mode 100644 index 4240f97e..00000000 --- a/GETTING_STARTED.md +++ /dev/null @@ -1,226 +0,0 @@ -# Getting Started with WoWee - -WoWee is a native C++ World of Warcraft client that connects to private servers. This guide walks you through setting up and playing WoWee. - -## Prerequisites - -- **World of Warcraft Game Data** (Vanilla 1.12, TBC 2.4.3, or WotLK 3.3.5a) -- **A Private Server** (AzerothCore, TrinityCore, Mangos, or Turtle WoW compatible) -- **System Requirements**: Linux, macOS, or Windows with a Vulkan-capable GPU - -## Installation - -### Step 1: Build WoWee - -See [Building](README.md#building) section in README for detailed build instructions. - -**Quick start (Linux/macOS)**: -```bash -./build.sh -cd build/bin -./wowee -``` - -**Quick start (Windows)**: -```powershell -.\build.ps1 -cd build\bin -.\wowee.exe -``` - -### Step 2: Extract Game Data - -WoWee needs game assets from your WoW installation: - -**Using provided script (Linux/macOS)**: -```bash -./extract_assets.sh /path/to/wow/directory -``` - -**Using provided script (Windows)**: -```powershell -.\extract_assets.ps1 "C:\Games\WoW-3.3.5a\Data" -``` - -**Manual extraction**: -1. Install [StormLib](https://github.com/ladislav-zezula/StormLib) -2. Use `asset_extract` or extract manually to `./Data/`: - ``` - Data/ - ├── manifest.json # File index (generated by asset_extract) - ├── expansions// # Per-expansion config and DB - ├── character/ # Character textures - ├── creature/ # Creature models/textures - ├── interface/ # UI textures and icons - ├── item/ # Item model textures - ├── spell/ # Spell effect models - ├── terrain/ # ADT terrain, WMO, M2 doodads - ├── world/ # World map images - └── sound/ # Audio files - ``` - -### Step 3: Connect to a Server - -1. **Start WoWee** - ```bash - cd build/bin && ./wowee - ``` - -2. **Enter Realm Information** - - Server Address: e.g., `localhost:3724` or `play.example.com:3724` - - WoWee fetches the realm list automatically - - Select your realm and click **Connect** - -3. **Choose Character** - - Select existing character or create new one - - Customize appearance and settings - - Click **Enter World** - -## First Steps in Game - -### Default Controls - -| Action | Key | -|--------|-----| -| Move Forward | W | -| Move Backward | S | -| Strafe Left | A | -| Strafe Right | D | -| Jump | Space | -| Toggle Chat | Enter | -| Open Character Screen | C | -| Open Inventory | I | -| Open All Bags | B | -| Open Spellbook | P | -| Open Talents | N | -| Open Quest Log | L | -| Open World Map | M | -| Toggle Nameplates | V | -| Toggle Raid Frames | F | -| Open Guild Roster | O | -| Open Dungeon Finder | J | -| Open Achievements | Y | -| Open Skills | K | -| Toggle Settings | Escape | -| Target Next Enemy | Tab | -| Target Previous Enemy | Shift+Tab | - -### Customizing Controls - -Press **Escape** → **Keybindings** to customize hotkeys. - -## Recommended First Steps - -### 1. Adjust Graphics Settings -- Press Escape → **Video Settings** -- Select appropriate **Graphics Preset** for your GPU: - - **LOW**: Low-end GPUs or when performance is priority - - **MEDIUM**: Balanced quality and performance - - **HIGH**: Good GPU with modern drivers - - **ULTRA**: High-end GPU for maximum quality - -### 2. Adjust Audio -- Press Escape → **Audio Settings** -- Set **Master Volume** to preferred level -- Adjust individual audio tracks (Music, Ambient, UI, etc.) -- Toggle **Original Soundtrack** if available - -### 3. Configure UI -- Press Escape → **Game Settings** -- Minimap preferences (rotation, square mode, zoom) -- Bag settings (separate windows, compact mode) -- Action bar visibility - -### 4. Complete First Quest -- Talk to nearby NPCs (they have quest markers ! or ?) -- Accept quest, complete objectives, return for reward -- Level up and gain experience - -## Important Notes - -### Data Directory -Game data is loaded from `Data/` subdirectory: -- If running from build folder: `../../Data` (symlinked automatically) -- If running from binary folder: `./Data` (must exist) -- If running in-place: Ensure `Data/` is in correct location - -### Settings -- Settings are saved to `~/.wowee/settings.cfg` (Linux/macOS) -- Or `%APPDATA%\wowee\settings.cfg` (Windows) -- Keybindings, graphics settings, and UI state persist - -### Multi-Expansion Support -WoWee auto-detects expansion from server: -- **Vanilla 1.12** - Original game -- **TBC 2.4.3** - Burning Crusade -- **WotLK 3.3.5a** - Wrath of the Lich King - -You can override with environment variable: -```bash -WOWEE_EXPANSION=tbc ./wowee # Force TBC -``` - -## Troubleshooting - -### "No realm list" or "Connection Failed" -- Check server address is correct -- Verify server is running -- See [Troubleshooting Guide](TROUBLESHOOTING.md#connection-issues) - -### Graphics Errors -- See [Graphics Troubleshooting](TROUBLESHOOTING.md#graphics-issues) -- Start with LOW graphics preset -- Update GPU driver - -### Audio Not Working -- Check system audio is enabled -- Verify audio files are extracted -- See [Audio Troubleshooting](TROUBLESHOOTING.md#audio-issues) - -### General Issues -- Comprehensive troubleshooting: See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) -- Check `logs/wowee.log` in the working directory for errors -- Verify expansion matches server requirements - -## Server Configuration - -### Tested Servers -- **AzerothCore** - Full support, recommended for learning -- **TrinityCore** - Full support, extensive customization -- **Mangos** - Full support, solid foundation -- **Turtle WoW** - Full support, 1.17 custom content - -### Server Requirements -- Must support Vanilla, TBC, or WotLK protocol -- Warden anti-cheat supported (module execution via emulation) -- Network must allow connections to realm list and world server ports - -See [Multi-Expansion Guide](EXPANSION_GUIDE.md) for protocol details. - -## Next Steps - -1. **Explore the World** - Travel to different zones and enjoy the landscape -2. **Join a Guild** - Find other players to group with -3. **Run Dungeons** - Experience instanced content -4. **PvP** - Engage in player-versus-player combat -5. **Twink Alt** - Create additional characters -6. **Customize Settings** - Fine-tune graphics, audio, and UI - -## Getting Help - -- **Game Issues**: See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) -- **Graphics Help**: See [Graphics & Performance](README.md#graphics--performance) section -- **Multi-Expansion**: See [EXPANSION_GUIDE.md](EXPANSION_GUIDE.md) -- **Building Issues**: See [README.md](README.md#building) - -## Tips for Better Performance - -- Start with reasonable graphics preset for your GPU -- Close other applications when testing -- Keep GPU drivers updated -- Use FSR2 (if supported) for smooth 60+ FPS on weaker hardware -- Monitor frame rate with debug overlay (if available) - -## Enjoy! - -WoWee is a project to experience classic World of Warcraft on a modern engine. Have fun exploring Azeroth! diff --git a/PKGBUILD b/PKGBUILD index 15091216..013be0a1 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -9,27 +9,28 @@ arch=('x86_64') url="https://github.com/Kelsidavis/WoWee" license=('MIT') depends=( - 'sdl2' # Windowing and event loop - 'vulkan-icd-loader' # Vulkan runtime (GPU driver communication) - 'openssl' # SRP6a auth protocol (key exchange + RC4 encryption) - 'zlib' # Network packet decompression and Warden module inflate - 'ffmpeg' # Video playback (login cinematics) - 'unicorn' # Warden anti-cheat x86 emulation (cross-platform, no Wine) - 'libx11' # X11 windowing support - 'stormlib' # AUR — MPQ extraction (wowee-extract-assets uses libstorm.so) + 'sdl2' + 'vulkan-icd-loader' + 'openssl' + 'zlib' + 'ffmpeg' + 'unicorn' + 'glew' + 'libx11' + 'stormlib' # AUR — required at runtime by wowee-extract-assets (libstorm.so) ) makedepends=( - 'git' # Clone submodules (imgui, vk-bootstrap) - 'cmake' # Build system - 'pkgconf' # Dependency detection - 'glm' # Header-only math library (vectors, matrices, quaternions) - 'vulkan-headers' # Vulkan API definitions (build-time only) - 'shaderc' # GLSL → SPIR-V shader compilation - 'python' # Opcode registry generation and DBC validation scripts + 'git' + 'cmake' + 'pkgconf' + 'glm' + 'vulkan-headers' + 'shaderc' + 'python' ) provides=('wowee') conflicts=('wowee') -source=("${pkgname}::git+https://github.com/Kelsidavis/WoWee.git#branch=master" +source=("${pkgname}::git+https://github.com/Kelsidavis/WoWee.git#branch=main" "git+https://github.com/ocornut/imgui.git" "git+https://github.com/charles-lunarg/vk-bootstrap.git") sha256sums=('SKIP' 'SKIP' 'SKIP') diff --git a/README.md b/README.md index 69e0a6dd..54ae7eaa 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A native C++ World of Warcraft client with a custom Vulkan renderer. [![Sponsor](https://img.shields.io/github/sponsors/Kelsidavis?label=Sponsor&logo=GitHub)](https://github.com/sponsors/Kelsidavis) -[![Discord](https://img.shields.io/discord/1?label=Discord&logo=discord)](https://discord.gg/PSdMPS8uje) +[![Discord](https://img.shields.io/discord/1?label=Discord&logo=discord)](https://discord.gg/SDqjA79B) [![Watch the video](https://img.youtube.com/vi/B-jtpPmiXGM/maxresdefault.jpg)](https://youtu.be/B-jtpPmiXGM) @@ -19,15 +19,13 @@ Protocol Compatible with **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a**. > **Legal Disclaimer**: This is an educational/research project. It does not include any Blizzard Entertainment assets, data files, or proprietary code. World of Warcraft and all related assets are the property of Blizzard Entertainment, Inc. This project is not affiliated with or endorsed by Blizzard Entertainment. Users are responsible for supplying their own legally obtained game data files and for ensuring compliance with all applicable laws in their jurisdiction. -## Status & Direction (2026-03-30) +## Status & Direction (2026-03-07) -- **Compatibility**: **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a** are all supported via expansion profiles and per-expansion packet parsers. All three expansions are roughly on par. -- **Tested against**: AzerothCore/ChromieCraft, TrinityCore, Mangos, and Turtle WoW (1.17). -- **Current focus**: code quality (SOLID decomposition, documentation), rendering stability, and multi-expansion coverage. +- **Compatibility**: **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a** are all supported via expansion profiles and per-expansion packet parsers (`src/game/packet_parsers_classic.cpp`, `src/game/packet_parsers_tbc.cpp`). All three expansions are roughly on par — no single one is significantly more complete than the others. +- **Tested against**: AzerothCore, TrinityCore, Mangos, and Turtle WoW (1.17). +- **Current focus**: instance dungeons, visual accuracy (lava/water, shadow mapping, WMO interiors), and multi-expansion coverage. - **Warden**: Full module execution via Unicorn Engine CPU emulation. Decrypts (RC4→RSA→zlib), parses and relocates the PE module, executes via x86 emulation with Windows API interception. Module cache at `~/.local/share/wowee/warden_cache/`. -- **CI**: GitHub Actions builds for Linux (x86-64, ARM64), Windows (MSYS2 x86-64 + ARM64), and macOS (ARM64). Security scans via CodeQL, Semgrep, and sanitizers. -- **Container builds**: Multi-platform Docker build system for Linux, macOS (arm64/x86_64 via osxcross), and Windows (LLVM-MinGW) cross-compilation. -- **Release**: v1.8.9-preview — 530+ WoW API functions, 140+ events, 664 opcode handlers. +- **CI**: GitHub Actions builds for Linux (x86-64, ARM64), Windows (MSYS2), and macOS (ARM64). Security scans via CodeQL, Semgrep, and sanitizers. ## Features @@ -54,7 +52,7 @@ Protocol Compatible with **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a**. - **Movement** -- WASD movement, camera orbit, spline path following, transport riding (trams, ships, zeppelins), movement ACK responses - **Combat** -- Auto-attack, spell casting with cooldowns, damage calculation, death handling, spirit healer resurrection - **Targeting** -- Tab-cycling with hostility filtering, click-to-target, faction-based hostility (using Faction.dbc) -- **Inventory** -- 23 equipment slots, 16 backpack slots, drag-drop, auto-equip, item tooltips with weapon damage/speed, server-synced bag sort (quality/type/stack), independent bag windows +- **Inventory** -- 23 equipment slots, 16 backpack slots, drag-drop, auto-equip, item tooltips with weapon damage/speed - **Bank** -- Full bank support for all expansions, bag slots, drag-drop, right-click deposit (non-equippable items) - **Spells** -- Spellbook with specialty, general, profession, mount, and companion tabs; drag-drop to action bar; item use support - **Talents** -- Talent tree UI with proper visuals and functionality @@ -68,34 +66,10 @@ Protocol Compatible with **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a**. - **Gossip** -- NPC interaction, dialogue options - **Chat** -- Tabs/channels, emotes, chat bubbles, clickable URLs, clickable item links with tooltips - **Party** -- Group invites, party list, out-of-range member health via SMSG_PARTY_MEMBER_STATS -- **Pets** -- Pet tracking via SMSG_PET_SPELLS, action bar (10 slots with icon/autocast tinting/tooltips), dismiss button -- **Map Exploration** -- Subzone-level fog-of-war reveal, world map with continent/zone views, quest POI markers, taxi node markers, party member dots -- **NPC Voices** -- Race/gender-specific NPC greeting, farewell, vendor, pissed, aggro, and flee sounds for all playable races including Blood Elf and Draenei +- **Pets** -- Pet tracking via SMSG_PET_SPELLS, dismiss pet button +- **Map Exploration** -- Subzone-level fog-of-war reveal matching retail behavior - **Warden** -- Warden anti-cheat module execution via Unicorn Engine x86 emulation (cross-platform, no Wine) -- **UI** -- Loading screens with progress bar, settings window with graphics quality presets (LOW/MEDIUM/HIGH/ULTRA), shadow distance slider, minimap with zoom/rotation/square mode, top-right minimap mute speaker, separate bag windows with compact-empty mode (aggregate view) - -## Graphics & Performance - -### Quality Presets - -WoWee includes four built-in graphics quality presets to help you quickly balance visual quality and performance: - -| Preset | Shadows | MSAA | Normal Mapping | Clutter Density | -|--------|---------|------|----------------|-----------------| -| **LOW** | Off | Off | Disabled | 25% | -| **MEDIUM** | 200m distance | 2x | Basic | 60% | -| **HIGH** | 350m distance | 4x | Full (0.8x) | 100% | -| **ULTRA** | 500m distance | 8x | Enhanced (1.2x) | 150% | - -Press Escape to open **Video Settings** and select a preset, or adjust individual settings for a custom configuration. - -### Performance Tips - -- Start with **LOW** or **MEDIUM** if you experience frame drops -- Shadows and MSAA have the largest impact on performance -- Reduce **shadow distance** if shadows cause issues -- Disable **water refraction** if you encounter GPU errors (requires FSR to be active) -- Use **FSR2** (built-in upscaling) for better frame rates on modern GPUs +- **UI** -- Loading screens with progress bar, settings window (shadow distance slider), minimap with zoom/rotation/square mode, top-right minimap mute speaker, separate bag windows with compact-empty mode (aggregate view) ## Building @@ -347,13 +321,3 @@ This project does not include any Blizzard Entertainment proprietary data, asset ## Known Issues MANY issues this is actively under development - -## Star History - - - - - - Star History Chart - - diff --git a/TESTING.md b/TESTING.md deleted file mode 100644 index 3846b15d..00000000 --- a/TESTING.md +++ /dev/null @@ -1,390 +0,0 @@ -# WoWee Testing Guide - -This document covers everything needed to build, run, lint, and extend the WoWee test suite. - ---- - -## Table of Contents - -1. [Overview](#overview) -2. [Prerequisites](#prerequisites) -3. [Test Suite Layout](#test-suite-layout) -4. [Building the Tests](#building-the-tests) - - [Release Build (normal)](#release-build-normal) - - [Debug + ASAN/UBSan Build](#debug--asanubsan-build) -5. [Running Tests](#running-tests) - - [test.sh — the unified entry point](#testsh--the-unified-entry-point) - - [Running directly with ctest](#running-directly-with-ctest) -6. [Lint (clang-tidy)](#lint-clang-tidy) - - [Running lint](#running-lint) - - [Applying auto-fixes](#applying-auto-fixes) - - [Configuration (.clang-tidy)](#configuration-clang-tidy) -7. [ASAN / UBSan](#asan--ubsan) -8. [Adding New Tests](#adding-new-tests) -9. [CI Reference](#ci-reference) - ---- - -## Overview - -WoWee uses **Catch2 v3** (amalgamated) for unit testing and **clang-tidy** for static analysis. The `test.sh` script is the single entry point for both. - -| Command | What it does | -|---|---| -| `./test.sh` | Runs both unit tests (Release) and lint | -| `./test.sh --test` | Runs unit tests only (Release build) | -| `./test.sh --lint` | Runs clang-tidy only | -| `./test.sh --asan` | Runs unit tests under ASAN + UBSan (Debug build) | -| `FIX=1 ./test.sh --lint` | Applies clang-tidy auto-fixes in-place | - -All commands exit non-zero on any failure. - ---- - -## Prerequisites - -The test suite requires the same base toolchain used to build the project. See [BUILD_INSTRUCTIONS.md](BUILD_INSTRUCTIONS.md) for platform-specific dependency installation. - -### Linux (Ubuntu / Debian) - -```bash -sudo apt update -sudo apt install -y \ - build-essential cmake pkg-config git \ - libssl-dev \ - clang-tidy -``` - -### Linux (Arch) - -```bash -sudo pacman -S --needed base-devel cmake pkgconf git openssl clang -``` - -### macOS - -```bash -brew install cmake openssl@3 llvm -# Add LLVM tools to PATH so clang-tidy is found: -export PATH="$(brew --prefix llvm)/bin:$PATH" -``` - -### Windows (MSYS2) - -Install the full toolchain as described in `BUILD_INSTRUCTIONS.md`, then add: - -```bash -pacman -S --needed mingw-w64-x86_64-clang-tools-extra -``` - ---- - -## Test Suite Layout - -``` -tests/ - CMakeLists.txt — CMake test configuration - test_packet.cpp — Network packet encode/decode - test_srp.cpp — SRP-6a authentication math (requires OpenSSL) - test_opcode_table.cpp — Opcode registry lookup - test_entity.cpp — ECS entity basics - test_dbc_loader.cpp — DBC binary file parsing - test_m2_structs.cpp — M2 model struct layout / alignment - test_blp_loader.cpp — BLP texture file parsing - test_frustum.cpp — View-frustum culling math -``` - -The Catch2 v3 amalgamated source lives at: - -``` -extern/catch2/ - catch_amalgamated.hpp - catch_amalgamated.cpp -``` - ---- - -## Building the Tests - -Tests are _not_ built by default. Enable them with `-DWOWEE_BUILD_TESTS=ON`. - -### Release Build (normal) - -> **Note:** Per project rules, always use `rebuild.sh` for a full clean build. Direct `cmake --build` is fine for test-only incremental builds. - -```bash -# Configure (only needed once) -cmake -B build -DCMAKE_BUILD_TYPE=Release -DWOWEE_BUILD_TESTS=ON - -# Build test targets (fast — only compiles tests and Catch2) -cmake --build build --target \ - test_packet test_srp test_opcode_table test_entity \ - test_dbc_loader test_m2_structs test_blp_loader test_frustum -``` - -Or simply run a full rebuild (builds everything including the main binary): - -```bash -./rebuild.sh # ~10 minutes — see BUILD_INSTRUCTIONS.md -``` - -### Debug + ASAN/UBSan Build - -A separate CMake build directory is used so ASAN flags do not pollute the Release binary. - -```bash -cmake -B build_asan \ - -DCMAKE_BUILD_TYPE=Debug \ - -DWOWEE_ENABLE_ASAN=ON \ - -DWOWEE_BUILD_TESTS=ON - -cmake --build build_asan --target \ - test_packet test_srp test_opcode_table test_entity \ - test_dbc_loader test_m2_structs test_blp_loader test_frustum -``` - -CMake will print: `Test targets: ASAN + UBSan ENABLED` when configured correctly. - ---- - -## Running Tests - -### test.sh — the unified entry point - -`test.sh` is the recommended way to run tests and/or lint. It handles build-directory discovery, dependency checking, and exit-code aggregation across both steps. - -```bash -# Run everything (tests + lint) — default when no flags are given -./test.sh - -# Tests only (Release build) -./test.sh --test - -# Tests only under ASAN+UBSan (Debug build — requires build_asan/) -./test.sh --asan - -# Lint only -./test.sh --lint - -# Both tests and lint explicitly -./test.sh --test --lint - -# Usage summary -./test.sh --help -``` - -**Exit codes:** - -| Outcome | Exit code | -|---|---| -| All tests passed, lint clean | `0` | -| Any test failed | `1` | -| Any lint diagnostic | `1` | -| Both test failure and lint issues | `1` | - -### Running directly with ctest - -```bash -# Release build -cd build -ctest --output-on-failure - -# ASAN build -cd build_asan -ctest --output-on-failure - -# Run one specific test suite by name -ctest --output-on-failure -R srp - -# Verbose output (shows every SECTION and REQUIRE) -ctest --output-on-failure -V -``` - -You can also run a test binary directly for detailed Catch2 output: - -```bash -./build/bin/test_srp -./build/bin/test_srp --reporter console -./build/bin/test_srp "[authentication]" # run only tests tagged [authentication] -``` - ---- - -## Lint (clang-tidy) - -The project uses clang-tidy to enforce C++20 best practices on all first-party sources under `src/`. Third-party code (anything in `extern/`) and generated files are excluded. - -### Running lint - -```bash -./test.sh --lint -``` - -Under the hood the script: - -1. Locates `clang-tidy` (tries versions 14–18, then `clang-tidy`). -2. Uses `run-clang-tidy` for parallel execution when available; falls back to sequential. -3. Reads `build/compile_commands.json` (generated by CMake) for compiler flags. -4. Feeds GCC stdlib include paths as `-isystem` extras so clang-tidy can resolve ``, ``, etc. when the compile-commands were generated with GCC. - -`compile_commands.json` is regenerated automatically by any CMake configure step. If you only want to update it without rebuilding: - -```bash -cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -``` - -### Applying auto-fixes - -Some clang-tidy checks can apply fixes automatically (e.g. `modernize-*`, `readability-*`): - -```bash -FIX=1 ./test.sh --lint -``` - -> **Caution:** Review the diff before committing — automatic fixes occasionally produce non-idiomatic results in complex template code. - -### Configuration (.clang-tidy) - -The active check set is defined in [.clang-tidy](.clang-tidy) at the repository root. - -**Enabled check categories:** - -| Category | What it catches | -|---|---| -| `bugprone-*` | Common bug patterns (signed overflow, misplaced `=`, etc.) | -| `clang-analyzer-*` | Deep flow-analysis: null dereferences, memory leaks, dead stores | -| `performance-*` | Unnecessary copies, inefficient STL usage | -| `modernize-*` (subset) | Pre-C++11 patterns that should use modern equivalents | -| `readability-*` (subset) | Control-flow simplification, redundant code | - -**Notable suppressions** (see `.clang-tidy` for details): - -| Suppressed check | Reason | -|---|---| -| `bugprone-easily-swappable-parameters` | High false-positive rate in graphics/math APIs | -| `clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling` | Intentional low-level buffer code in rendering | -| `performance-avoid-endl` | `std::endl` is used intentionally for logger flushing | - -To suppress a specific warning inline, use: - -```cpp -// NOLINT(bugprone-narrowing-conversions) -uint8_t byte = static_cast(value); // NOLINT -``` - ---- - -## ASAN / UBSan - -AddressSanitizer (ASAN) and Undefined Behaviour Sanitizer (UBSan) are applied to all test targets when `WOWEE_ENABLE_ASAN=ON`. - -Both the test executables **and** the `catch2_main` static library are recompiled with: - -``` --fsanitize=address,undefined -fno-omit-frame-pointer -``` - -This means any heap overflow, stack buffer overflow, use-after-free, null dereference, signed integer overflow, or misaligned access detected during a test will abort the process and print a human-readable report to stderr. - -### Workflow - -```bash -# 1. Configure once (only needs to be re-run when CMakeLists.txt changes) -cmake -B build_asan \ - -DCMAKE_BUILD_TYPE=Debug \ - -DWOWEE_ENABLE_ASAN=ON \ - -DWOWEE_BUILD_TESTS=ON - -# 2. Build test binaries (fast incremental after the first build) -cmake --build build_asan --target test_packet test_srp # etc. - -# 3. Run -./test.sh --asan -``` - -### Interpreting ASAN output - -A failing ASAN report looks like: - -``` -==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000010 -READ of size 4 at 0x602000000010 thread T0 - #0 0x... in PacketBuffer::read_uint32 src/network/packet.cpp:42 - #1 0x... in test_packet tests/test_packet.cpp:88 -``` - -Address the issue in the source file and re-run. Do **not** suppress ASAN reports without a code fix. - ---- - -## Adding New Tests - -1. **Create** `tests/test_.cpp` with a standard Catch2 v3 structure: - -```cpp -#include "catch_amalgamated.hpp" - -TEST_CASE("SomeFeature does X", "[tag]") { - REQUIRE(1 + 1 == 2); -} -``` - -2. **Register** the test in `tests/CMakeLists.txt` following the existing pattern: - -```cmake -# ── test_ ────────────────────────────────────────────── -add_executable(test_ - test_.cpp - ${TEST_COMMON_SOURCES} - ${CMAKE_SOURCE_DIR}/src//.cpp # source under test -) -target_include_directories(test_ PRIVATE ${TEST_INCLUDE_DIRS}) -target_include_directories(test_ SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) -target_link_libraries(test_ PRIVATE catch2_main) -add_test(NAME COMMAND test_) -register_test_target(test_) # required — enables ASAN propagation -``` - -3. **Build** and verify: - -```bash -cmake --build build --target test_ -./test.sh --test -``` - -The `register_test_target()` macro call is **mandatory** — without it the new test will not receive ASAN/UBSan flags when `WOWEE_ENABLE_ASAN=ON`. - ---- - -## CI Reference - -The following commands map to typical CI jobs: - -| Job | Command | -|---|---| -| Unit tests (Release) | `./test.sh --test` | -| Unit tests (ASAN+UBSan) | `./test.sh --asan` | -| Lint | `./test.sh --lint` | -| Full check (tests + lint) | `./test.sh` | - -**Configuring the ASAN job in CI:** - -```yaml -- name: Configure ASAN build - run: | - cmake -B build_asan \ - -DCMAKE_BUILD_TYPE=Debug \ - -DWOWEE_ENABLE_ASAN=ON \ - -DWOWEE_BUILD_TESTS=ON - -- name: Build test targets - run: | - cmake --build build_asan --target \ - test_packet test_srp test_opcode_table test_entity \ - test_dbc_loader test_m2_structs test_blp_loader test_frustum - -- name: Run ASAN tests - run: ./test.sh --asan -``` - -> See [BUILD_INSTRUCTIONS.md](BUILD_INSTRUCTIONS.md) for full platform dependency installation steps required before any CI job. diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md deleted file mode 100644 index 3673118d..00000000 --- a/TROUBLESHOOTING.md +++ /dev/null @@ -1,184 +0,0 @@ -# Troubleshooting Guide - -This guide covers common issues and solutions for WoWee. - -## Connection Issues - -### "Authentication Failed" -- **Cause**: Incorrect server address, expired realm list, or version mismatch -- **Solution**: - 1. Verify server address in realm list is correct - 2. Ensure your WoW data directory is for the correct expansion (Vanilla/TBC/WotLK) - 3. Check that the emulator server is running and reachable - -### "Realm List Connection Failed" -- **Cause**: Server is down, firewall blocking connection, or DNS issue -- **Solution**: - 1. Verify server IP/hostname is correct - 2. Test connectivity: `ping realm-server-address` - 3. Check firewall rules for port 3724 (auth) and 8085 (realm list) - 4. Try using IP address instead of hostname (DNS issues) - -### "Connection Lost During Login" -- **Cause**: Network timeout, server overload, or incompatible protocol version -- **Solution**: - 1. Check your network connection - 2. Reduce number of assets loading (lower graphics preset) - 3. Verify server supports this expansion version - -## Graphics Issues - -### "VK_ERROR_DEVICE_LOST" or Client Crashes -- **Cause**: GPU driver issue, insufficient VRAM, or graphics feature incompatibility -- **Solution**: - 1. **Immediate**: Disable advanced graphics features: - - Press Escape → Video Settings - - Set graphics preset to **LOW** - - Disable Water Refraction (requires FSR) - - Disable MSAA (set to Off) - 2. **Medium term**: Update GPU driver to latest version - 3. **Verify**: Use a graphics test tool to ensure GPU stability - 4. **If persists**: Try FSR2 disabled mode - check renderer logs - -### Black Screen or Rendering Issues -- **Cause**: Missing shaders, GPU memory allocation failure, or incorrect graphics settings -- **Solution**: - 1. Check logs: Look in `~/.wowee/logs/` for error messages - 2. Verify shaders compiled: Check for `.spv` files in `assets/shaders/compiled/` - 3. Reduce shadow distance: Press Escape → Video Settings → Lower shadow distance from 300m to 100m - 4. Disable shadows entirely if issues persist - -### Low FPS or Frame Stuttering -- **Cause**: Too high graphics settings for your GPU, memory fragmentation, or asset loading -- **Solution**: - 1. Apply lower graphics preset: Escape → LOW or MEDIUM - 2. Disable MSAA: Set to "Off" - 3. Reduce draw distance: Move further away from complex areas - 4. Close other applications consuming GPU memory - 5. Check CPU usage - if high, reduce number of visible entities - -### Water/Terrain Flickering -- **Cause**: Shadow mapping artifacts, terrain LOD issues, or GPU memory pressure -- **Solution**: - 1. Increase shadow distance slightly (150m to 200m) - 2. Disable shadows entirely as last resort - 3. Check GPU memory usage - -## Audio Issues - -### No Sound -- **Cause**: Audio initialization failed, missing audio data, or incorrect mixer setup -- **Solution**: - 1. Check system audio is working: Test with another application - 2. Verify audio files extracted: Check for `.wav` files in `Data/Audio/` - 3. Unmute audio: Look for speaker icon in minimap (top-right) - click to unmute - 4. Check settings: Escape → Audio Settings → Master Volume > 0 - -### Sound Cutting Out -- **Cause**: Audio buffer underrun, too many simultaneous sounds, or driver issue -- **Solution**: - 1. Lower audio volume: Escape → Audio Settings → Reduce Master Volume - 2. Disable distant ambient sounds: Reduce Ambient Volume - 3. Reduce number of particle effects - 4. Update audio driver - -## Gameplay Issues - -### Character Stuck or Not Moving -- **Cause**: Network synchronization issue, collision bug, or server desync -- **Solution**: - 1. Try pressing Escape to deselect any target, then move - 2. Jump (Spacebar) to test physics - 3. Reload the character: Press Escape → Disconnect → Reconnect - 4. Check for transport/vehicle state: Press 'R' to dismount if applicable - -### Spells Not Casting or Showing "Error" -- **Cause**: Cooldown, mana insufficient, target out of range, or server desync -- **Solution**: - 1. Verify spell is off cooldown (action bar shows availability) - 2. Check mana/energy: Look at player frame (top-left) - 3. Verify target range: Hover action bar button for range info - 4. Check server logs for error messages (combat log will show reason) - -### Quests Not Updating -- **Cause**: Objective already completed in different session, quest giver not found, or network desync -- **Solution**: - 1. Check quest objective: Open quest log (Q key) → Verify objective requirements - 2. Re-interact with NPC to trigger update packet - 3. Reload character if issue persists - -### Items Not Appearing in Inventory -- **Cause**: Inventory full, item filter active, or network desync -- **Solution**: - 1. Check inventory space: Open inventory (B key) → Count free slots - 2. Verify item isn't already there: Search inventory for item name - 3. Check if bags are full: Open bag windows, consolidate items - 4. Reload character if item is still missing - -## Performance Optimization - -### For Low-End GPUs -``` -Graphics Preset: LOW -- Shadows: OFF -- MSAA: OFF -- Normal Mapping: Disabled -- Clutter Density: 25% -- Draw Distance: Minimum -- Particles: Reduced -``` - -### For Mid-Range GPUs -``` -Graphics Preset: MEDIUM -- Shadows: 200m -- MSAA: 2x -- Normal Mapping: Basic -- Clutter Density: 60% -- FSR2: Enabled (if desired) -``` - -### For High-End GPUs -``` -Graphics Preset: HIGH or ULTRA -- Shadows: 350-500m -- MSAA: 4-8x -- Normal Mapping: Full (1.2x strength) -- Clutter Density: 100-150% -- FSR2: Optional (for 4K smoothness) -``` - -## Getting Help - -### Check Logs -Detailed logs are saved to `logs/wowee.log` in the working directory (typically `build/bin/`). - -Include relevant log entries when reporting issues. - -### Check Server Compatibility -- **AzerothCore**: Full support -- **TrinityCore**: Full support -- **Mangos**: Full support -- **Turtle WoW**: Full support (1.17) - -### Report Issues -If you encounter a bug: -1. Enable logging: Watch console for error messages -2. Reproduce the issue consistently -3. Gather system info: GPU, driver version, OS -4. Check if issue is expansion-specific (Classic/TBC/WotLK) -5. Report with detailed steps to reproduce - -### Clear Cache -If experiencing persistent issues, clear WoWee's cache: -```bash -# Linux/macOS -rm -rf ~/.wowee/warden_cache/ -rm -rf ~/.wowee/asset_cache/ - -# Windows -rmdir %APPDATA%\wowee\warden_cache\ /s -rmdir %APPDATA%\wowee\asset_cache\ /s -``` - -Then restart WoWee to rebuild cache. diff --git a/assets/shaders/basic.frag b/assets/shaders/basic.frag new file mode 100644 index 00000000..158eb776 --- /dev/null +++ b/assets/shaders/basic.frag @@ -0,0 +1,38 @@ +#version 330 core + +in vec3 FragPos; +in vec3 Normal; +in vec2 TexCoord; + +out vec4 FragColor; + +uniform vec3 uLightPos; +uniform vec3 uViewPos; +uniform vec4 uColor; +uniform sampler2D uTexture; +uniform bool uUseTexture; + +void main() { + // Ambient + vec3 ambient = 0.3 * vec3(1.0); + + // Diffuse + vec3 norm = normalize(Normal); + vec3 lightDir = normalize(uLightPos - FragPos); + float diff = max(dot(norm, lightDir), 0.0); + vec3 diffuse = diff * vec3(1.0); + + // Specular + vec3 viewDir = normalize(uViewPos - FragPos); + vec3 reflectDir = reflect(-lightDir, norm); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0); + vec3 specular = 0.5 * spec * vec3(1.0); + + vec3 result = (ambient + diffuse + specular); + + if (uUseTexture) { + FragColor = texture(uTexture, TexCoord) * vec4(result, 1.0); + } else { + FragColor = uColor * vec4(result, 1.0); + } +} diff --git a/assets/shaders/basic.vert b/assets/shaders/basic.vert new file mode 100644 index 00000000..141f2270 --- /dev/null +++ b/assets/shaders/basic.vert @@ -0,0 +1,22 @@ +#version 330 core + +layout (location = 0) in vec3 aPosition; +layout (location = 1) in vec3 aNormal; +layout (location = 2) in vec2 aTexCoord; + +out vec3 FragPos; +out vec3 Normal; +out vec2 TexCoord; + +uniform mat4 uModel; +uniform mat4 uView; +uniform mat4 uProjection; + +void main() { + FragPos = vec3(uModel * vec4(aPosition, 1.0)); + // Use mat3(uModel) directly - avoids expensive inverse() per vertex + Normal = mat3(uModel) * aNormal; + TexCoord = aTexCoord; + + gl_Position = uProjection * uView * vec4(FragPos, 1.0); +} diff --git a/assets/shaders/character.frag.glsl b/assets/shaders/character.frag.glsl index a4bdada6..b28fa314 100644 --- a/assets/shaders/character.frag.glsl +++ b/assets/shaders/character.frag.glsl @@ -126,14 +126,7 @@ void main() { vec4 texColor = texture(uTexture, finalUV); - if (alphaTest != 0) { - // Screen-space sharpened alpha for alpha-to-coverage anti-aliasing. - // Rescales alpha so the 0.5 cutoff maps to exactly the texel boundary, - // giving smooth edges when MSAA + alpha-to-coverage is active. - float aGrad = fwidth(texColor.a); - texColor.a = clamp((texColor.a - 0.5) / max(aGrad, 0.001) * 0.5 + 0.5, 0.0, 1.0); - if (texColor.a < 1.0 / 255.0) discard; - } + if (alphaTest != 0 && texColor.a < 0.5) discard; if (colorKeyBlack != 0) { float lum = dot(texColor.rgb, vec3(0.299, 0.587, 0.114)); float ck = smoothstep(0.12, 0.30, lum); @@ -176,7 +169,7 @@ void main() { if (proj.x >= 0.0 && proj.x <= 1.0 && proj.y >= 0.0 && proj.y <= 1.0 && proj.z >= 0.0 && proj.z <= 1.0) { - float bias = max(0.0005 * (1.0 - abs(dot(norm, ldir))), 0.00005); + float bias = max(0.0005 * (1.0 - dot(norm, ldir)), 0.00005); shadow = sampleShadowPCF(uShadowMap, vec3(proj.xy, proj.z - bias)); } shadow = mix(1.0, shadow, shadowParams.y); diff --git a/assets/shaders/character.frag.spv b/assets/shaders/character.frag.spv index 7277d275..99c68edf 100644 Binary files a/assets/shaders/character.frag.spv and b/assets/shaders/character.frag.spv differ diff --git a/assets/shaders/fsr2_sharpen.frag.glsl b/assets/shaders/fsr2_sharpen.frag.glsl index 392a3a37..9cd1271c 100644 --- a/assets/shaders/fsr2_sharpen.frag.glsl +++ b/assets/shaders/fsr2_sharpen.frag.glsl @@ -10,7 +10,9 @@ layout(push_constant) uniform PushConstants { } pc; void main() { - vec2 tc = TexCoord; + // Undo the vertex shader Y flip (postprocess.vert flips for Vulkan overlay, + // but we need standard UV coords for texture sampling) + vec2 tc = vec2(TexCoord.x, 1.0 - TexCoord.y); vec2 texelSize = pc.params.xy; float sharpness = pc.params.z; diff --git a/assets/shaders/fsr2_sharpen.frag.spv b/assets/shaders/fsr2_sharpen.frag.spv index 24519b93..20672a9e 100644 Binary files a/assets/shaders/fsr2_sharpen.frag.spv and b/assets/shaders/fsr2_sharpen.frag.spv differ diff --git a/assets/shaders/fsr_easu.frag.glsl b/assets/shaders/fsr_easu.frag.glsl index 6a36be75..20e5ed32 100644 --- a/assets/shaders/fsr_easu.frag.glsl +++ b/assets/shaders/fsr_easu.frag.glsl @@ -21,7 +21,9 @@ vec3 fsrFetch(vec2 p, vec2 off) { } void main() { - vec2 tc = TexCoord; + // Undo the vertex shader Y flip (postprocess.vert flips for Vulkan overlay, + // but we need standard UV coords for texture sampling) + vec2 tc = vec2(TexCoord.x, 1.0 - TexCoord.y); // Map output pixel to input space vec2 pp = tc * fsr.con2.xy; // output pixel position diff --git a/assets/shaders/fsr_easu.frag.spv b/assets/shaders/fsr_easu.frag.spv index 12780757..5ddc2ea8 100644 Binary files a/assets/shaders/fsr_easu.frag.spv and b/assets/shaders/fsr_easu.frag.spv differ diff --git a/assets/shaders/fxaa.frag.glsl b/assets/shaders/fxaa.frag.glsl deleted file mode 100644 index 3da10854..00000000 --- a/assets/shaders/fxaa.frag.glsl +++ /dev/null @@ -1,155 +0,0 @@ -#version 450 - -// FXAA 3.11 — Fast Approximate Anti-Aliasing post-process pass. -// Reads the resolved scene color and outputs a smoothed result. -// Push constant: rcpFrame = vec2(1/width, 1/height), sharpness (0=off, 2=max), desaturate (1=ghost grayscale). - -layout(set = 0, binding = 0) uniform sampler2D uScene; - -layout(location = 0) in vec2 TexCoord; -layout(location = 0) out vec4 outColor; - -layout(push_constant) uniform PC { - vec2 rcpFrame; - float sharpness; // 0 = no sharpen, 2 = max (matches FSR2 RCAS range) - float desaturate; // 1 = full grayscale (ghost mode), 0 = normal color -} pc; - -// Quality tuning -#define FXAA_EDGE_THRESHOLD (1.0/8.0) // minimum edge contrast to process -#define FXAA_EDGE_THRESHOLD_MIN (1.0/24.0) // ignore very dark regions -#define FXAA_SEARCH_STEPS 12 -#define FXAA_SEARCH_THRESHOLD (1.0/4.0) -#define FXAA_SUBPIX 0.75 -#define FXAA_SUBPIX_TRIM (1.0/4.0) -#define FXAA_SUBPIX_TRIM_SCALE (1.0/(1.0 - FXAA_SUBPIX_TRIM)) -#define FXAA_SUBPIX_CAP (3.0/4.0) - -float luma(vec3 c) { - return dot(c, vec3(0.299, 0.587, 0.114)); -} - -void main() { - vec2 uv = TexCoord; - vec2 rcp = pc.rcpFrame; - - // --- Centre and cardinal neighbours --- - vec3 rgbM = texture(uScene, uv).rgb; - vec3 rgbN = texture(uScene, uv + vec2( 0.0, -1.0) * rcp).rgb; - vec3 rgbS = texture(uScene, uv + vec2( 0.0, 1.0) * rcp).rgb; - vec3 rgbE = texture(uScene, uv + vec2( 1.0, 0.0) * rcp).rgb; - vec3 rgbW = texture(uScene, uv + vec2(-1.0, 0.0) * rcp).rgb; - - float lumaN = luma(rgbN); - float lumaS = luma(rgbS); - float lumaE = luma(rgbE); - float lumaW = luma(rgbW); - float lumaM = luma(rgbM); - - float lumaMin = min(lumaM, min(min(lumaN, lumaS), min(lumaE, lumaW))); - float lumaMax = max(lumaM, max(max(lumaN, lumaS), max(lumaE, lumaW))); - float range = lumaMax - lumaMin; - - // Early exit on smooth regions - if (range < max(FXAA_EDGE_THRESHOLD_MIN, lumaMax * FXAA_EDGE_THRESHOLD)) { - outColor = vec4(rgbM, 1.0); - return; - } - - // --- Diagonal neighbours --- - vec3 rgbNW = texture(uScene, uv + vec2(-1.0, -1.0) * rcp).rgb; - vec3 rgbNE = texture(uScene, uv + vec2( 1.0, -1.0) * rcp).rgb; - vec3 rgbSW = texture(uScene, uv + vec2(-1.0, 1.0) * rcp).rgb; - vec3 rgbSE = texture(uScene, uv + vec2( 1.0, 1.0) * rcp).rgb; - - float lumaNW = luma(rgbNW); - float lumaNE = luma(rgbNE); - float lumaSW = luma(rgbSW); - float lumaSE = luma(rgbSE); - - // --- Sub-pixel blend factor --- - float lumaL = (lumaN + lumaS + lumaE + lumaW) * 0.25; - float rangeL = abs(lumaL - lumaM); - float blendL = max(0.0, (rangeL / range) - FXAA_SUBPIX_TRIM) * FXAA_SUBPIX_TRIM_SCALE; - blendL = min(FXAA_SUBPIX_CAP, blendL) * FXAA_SUBPIX; - - // --- Edge orientation (horizontal vs. vertical) --- - float edgeHorz = - abs(-2.0*lumaW + lumaNW + lumaSW) - + 2.0*abs(-2.0*lumaM + lumaN + lumaS) - + abs(-2.0*lumaE + lumaNE + lumaSE); - float edgeVert = - abs(-2.0*lumaS + lumaSW + lumaSE) - + 2.0*abs(-2.0*lumaM + lumaW + lumaE) - + abs(-2.0*lumaN + lumaNW + lumaNE); - - bool horzSpan = (edgeHorz >= edgeVert); - float lengthSign = horzSpan ? rcp.y : rcp.x; - - float luma1 = horzSpan ? lumaN : lumaW; - float luma2 = horzSpan ? lumaS : lumaE; - float grad1 = abs(luma1 - lumaM); - float grad2 = abs(luma2 - lumaM); - lengthSign = (grad1 >= grad2) ? -lengthSign : lengthSign; - - // --- Edge search --- - vec2 posB = uv; - vec2 offNP = horzSpan ? vec2(rcp.x, 0.0) : vec2(0.0, rcp.y); - if (!horzSpan) posB.x += lengthSign * 0.5; - if ( horzSpan) posB.y += lengthSign * 0.5; - - float lumaMLSS = lumaM - (luma1 + luma2) * 0.5; - float gradientScaled = max(grad1, grad2) * 0.25; - - vec2 posN = posB - offNP; - vec2 posP = posB + offNP; - bool done1 = false, done2 = false; - float lumaEnd1 = 0.0, lumaEnd2 = 0.0; - - for (int i = 0; i < FXAA_SEARCH_STEPS; ++i) { - if (!done1) lumaEnd1 = luma(texture(uScene, posN).rgb) - lumaMLSS; - if (!done2) lumaEnd2 = luma(texture(uScene, posP).rgb) - lumaMLSS; - done1 = done1 || (abs(lumaEnd1) >= gradientScaled * FXAA_SEARCH_THRESHOLD); - done2 = done2 || (abs(lumaEnd2) >= gradientScaled * FXAA_SEARCH_THRESHOLD); - if (done1 && done2) break; - if (!done1) posN -= offNP; - if (!done2) posP += offNP; - } - - float dstN = horzSpan ? (uv.x - posN.x) : (uv.y - posN.y); - float dstP = horzSpan ? (posP.x - uv.x) : (posP.y - uv.y); - bool dirN = (dstN < dstP); - float lumaEndFinal = dirN ? lumaEnd1 : lumaEnd2; - - float spanLength = dstN + dstP; - float pixelOffset = (dirN ? dstN : dstP) / spanLength; - bool goodSpan = ((lumaEndFinal < 0.0) != (lumaMLSS < 0.0)); - float pixelOffsetFinal = max(goodSpan ? pixelOffset : 0.0, blendL); - - vec2 finalUV = uv; - if ( horzSpan) finalUV.y += pixelOffsetFinal * lengthSign; - if (!horzSpan) finalUV.x += pixelOffsetFinal * lengthSign; - - vec3 fxaaResult = texture(uScene, finalUV).rgb; - - // Post-FXAA contrast-adaptive sharpening (unsharp mask). - // Counteracts FXAA's sub-pixel blur when sharpness > 0. - if (pc.sharpness > 0.0) { - vec2 r = pc.rcpFrame; - vec3 blur = (texture(uScene, uv + vec2(-r.x, 0)).rgb - + texture(uScene, uv + vec2( r.x, 0)).rgb - + texture(uScene, uv + vec2(0, -r.y)).rgb - + texture(uScene, uv + vec2(0, r.y)).rgb) * 0.25; - // scale sharpness from [0,2] to a modest [0, 0.3] boost factor - float s = pc.sharpness * 0.15; - fxaaResult = clamp(fxaaResult + s * (fxaaResult - blur), 0.0, 1.0); - } - - // Ghost mode: desaturate to grayscale (with a slight cool blue tint). - if (pc.desaturate > 0.5) { - float gray = dot(fxaaResult, vec3(0.299, 0.587, 0.114)); - fxaaResult = mix(fxaaResult, vec3(gray, gray, gray * 1.05), pc.desaturate); - } - - outColor = vec4(fxaaResult, 1.0); -} diff --git a/assets/shaders/fxaa.frag.spv b/assets/shaders/fxaa.frag.spv deleted file mode 100644 index b87b3dee..00000000 Binary files a/assets/shaders/fxaa.frag.spv and /dev/null differ diff --git a/assets/shaders/m2.vert.glsl b/assets/shaders/m2.vert.glsl index 3b8c113e..6f4545c8 100644 --- a/assets/shaders/m2.vert.glsl +++ b/assets/shaders/m2.vert.glsl @@ -13,29 +13,19 @@ layout(set = 0, binding = 0) uniform PerFrame { vec4 shadowParams; }; -// Per-draw push constants (batch-level data only) layout(push_constant) uniform Push { - int texCoordSet; // UV set index (0 or 1) - int isFoliage; // Foliage wind animation flag - int instanceDataOffset; // Base index into InstanceSSBO for this draw group + mat4 model; + vec2 uvOffset; + int texCoordSet; + int useBones; + int isFoliage; + float fadeAlpha; } push; layout(set = 2, binding = 0) readonly buffer BoneSSBO { mat4 bones[]; }; -// Per-instance data read via gl_InstanceIndex (GPU instancing) -struct InstanceData { - mat4 model; - vec2 uvOffset; - float fadeAlpha; - int useBones; - int boneBase; -}; -layout(set = 3, binding = 0) readonly buffer InstanceSSBO { - InstanceData instanceData[]; -}; - layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aNormal; layout(location = 2) in vec2 aTexCoord; @@ -51,23 +41,15 @@ layout(location = 4) out float ModelHeight; layout(location = 5) out float vFadeAlpha; void main() { - // Fetch per-instance data from SSBO - int instIdx = push.instanceDataOffset + gl_InstanceIndex; - mat4 model = instanceData[instIdx].model; - vec2 uvOff = instanceData[instIdx].uvOffset; - float fade = instanceData[instIdx].fadeAlpha; - int uBones = instanceData[instIdx].useBones; - int bBase = instanceData[instIdx].boneBase; - vec4 pos = vec4(aPos, 1.0); vec4 norm = vec4(aNormal, 0.0); - if (uBones != 0) { + if (push.useBones != 0) { ivec4 bi = ivec4(aBoneIndicesF); - mat4 skinMat = bones[bBase + bi.x] * aBoneWeights.x - + bones[bBase + bi.y] * aBoneWeights.y - + bones[bBase + bi.z] * aBoneWeights.z - + bones[bBase + bi.w] * aBoneWeights.w; + mat4 skinMat = bones[bi.x] * aBoneWeights.x + + bones[bi.y] * aBoneWeights.y + + bones[bi.z] * aBoneWeights.z + + bones[bi.w] * aBoneWeights.w; pos = skinMat * pos; norm = skinMat * norm; } @@ -75,7 +57,7 @@ void main() { // Wind animation for foliage if (push.isFoliage != 0) { float windTime = fogParams.z; - vec3 worldRef = model[3].xyz; + vec3 worldRef = push.model[3].xyz; float heightFactor = clamp(pos.z / 20.0, 0.0, 1.0); heightFactor *= heightFactor; // quadratic — base stays grounded @@ -98,15 +80,15 @@ void main() { pos.y += trunkSwayY + branchSwayY + leafFlutterY; } - vec4 worldPos = model * pos; + vec4 worldPos = push.model * pos; FragPos = worldPos.xyz; - Normal = mat3(model) * norm.xyz; + Normal = mat3(push.model) * norm.xyz; - TexCoord = (push.texCoordSet == 1 ? aTexCoord2 : aTexCoord) + uvOff; + TexCoord = (push.texCoordSet == 1 ? aTexCoord2 : aTexCoord) + push.uvOffset; - InstanceOrigin = model[3].xyz; + InstanceOrigin = push.model[3].xyz; ModelHeight = pos.z; - vFadeAlpha = fade; + vFadeAlpha = push.fadeAlpha; gl_Position = projection * view * worldPos; } diff --git a/assets/shaders/m2.vert.spv b/assets/shaders/m2.vert.spv index 11364e67..8397440f 100644 Binary files a/assets/shaders/m2.vert.spv and b/assets/shaders/m2.vert.spv differ diff --git a/assets/shaders/m2_cull.comp.glsl b/assets/shaders/m2_cull.comp.glsl deleted file mode 100644 index fd87ff93..00000000 --- a/assets/shaders/m2_cull.comp.glsl +++ /dev/null @@ -1,76 +0,0 @@ -#version 450 - -// GPU Frustum Culling for M2 doodads -// Each compute thread tests one M2 instance against 6 frustum planes. -// Input: per-instance bounding sphere + flags. -// Output: uint visibility array (1 = visible, 0 = culled). - -layout(local_size_x = 64) in; - -// Per-instance cull data (uploaded from CPU each frame) -struct CullInstance { - vec4 sphere; // xyz = world position, w = padded radius - float effectiveMaxDistSq; // adaptive distance cull threshold - uint flags; // bit 0 = valid, bit 1 = smoke, bit 2 = invisibleTrap - float _pad0; - float _pad1; -}; - -layout(std140, set = 0, binding = 0) uniform CullUniforms { - vec4 frustumPlanes[6]; // xyz = normal, w = distance - vec4 cameraPos; // xyz = camera position, w = maxPossibleDistSq - uint instanceCount; - uint _pad0; - uint _pad1; - uint _pad2; -}; - -layout(std430, set = 0, binding = 1) readonly buffer CullInput { - CullInstance cullInstances[]; -}; - -layout(std430, set = 0, binding = 2) writeonly buffer CullOutput { - uint visibility[]; -}; - -void main() { - uint id = gl_GlobalInvocationID.x; - if (id >= instanceCount) return; - - CullInstance inst = cullInstances[id]; - - // Flag check: must be valid, not smoke, not invisible trap - uint f = inst.flags; - if ((f & 1u) == 0u || (f & 6u) != 0u) { - visibility[id] = 0u; - return; - } - - // Early distance rejection (loose upper bound) - vec3 toCam = inst.sphere.xyz - cameraPos.xyz; - float distSq = dot(toCam, toCam); - if (distSq > cameraPos.w) { - visibility[id] = 0u; - return; - } - - // Accurate per-instance distance cull - if (distSq > inst.effectiveMaxDistSq) { - visibility[id] = 0u; - return; - } - - // Frustum cull: sphere vs 6 planes - float radius = inst.sphere.w; - if (radius > 0.0) { - for (int i = 0; i < 6; i++) { - float d = dot(frustumPlanes[i].xyz, inst.sphere.xyz) + frustumPlanes[i].w; - if (d < -radius) { - visibility[id] = 0u; - return; - } - } - } - - visibility[id] = 1u; -} diff --git a/assets/shaders/m2_cull.comp.spv b/assets/shaders/m2_cull.comp.spv deleted file mode 100644 index ef1a08fd..00000000 Binary files a/assets/shaders/m2_cull.comp.spv and /dev/null differ diff --git a/assets/shaders/m2_particle.frag.glsl b/assets/shaders/m2_particle.frag.glsl index 49fac7e1..f91a3fb7 100644 --- a/assets/shaders/m2_particle.frag.glsl +++ b/assets/shaders/m2_particle.frag.glsl @@ -25,9 +25,6 @@ void main() { if (lum < 0.05) discard; } - // Soft circular falloff for point-sprite edges. - float edge = 1.0 - smoothstep(0.4, 0.5, length(p - 0.5)); - float alpha = texColor.a * vColor.a * edge; - vec3 rgb = texColor.rgb * vColor.rgb * alpha; - outColor = vec4(rgb, alpha); + float edge = smoothstep(0.5, 0.4, length(p - 0.5)); + outColor = texColor * vColor * vec4(vec3(1.0), edge); } diff --git a/assets/shaders/m2_ribbon.frag.glsl b/assets/shaders/m2_ribbon.frag.glsl deleted file mode 100644 index 4e0e483e..00000000 --- a/assets/shaders/m2_ribbon.frag.glsl +++ /dev/null @@ -1,25 +0,0 @@ -#version 450 - -// M2 ribbon emitter fragment shader. -// Samples the ribbon texture, multiplied by vertex color and alpha. -// Uses additive blending (pipeline-level) for magic/spell trails. - -layout(set = 1, binding = 0) uniform sampler2D uTexture; - -layout(location = 0) in vec3 vColor; -layout(location = 1) in float vAlpha; -layout(location = 2) in vec2 vUV; -layout(location = 3) in float vFogFactor; - -layout(location = 0) out vec4 outColor; - -void main() { - vec4 tex = texture(uTexture, vUV); - // For additive ribbons alpha comes from texture luminance; multiply by vertex alpha. - float a = tex.a * vAlpha; - if (a < 0.01) discard; - vec3 rgb = tex.rgb * vColor; - // Ribbons fade slightly with fog (additive blend attenuated toward black = invisible in fog). - rgb *= vFogFactor; - outColor = vec4(rgb, a); -} diff --git a/assets/shaders/m2_ribbon.frag.spv b/assets/shaders/m2_ribbon.frag.spv deleted file mode 100644 index 9b0a3fe9..00000000 Binary files a/assets/shaders/m2_ribbon.frag.spv and /dev/null differ diff --git a/assets/shaders/m2_ribbon.vert.glsl b/assets/shaders/m2_ribbon.vert.glsl deleted file mode 100644 index 492a295e..00000000 --- a/assets/shaders/m2_ribbon.vert.glsl +++ /dev/null @@ -1,43 +0,0 @@ -#version 450 - -// M2 ribbon emitter vertex shader. -// Ribbon geometry is generated CPU-side as a triangle strip. -// Vertex format: pos(3) + color(3) + alpha(1) + uv(2) = 9 floats. - -layout(set = 0, binding = 0) uniform PerFrame { - mat4 view; - mat4 projection; - mat4 lightSpaceMatrix; - vec4 lightDir; - vec4 lightColor; - vec4 ambientColor; - vec4 viewPos; - vec4 fogColor; - vec4 fogParams; - vec4 shadowParams; -}; - -layout(location = 0) in vec3 aPos; -layout(location = 1) in vec3 aColor; -layout(location = 2) in float aAlpha; -layout(location = 3) in vec2 aUV; - -layout(location = 0) out vec3 vColor; -layout(location = 1) out float vAlpha; -layout(location = 2) out vec2 vUV; -layout(location = 3) out float vFogFactor; - -void main() { - vec4 worldPos = vec4(aPos, 1.0); - vec4 viewPos4 = view * worldPos; - gl_Position = projection * viewPos4; - - float dist = length(viewPos4.xyz); - float fogStart = fogParams.x; - float fogEnd = fogParams.y; - vFogFactor = clamp((fogEnd - dist) / max(fogEnd - fogStart, 0.001), 0.0, 1.0); - - vColor = aColor; - vAlpha = aAlpha; - vUV = aUV; -} diff --git a/assets/shaders/m2_ribbon.vert.spv b/assets/shaders/m2_ribbon.vert.spv deleted file mode 100644 index d74ba750..00000000 Binary files a/assets/shaders/m2_ribbon.vert.spv and /dev/null differ diff --git a/assets/shaders/minimap_display.frag.glsl b/assets/shaders/minimap_display.frag.glsl index 476017b9..dacaaed1 100644 --- a/assets/shaders/minimap_display.frag.glsl +++ b/assets/shaders/minimap_display.frag.glsl @@ -40,27 +40,23 @@ void main() { float cs = cos(push.rotation); float sn = sin(push.rotation); vec2 rotated = vec2(center.x * cs - center.y * sn, center.x * sn + center.y * cs); - vec2 mapUV = push.playerUV + vec2(rotated.x, rotated.y) * push.zoomRadius * 2.0; + vec2 mapUV = push.playerUV + vec2(-rotated.x, rotated.y) * push.zoomRadius * 2.0; vec4 mapColor = texture(uComposite, mapUV); - // Single player direction indicator (center arrow) rendered in-shader. - vec2 local = center; // [-0.5, 0.5] around minimap center - float ac = cos(push.arrowRotation); - float as = sin(push.arrowRotation); - // TexCoord Y grows downward on screen; use negative Y so 0-angle points North (up). - vec2 tip = vec2(0.0, -0.09); - vec2 left = vec2(-0.045, 0.02); - vec2 right = vec2( 0.045, 0.02); - mat2 rot = mat2(ac, -as, as, ac); - tip = rot * tip; - left = rot * left; - right = rot * right; - if (pointInTriangle(local, tip, left, right)) { - mapColor.rgb = vec3(1.0, 0.86, 0.05); + // Player arrow + float acs = cos(push.arrowRotation); + float asn = sin(push.arrowRotation); + vec2 ac = center; + vec2 arrowPos = vec2(-(ac.x * acs - ac.y * asn), ac.x * asn + ac.y * acs); + + vec2 tip = vec2(0.0, -0.04); + vec2 left = vec2(-0.02, 0.02); + vec2 right = vec2(0.02, 0.02); + + if (pointInTriangle(arrowPos, tip, left, right)) { + mapColor = vec4(1.0, 0.8, 0.0, 1.0); } - float centerDot = smoothstep(0.016, 0.0, length(local)); - mapColor.rgb = mix(mapColor.rgb, vec3(1.0), centerDot * 0.95); // Dark border ring float border = smoothstep(0.48, 0.5, dist); diff --git a/assets/shaders/minimap_display.frag.spv b/assets/shaders/minimap_display.frag.spv index f33deef2..5c0ac7b0 100644 Binary files a/assets/shaders/minimap_display.frag.spv and b/assets/shaders/minimap_display.frag.spv differ diff --git a/assets/shaders/postprocess.vert.glsl b/assets/shaders/postprocess.vert.glsl index 2ed8f784..aa78b1b5 100644 --- a/assets/shaders/postprocess.vert.glsl +++ b/assets/shaders/postprocess.vert.glsl @@ -6,7 +6,5 @@ void main() { // Fullscreen triangle trick: 3 vertices, no vertex buffer TexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); gl_Position = vec4(TexCoord * 2.0 - 1.0, 0.0, 1.0); - // No Y-flip: scene textures use Vulkan convention (v=0 at top), - // and NDC y=-1 already maps to framebuffer top, so the triangle - // naturally samples the correct row without any inversion. + TexCoord.y = 1.0 - TexCoord.y; // flip Y for Vulkan } diff --git a/assets/shaders/postprocess.vert.spv b/assets/shaders/postprocess.vert.spv index 89065a80..afc10472 100644 Binary files a/assets/shaders/postprocess.vert.spv and b/assets/shaders/postprocess.vert.spv differ diff --git a/assets/shaders/quest_marker.frag.glsl b/assets/shaders/quest_marker.frag.glsl index 0e209d8f..020b625d 100644 --- a/assets/shaders/quest_marker.frag.glsl +++ b/assets/shaders/quest_marker.frag.glsl @@ -5,7 +5,6 @@ layout(set = 1, binding = 0) uniform sampler2D markerTexture; layout(push_constant) uniform Push { mat4 model; float alpha; - float grayscale; // 0 = full colour, 1 = fully desaturated (trivial quests) } push; layout(location = 0) in vec2 TexCoord; @@ -15,7 +14,5 @@ layout(location = 0) out vec4 outColor; void main() { vec4 texColor = texture(markerTexture, TexCoord); if (texColor.a < 0.1) discard; - float lum = dot(texColor.rgb, vec3(0.299, 0.587, 0.114)); - vec3 rgb = mix(texColor.rgb, vec3(lum), push.grayscale); - outColor = vec4(rgb, texColor.a * push.alpha); + outColor = vec4(texColor.rgb, texColor.a * push.alpha); } diff --git a/assets/shaders/quest_marker.frag.spv b/assets/shaders/quest_marker.frag.spv index 90814c30..e947d04c 100644 Binary files a/assets/shaders/quest_marker.frag.spv and b/assets/shaders/quest_marker.frag.spv differ diff --git a/assets/shaders/terrain.frag b/assets/shaders/terrain.frag new file mode 100644 index 00000000..c7677725 --- /dev/null +++ b/assets/shaders/terrain.frag @@ -0,0 +1,146 @@ +#version 330 core + +in vec3 FragPos; +in vec3 Normal; +in vec2 TexCoord; +in vec2 LayerUV; + +out vec4 FragColor; + +// Texture layers (up to 4) +uniform sampler2D uBaseTexture; +uniform sampler2D uLayer1Texture; +uniform sampler2D uLayer2Texture; +uniform sampler2D uLayer3Texture; + +// Alpha maps for blending +uniform sampler2D uLayer1Alpha; +uniform sampler2D uLayer2Alpha; +uniform sampler2D uLayer3Alpha; + +// Layer control +uniform int uLayerCount; +uniform bool uHasLayer1; +uniform bool uHasLayer2; +uniform bool uHasLayer3; + +// Lighting +uniform vec3 uLightDir; +uniform vec3 uLightColor; +uniform vec3 uAmbientColor; + +// Camera +uniform vec3 uViewPos; + +// Fog +uniform vec3 uFogColor; +uniform float uFogStart; +uniform float uFogEnd; + +// Shadow mapping +uniform sampler2DShadow uShadowMap; +uniform mat4 uLightSpaceMatrix; +uniform bool uShadowEnabled; +uniform float uShadowStrength; + +float calcShadow() { + vec4 lsPos = uLightSpaceMatrix * vec4(FragPos, 1.0); + vec3 proj = lsPos.xyz / lsPos.w * 0.5 + 0.5; + if (proj.z > 1.0) return 1.0; + float edgeDist = max(abs(proj.x - 0.5), abs(proj.y - 0.5)); + float coverageFade = 1.0 - smoothstep(0.40, 0.49, edgeDist); + vec3 norm = normalize(Normal); + vec3 lightDir = normalize(-uLightDir); + float bias = max(0.005 * (1.0 - dot(norm, lightDir)), 0.001); + // 5-tap PCF tuned for slightly sharper detail while keeping stability. + vec2 texel = vec2(1.0 / 2048.0); + float ref = proj.z - bias; + vec2 off = texel * 0.7; + float shadow = 0.0; + shadow += texture(uShadowMap, vec3(proj.xy, ref)) * 0.55; + shadow += texture(uShadowMap, vec3(proj.xy + vec2(off.x, 0.0), ref)) * 0.1125; + shadow += texture(uShadowMap, vec3(proj.xy - vec2(off.x, 0.0), ref)) * 0.1125; + shadow += texture(uShadowMap, vec3(proj.xy + vec2(0.0, off.y), ref)) * 0.1125; + shadow += texture(uShadowMap, vec3(proj.xy - vec2(0.0, off.y), ref)) * 0.1125; + return mix(1.0, shadow, coverageFade); +} + +float sampleAlpha(sampler2D tex, vec2 uv) { + // Slight blur near alpha-map borders to hide seams between chunks. + vec2 edge = min(uv, 1.0 - uv); + float border = min(edge.x, edge.y); + float doBlur = step(border, 2.0 / 64.0); // within ~2 texels of edge + if (doBlur < 0.5) { + return texture(tex, uv).r; + } + vec2 texel = vec2(1.0 / 64.0); + float a = 0.0; + a += texture(tex, uv + vec2(-texel.x, 0.0)).r; + a += texture(tex, uv + vec2(texel.x, 0.0)).r; + a += texture(tex, uv + vec2(0.0, -texel.y)).r; + a += texture(tex, uv + vec2(0.0, texel.y)).r; + return a * 0.25; +} + +void main() { + // Sample base texture + vec4 baseColor = texture(uBaseTexture, TexCoord); + vec4 finalColor = baseColor; + + // Apply texture layers with alpha blending + // TexCoord = tiling UVs for texture sampling (repeats across chunk) + // LayerUV = 0-1 per-chunk UVs for alpha map sampling + float a1 = uHasLayer1 ? sampleAlpha(uLayer1Alpha, LayerUV) : 0.0; + float a2 = uHasLayer2 ? sampleAlpha(uLayer2Alpha, LayerUV) : 0.0; + float a3 = uHasLayer3 ? sampleAlpha(uLayer3Alpha, LayerUV) : 0.0; + + // Normalize weights to reduce quilting seams at chunk borders. + float w0 = 1.0; + float w1 = a1; + float w2 = a2; + float w3 = a3; + float sum = w0 + w1 + w2 + w3; + if (sum > 0.0) { + w0 /= sum; w1 /= sum; w2 /= sum; w3 /= sum; + } + + finalColor = baseColor * w0; + if (uHasLayer1) { + vec4 layer1Color = texture(uLayer1Texture, TexCoord); + finalColor += layer1Color * w1; + } + if (uHasLayer2) { + vec4 layer2Color = texture(uLayer2Texture, TexCoord); + finalColor += layer2Color * w2; + } + if (uHasLayer3) { + vec4 layer3Color = texture(uLayer3Texture, TexCoord); + finalColor += layer3Color * w3; + } + + // Normalize normal + vec3 norm = normalize(Normal); + vec3 lightDir = normalize(-uLightDir); + + // Ambient lighting + vec3 ambient = uAmbientColor * finalColor.rgb; + + // Diffuse lighting (two-sided for terrain hills) + float diff = abs(dot(norm, lightDir)); + diff = max(diff, 0.2); // Minimum light to prevent completely dark faces + vec3 diffuse = diff * uLightColor * finalColor.rgb; + + // Shadow + float shadow = uShadowEnabled ? calcShadow() : 1.0; + shadow = mix(1.0, shadow, clamp(uShadowStrength, 0.0, 1.0)); + + // Combine lighting (terrain is purely diffuse — no specular on ground) + vec3 result = ambient + shadow * diffuse; + + // Apply fog + float distance = length(uViewPos - FragPos); + float fogFactor = clamp((uFogEnd - distance) / (uFogEnd - uFogStart), 0.0, 1.0); + result = mix(uFogColor, result, fogFactor); + + FragColor = vec4(result, 1.0); +} diff --git a/assets/shaders/terrain.frag.glsl b/assets/shaders/terrain.frag.glsl index 0fcdd7dd..0a424090 100644 --- a/assets/shaders/terrain.frag.glsl +++ b/assets/shaders/terrain.frag.glsl @@ -116,8 +116,8 @@ void main() { vec4 lsPos = lightSpaceMatrix * vec4(biasedPos, 1.0); vec3 proj = lsPos.xyz / lsPos.w; proj.xy = proj.xy * 0.5 + 0.5; - if (proj.x >= 0.0 && proj.x <= 1.0 && proj.y >= 0.0 && proj.y <= 1.0 && proj.z >= 0.0 && proj.z <= 1.0) { - float bias = max(0.0005 * (1.0 - abs(dot(norm, ldir))), 0.00005); + if (proj.x >= 0.0 && proj.x <= 1.0 && proj.y >= 0.0 && proj.y <= 1.0 && proj.z <= 1.0) { + float bias = 0.0002; shadow = sampleShadowPCF(uShadowMap, vec3(proj.xy, proj.z - bias)); shadow = mix(1.0, shadow, shadowParams.y); } diff --git a/assets/shaders/terrain.vert b/assets/shaders/terrain.vert new file mode 100644 index 00000000..f8a57ae8 --- /dev/null +++ b/assets/shaders/terrain.vert @@ -0,0 +1,28 @@ +#version 330 core + +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aTexCoord; +layout(location = 3) in vec2 aLayerUV; + +out vec3 FragPos; +out vec3 Normal; +out vec2 TexCoord; +out vec2 LayerUV; + +uniform mat4 uModel; +uniform mat4 uView; +uniform mat4 uProjection; + +void main() { + vec4 worldPos = uModel * vec4(aPosition, 1.0); + FragPos = worldPos.xyz; + + // Terrain uses identity model matrix, so normal passes through directly + Normal = aNormal; + + TexCoord = aTexCoord; + LayerUV = aLayerUV; + + gl_Position = uProjection * uView * worldPos; +} diff --git a/assets/shaders/water.frag.glsl b/assets/shaders/water.frag.glsl index f656b681..5d3af519 100644 --- a/assets/shaders/water.frag.glsl +++ b/assets/shaders/water.frag.glsl @@ -47,16 +47,12 @@ layout(location = 0) out vec4 outColor; // Dual-scroll detail normals (multi-octave ripple overlay) // ============================================================ vec3 dualScrollWaveNormal(vec2 p, float time) { - // Three wave octaves at different angles, frequencies, and speeds. - // Directions are non-axis-aligned to prevent visible tiling patterns. - // Frequency increases and amplitude decreases per octave (standard - // multi-octave noise layering for natural water appearance). - vec2 d1 = normalize(vec2(0.86, 0.51)); // ~30° from +X - vec2 d2 = normalize(vec2(-0.47, 0.88)); // ~118° (opposing cross-wave) - vec2 d3 = normalize(vec2(0.32, -0.95)); // ~-71° (third axis for variety) - float f1 = 0.19, f2 = 0.43, f3 = 0.72; // spatial frequency (higher = tighter ripples) - float s1 = 0.95, s2 = 1.73, s3 = 2.40; // scroll speed (higher octaves move faster) - float a1 = 0.22, a2 = 0.10, a3 = 0.05; // amplitude (decreasing for natural falloff) + vec2 d1 = normalize(vec2(0.86, 0.51)); + vec2 d2 = normalize(vec2(-0.47, 0.88)); + vec2 d3 = normalize(vec2(0.32, -0.95)); + float f1 = 0.19, f2 = 0.43, f3 = 0.72; + float s1 = 0.95, s2 = 1.73, s3 = 2.40; + float a1 = 0.22, a2 = 0.10, a3 = 0.05; vec2 p1 = p + d1 * (time * s1 * 4.0); vec2 p2 = p + d2 * (time * s2 * 4.0); diff --git a/assets/shaders/wmo.frag.glsl b/assets/shaders/wmo.frag.glsl index ce29a0d1..a4bae057 100644 --- a/assets/shaders/wmo.frag.glsl +++ b/assets/shaders/wmo.frag.glsl @@ -29,9 +29,6 @@ layout(set = 1, binding = 1) uniform WMOMaterial { float heightMapVariance; float normalMapStrength; int isLava; - float wmoAmbientR; - float wmoAmbientG; - float wmoAmbientB; }; layout(set = 1, binding = 2) uniform sampler2D uNormalHeightMap; @@ -176,7 +173,7 @@ void main() { if (proj.x >= 0.0 && proj.x <= 1.0 && proj.y >= 0.0 && proj.y <= 1.0 && proj.z >= 0.0 && proj.z <= 1.0) { - float bias = max(0.0005 * (1.0 - abs(dot(norm, ldir))), 0.00005); + float bias = max(0.0005 * (1.0 - dot(norm, ldir)), 0.00005); shadow = sampleShadowPCF(uShadowMap, vec3(proj.xy, proj.z - bias)); } shadow = mix(1.0, shadow, shadowParams.y); @@ -188,13 +185,7 @@ void main() { } else if (unlit != 0) { result = texColor.rgb * shadow; } else if (isInterior != 0) { - // WMO interior: vertex colors (MOCV) are pre-baked lighting from the artist. - // The MOHD ambient color tints/floors the vertex colors so dark spots don't - // go completely black, matching the WoW client's interior shading. - vec3 wmoAmbient = vec3(wmoAmbientR, wmoAmbientG, wmoAmbientB); - // Clamp ambient to at least 0.3 to avoid total darkness when MOHD color is zero - wmoAmbient = max(wmoAmbient, vec3(0.3)); - vec3 mocv = max(VertColor.rgb, wmoAmbient); + vec3 mocv = max(VertColor.rgb, vec3(0.5)); result = texColor.rgb * mocv * shadow; } else { vec3 ldir = normalize(-lightDir.xyz); diff --git a/assets/shaders/wmo.frag.spv b/assets/shaders/wmo.frag.spv index 3507f3c9..524dbd1e 100644 Binary files a/assets/shaders/wmo.frag.spv and b/assets/shaders/wmo.frag.spv differ diff --git a/assets/textures/README.md b/assets/textures/README.md deleted file mode 100644 index aea06c61..00000000 --- a/assets/textures/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# HD Texture Assets - -**Source**: TurtleHD Texture Pack (Turtle WoW) -**Imported**: 2026-01-27 -**Total Files**: 298 BLP textures -**Total Size**: 10MB - -## Directory Structure - -``` -textures/ -├── character/ -│ └── human/ # 274 human male textures -├── creature/ # 15 creature textures -├── item/ # (reserved for future) -└── world/ - ├── generic/ # 1 generic world texture - └── stormwind/ # 8 Stormwind building textures -``` - -## Usage - -These HD BLP textures are ready for integration with: -- **WMO Renderer**: Building texture mapping -- **Character Renderer**: M2 model skin/face textures -- **Creature Renderer**: NPC texture application - -## Integration Status - -Textures are loaded via the BLP pipeline and applied to WMO/M2 renderers. -HD texture overrides (e.g. TurtleHD packs) can be placed as PNG files -alongside the original BLP paths — the asset manager checks for `.png` -overrides before loading the `.blp` version. diff --git a/container/FLOW.md b/container/FLOW.md deleted file mode 100644 index 619328c0..00000000 --- a/container/FLOW.md +++ /dev/null @@ -1,283 +0,0 @@ -# Container Build Flow - -Comprehensive documentation of the Docker-based build pipeline for each target platform. - ---- - -## Architecture Overview - -Each platform follows the same two-phase pattern: - -1. **Image Build** (one-time, cached by Docker) — installs compilers, toolchains, and pre-builds vcpkg dependencies. -2. **Container Run** (each build) — copies source into the container, runs CMake configure + build, outputs artifacts to the host. - -``` -Host Docker -───────────────────────────────────────────────────────────── -run-{platform}.sh/.ps1 - │ - ├─ docker build builder-{platform}.Dockerfile - │ (cached after first run) ├─ install compilers - │ ├─ install vcpkg + packages - │ └─ COPY build-{platform}.sh - │ - └─ docker run build-{platform}.sh (entrypoint) - ├─ bind /src (readonly) ├─ tar copy source → /wowee-build-src - └─ bind /out (writable) ├─ git clone FidelityFX SDKs - ├─ cmake -S . -B /out - ├─ cmake --build /out - └─ artifacts appear in /out -``` - ---- - -## Linux Build Flow - -**Image:** `wowee-builder-linux` -**Dockerfile:** `builder-linux.Dockerfile` -**Toolchain:** GCC + Ninja (native amd64) -**Base:** Ubuntu 24.04 - -### Docker Image Build Steps - -| Step | What | Why | -|------|------|-----| -| 1 | `apt-get install` cmake, ninja-build, build-essential, pkg-config, git, python3 | Core build tools | -| 2 | `apt-get install` glslang-tools, spirv-tools | Vulkan shader compilation | -| 3 | `apt-get install` libsdl2-dev, libglew-dev, libglm-dev, libssl-dev, zlib1g-dev | Runtime dependencies (system packages) | -| 4 | `apt-get install` libavformat-dev, libavcodec-dev, libswscale-dev, libavutil-dev | FFmpeg libraries | -| 5 | `apt-get install` libvulkan-dev, vulkan-tools | Vulkan SDK | -| 6 | `apt-get install` libstorm-dev, libunicorn-dev | MPQ archive + CPU emulation | -| 7 | COPY `build-linux.sh` → `/build-platform.sh` | Container entrypoint | - -### Container Run Steps (build-linux.sh) - -``` -1. tar copy /src → /wowee-build-src (excludes build/, .git/, large Data/ dirs) -2. git clone FidelityFX-FSR2 (if missing) -3. git clone FidelityFX-SDK (if missing) -4. cmake configure: - -G Ninja - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_C_COMPILER=gcc - -DCMAKE_CXX_COMPILER=g++ - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -5. cmake --build (parallel) -6. Create Data symlink: build/linux/bin/Data → ../../../Data -``` - -### Output -- `build/linux/bin/wowee` — ELF 64-bit x86-64 executable -- `build/linux/bin/Data` — symlink to project Data/ directory - ---- - -## macOS Build Flow - -**Image:** `wowee-builder-macos` -**Dockerfile:** `builder-macos.Dockerfile` (multi-stage) -**Toolchain:** osxcross (Clang 18 + Apple ld64) -**Base:** Ubuntu 24.04 -**Targets:** arm64-apple-darwin24.5 (default), x86_64-apple-darwin24.5 - -### Docker Image Build — Stage 1: SDK Fetcher - -The macOS SDK is fetched automatically from Apple's public software update catalog. -No manual download required. - -| Step | What | Why | -|------|------|-----| -| 1 | `FROM ubuntu:24.04 AS sdk-fetcher` | Lightweight stage for SDK download | -| 2 | `apt-get install` ca-certificates, python3, cpio, tar, gzip, xz-utils | SDK extraction tools | -| 3 | COPY `macos/sdk-fetcher.py` → `/opt/sdk-fetcher.py` | Python script that scrapes Apple's SUCATALOG | -| 4 | `python3 /opt/sdk-fetcher.py /opt/sdk` | Downloads, extracts, and packages MacOSX15.5.sdk.tar.gz | - -**SDK Fetcher internals** (`macos/sdk-fetcher.py`): -1. Queries Apple SUCATALOG URLs for the latest macOS package -2. Downloads the `CLTools_macOSNMOS_SDK.pkg` package -3. Extracts the XAR archive (using `bsdtar` or pure-Python fallback) -4. Decompresses the PBZX payload stream -5. Extracts via `cpio` to get the SDK directory -6. Packages as `MacOSX.sdk.tar.gz` - -### Docker Image Build — Stage 2: Builder - -| Step | What | Why | -|------|------|-----| -| 1 | `FROM ubuntu:24.04 AS builder` | Full build environment | -| 2 | `apt-get install` cmake, ninja-build, git, python3, curl, wget, xz-utils, zip, unzip, tar, make, patch, libssl-dev, zlib1g-dev, pkg-config, libbz2-dev, libxml2-dev, uuid-dev | Build tools + osxcross build deps | -| 3 | Install Clang 18 from LLVM apt repo (`llvm-toolchain-jammy-18`) | Cross-compiler backend | -| 4 | Symlink clang-18 → clang, clang++-18 → clang++, etc. | osxcross expects unversioned names | -| 5 | `git clone osxcross` → `/opt/osxcross` | Apple cross-compile toolchain wrapper | -| 6 | `COPY --from=sdk-fetcher /opt/sdk/ → /opt/osxcross/tarballs/` | SDK from stage 1 | -| 7 | `UNATTENDED=1 ./build.sh` | Builds osxcross (LLVM wrappers + cctools + ld64) | -| 8 | Create unprefixed symlinks (install_name_tool, otool, lipo, codesign) | vcpkg/CMake need these without arch prefix | -| 9 | COPY `macos/osxcross-toolchain.cmake` → `/opt/osxcross-toolchain.cmake` | Auto-detecting CMake toolchain | -| 10 | COPY `macos/triplets/` → `/opt/vcpkg-triplets/` | vcpkg cross-compile triplet definitions | -| 11 | `apt-get install` file, nasm | Mach-O detection + ffmpeg x86 asm | -| 12 | Bootstrap vcpkg → `/opt/vcpkg` | Package manager | -| 13 | `vcpkg install` sdl2, openssl, glew, glm, zlib, ffmpeg `--triplet arm64-osx-cross` | arm64 dependencies | -| 14 | `vcpkg install` same packages `--triplet x64-osx-cross` | x86_64 dependencies | -| 15 | `apt-get install` libvulkan-dev, glslang-tools | Vulkan headers (for compilation, not runtime) | -| 16 | COPY `build-macos.sh` → `/build-platform.sh` | Container entrypoint | - -### Custom Toolchain Files - -**`macos/osxcross-toolchain.cmake`** — Auto-detecting CMake toolchain: -- Detects SDK path via `file(GLOB)` in `/opt/osxcross/target/SDK/MacOSX*.sdk` -- Detects darwin version from compiler binary names (e.g., `arm64-apple-darwin24.5-clang`) -- Picks architecture from `CMAKE_OSX_ARCHITECTURES` -- Sets `CMAKE_C_COMPILER`, `CMAKE_CXX_COMPILER`, `CMAKE_AR`, `CMAKE_RANLIB`, `CMAKE_STRIP` - -**`macos/triplets/arm64-osx-cross.cmake`**: -```cmake -set(VCPKG_TARGET_ARCHITECTURE arm64) -set(VCPKG_LIBRARY_LINKAGE static) -set(VCPKG_CMAKE_SYSTEM_NAME Darwin) -set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE /opt/osxcross-toolchain.cmake) -``` - -### Container Run Steps (build-macos.sh) - -``` -1. Determine arch from MACOS_ARCH env (default: arm64) -2. Pick vcpkg triplet: arm64-osx-cross or x64-osx-cross -3. Auto-detect darwin target from osxcross binaries -4. tar copy /src → /wowee-build-src -5. git clone FidelityFX-FSR2 + FidelityFX-SDK (if missing) -6. cmake configure: - -G Ninja - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_SYSTEM_NAME=Darwin - -DCMAKE_OSX_ARCHITECTURES=${ARCH} - -DCMAKE_C_COMPILER=osxcross clang - -DCMAKE_CXX_COMPILER=osxcross clang++ - -DCMAKE_TOOLCHAIN_FILE=vcpkg.cmake - -DVCPKG_TARGET_TRIPLET=arm64-osx-cross - -DVCPKG_OVERLAY_TRIPLETS=/opt/vcpkg-triplets -7. cmake --build (parallel) -``` - -### CMakeLists.txt Integration - -The main CMakeLists.txt has a macOS cross-compile branch that: -- Finds Vulkan headers via vcpkg (`VulkanHeaders` package) instead of the Vulkan SDK -- Adds `-undefined dynamic_lookup` linker flag for Vulkan loader symbols (resolved at runtime via MoltenVK) - -### Output -- `build/macos/bin/wowee` — Mach-O 64-bit arm64 (or x86_64) executable (~40 MB) - ---- - -## Windows Build Flow - -**Image:** `wowee-builder-windows` -**Dockerfile:** `builder-windows.Dockerfile` -**Toolchain:** LLVM-MinGW (Clang + LLD) targeting x86_64-w64-mingw32-ucrt -**Base:** Ubuntu 24.04 - -### Docker Image Build Steps - -| Step | What | Why | -|------|------|-----| -| 1 | `apt-get install` ca-certificates, build-essential, cmake, ninja-build, git, python3, curl, zip, unzip, tar, xz-utils, pkg-config, nasm, libssl-dev, zlib1g-dev | Build tools | -| 2 | Download + extract LLVM-MinGW (v20240619 ucrt) → `/opt/llvm-mingw` | Clang/LLD cross-compiler for Windows | -| 3 | Add `/opt/llvm-mingw/bin` to PATH | Makes `x86_64-w64-mingw32-clang` etc. available | -| 4 | Bootstrap vcpkg → `/opt/vcpkg` | Package manager | -| 5 | `vcpkg install` sdl2, openssl, glew, glm, zlib, ffmpeg `--triplet x64-mingw-static` | Static Windows dependencies | -| 6 | `apt-get install` libvulkan-dev, glslang-tools | Vulkan headers + shader tools | -| 7 | Create no-op `powershell.exe` stub | vcpkg MinGW post-build hook needs it | -| 8 | COPY `build-windows.sh` → `/build-platform.sh` | Container entrypoint | - -### Container Run Steps (build-windows.sh) - -``` -1. Set up no-op powershell.exe (if not already present) -2. tar copy /src → /wowee-build-src -3. git clone FidelityFX-FSR2 + FidelityFX-SDK (if missing) -4. Generate Vulkan import library: - a. Extract vk* symbols from vulkan_core.h - b. Create vulkan-1.def file - c. Run dlltool to create libvulkan-1.a -5. Lock PKG_CONFIG_LIBDIR to vcpkg packages only -6. cmake configure: - -G Ninja - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_SYSTEM_NAME=Windows - -DCMAKE_C_COMPILER=x86_64-w64-mingw32-clang - -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-clang++ - -DCMAKE_RC_COMPILER=x86_64-w64-mingw32-windres - -DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld - -DCMAKE_TOOLCHAIN_FILE=vcpkg.cmake - -DVCPKG_TARGET_TRIPLET=x64-mingw-static - -DVCPKG_APPLOCAL_DEPS=OFF -7. cmake --build (parallel) -``` - -### Vulkan Import Library Generation - -Windows applications link against `vulkan-1.dll` (the Khronos Vulkan loader). Since the LLVM-MinGW toolchain doesn't ship a Vulkan import library, the build script generates one: - -1. Parses `vulkan_core.h` for `VKAPI_CALL vk*` function names -2. Creates a `.def` file mapping symbols to `vulkan-1.dll` -3. Uses `dlltool` to produce `libvulkan-1.a` (PE import library) - -This allows the linker to resolve Vulkan symbols at build time, while deferring actual loading to the runtime DLL. - -### Output -- `build/windows/bin/wowee.exe` — PE32+ x86-64 executable (~135 MB) - ---- - -## Shared Patterns - -### Source Tree Copy - -All three platforms use the same tar-based copy with exclusions: -```bash -tar -C /src \ - --exclude='./build' --exclude='./logs' --exclude='./cache' \ - --exclude='./container' --exclude='./.git' \ - --exclude='./Data/character' --exclude='./Data/creature' \ - --exclude='./Data/db' --exclude='./Data/environment' \ - --exclude='./Data/interface' --exclude='./Data/item' \ - --exclude='./Data/misc' --exclude='./Data/sound' \ - --exclude='./Data/spell' --exclude='./Data/terrain' \ - --exclude='./Data/world' \ - -cf - . | tar -C /wowee-build-src -xf - -``` - -**Kept:** `Data/opcodes/`, `Data/expansions/` (small, needed at build time for configuration). -**Excluded:** Large game asset directories (character, creature, environment, etc.) not needed for compilation. - -### FidelityFX SDK Fetch - -All platforms clone the same two repos at build time: -1. **FidelityFX-FSR2** — FSR 2.0 upscaling -2. **FidelityFX-SDK** — FSR 3.0 frame generation (repo URL/ref configurable via env vars) - -### .dockerignore - -The `.dockerignore` at the project root minimizes the Docker build context by excluding: -- `build/`, `cache/`, `logs/`, `.git/` -- Large external dirs (`extern/FidelityFX-*`) -- IDE files, documentation, host-only scripts -- SDK tarballs (`*.tar.xz`, `*.tar.gz`, etc.) - ---- - -## Timing Estimates - -These are approximate times on a 4-core machine with 16 GB RAM: - -| Phase | Linux | macOS | Windows | -|-------|-------|-------|---------| -| Docker image build (first time) | ~5 min | ~25 min | ~15 min | -| Docker image build (cached) | seconds | seconds | seconds | -| Source copy + SDK fetch | ~10 sec | ~10 sec | ~10 sec | -| CMake configure | ~20 sec | ~30 sec | ~30 sec | -| Compilation | ~8 min | ~8 min | ~8 min | -| **Total (first build)** | **~14 min** | **~34 min** | **~24 min** | -| **Total (subsequent)** | **~9 min** | **~9 min** | **~9 min** | - -macOS image is slowest because osxcross builds a subset of LLVM + cctools, and vcpkg packages are compiled for two architectures (arm64 + x64). diff --git a/container/README.md b/container/README.md deleted file mode 100644 index da911f2c..00000000 --- a/container/README.md +++ /dev/null @@ -1,119 +0,0 @@ -# Container Builds - -Build WoWee for **Linux**, **macOS**, or **Windows** with a single command. -All builds run inside Docker — no toolchains to install on your host. - -## Prerequisites - -- [Docker](https://docs.docker.com/get-docker/) (Docker Desktop on Windows/macOS, or Docker Engine on Linux) -- ~20 GB free disk space (toolchains + vcpkg packages are cached in the Docker image) - -## Quick Start - -Run **from the project root directory**. - -### Linux (native amd64) - -```bash -# Bash / Linux / macOS terminal -./container/run-linux.sh -``` -```powershell -# PowerShell (Windows) -.\container\run-linux.ps1 -``` - -Output: `build/linux/bin/wowee` - -### macOS (cross-compile, arm64 default) - -```bash -./container/run-macos.sh -``` -```powershell -.\container\run-macos.ps1 -``` - -Output: `build/macos/bin/wowee` - -For Intel (x86_64): -```bash -MACOS_ARCH=x86_64 ./container/run-macos.sh -``` -```powershell -.\container\run-macos.ps1 -Arch x86_64 -``` - -### Windows (cross-compile, x86_64) - -```bash -./container/run-windows.sh -``` -```powershell -.\container\run-windows.ps1 -``` - -Output: `build/windows/bin/wowee.exe` - -## Options - -| Option | Bash | PowerShell | Description | -|--------|------|------------|-------------| -| Rebuild image | `--rebuild-image` | `-RebuildImage` | Force a fresh Docker image build | -| macOS arch | `MACOS_ARCH=x86_64` | `-Arch x86_64` | Build for Intel instead of Apple Silicon | -| FidelityFX SDK repo | `WOWEE_FFX_SDK_REPO=` | `$env:WOWEE_FFX_SDK_REPO=""` | Custom FidelityFX SDK git URL | -| FidelityFX SDK ref | `WOWEE_FFX_SDK_REF=` | `$env:WOWEE_FFX_SDK_REF=""` | Custom FidelityFX SDK git ref/tag | - -## Docker Image Caching - -The first build takes longer because Docker builds the toolchain image (installing compilers, vcpkg packages, etc.). Subsequent builds reuse the cached image and only run the compilation step. - -To force a full image rebuild: -```bash -./container/run-linux.sh --rebuild-image -``` - -## Output Locations - -| Target | Binary | Size | -|--------|--------|------| -| Linux | `build/linux/bin/wowee` | ~135 MB | -| macOS | `build/macos/bin/wowee` | ~40 MB | -| Windows | `build/windows/bin/wowee.exe` | ~135 MB | - -## File Structure - -``` -container/ -├── run-linux.sh / .ps1 # Host launchers (bash / PowerShell) -├── run-macos.sh / .ps1 -├── run-windows.sh / .ps1 -├── build-linux.sh # Container entrypoints (run inside Docker) -├── build-macos.sh -├── build-windows.sh -├── builder-linux.Dockerfile # Docker image definitions -├── builder-macos.Dockerfile -├── builder-windows.Dockerfile -├── macos/ -│ ├── sdk-fetcher.py # Auto-fetches macOS SDK from Apple's catalog -│ ├── osxcross-toolchain.cmake # CMake toolchain for osxcross -│ └── triplets/ # vcpkg cross-compile triplets -│ ├── arm64-osx-cross.cmake -│ └── x64-osx-cross.cmake -├── README.md # This file -└── FLOW.md # Detailed build flow documentation -``` - -## Troubleshooting - -**"docker is not installed or not in PATH"** -Install Docker and ensure the `docker` command is available in your terminal. - -**Build fails on first run** -Some vcpkg packages (ffmpeg, SDL2) take a while to compile. Ensure you have enough RAM (4 GB+) and disk space. - -**macOS build: "could not find osxcross compiler"** -The Docker image may not have built correctly. Run with `--rebuild-image` to rebuild from scratch. - -**Windows build: linker errors about vulkan-1.dll** -The build script auto-generates a Vulkan import library. If this fails, ensure the Docker image has `libvulkan-dev` installed (it should, by default). diff --git a/container/build-in-container.sh b/container/build-in-container.sh new file mode 100755 index 00000000..cc0822b4 --- /dev/null +++ b/container/build-in-container.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -eu +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +podman build \ + -f "${SCRIPT_DIR}/builder-ubuntu.Dockerfile" \ + -t wowee-builder-ubuntu + +BUILD_DIR="$(mktemp --tmpdir -d wowee.XXXXX \ + --suffix=".$(cd "${PROJECT_ROOT}"; git rev-parse --short HEAD)")" +podman run \ + --mount "type=bind,src=${PROJECT_ROOT},dst=/WoWee-src,ro=true" \ + --mount "type=bind,src=${BUILD_DIR},dst=/build" \ + localhost/wowee-builder-ubuntu \ + ./build-wowee.sh diff --git a/container/build-linux.sh b/container/build-linux.sh deleted file mode 100755 index a87b5e03..00000000 --- a/container/build-linux.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash -# Linux amd64 build entrypoint — runs INSIDE the linux container. -# Bind-mounts: -# /src (ro) — project source -# /out (rw) — host ./build/linux - -set -euo pipefail - -SRC=/src -OUT=/out -NPROC=$(nproc) - -echo "==> [linux] Copying source tree..." -mkdir -p /wowee-build-src -tar -C "${SRC}" \ - --exclude='./build' --exclude='./logs' --exclude='./cache' \ - --exclude='./container' --exclude='./.git' \ - --exclude='./Data/character' --exclude='./Data/creature' \ - --exclude='./Data/db' --exclude='./Data/environment' \ - --exclude='./Data/interface' --exclude='./Data/item' \ - --exclude='./Data/misc' --exclude='./Data/sound' \ - --exclude='./Data/spell' --exclude='./Data/terrain' \ - --exclude='./Data/world' \ - -cf - . | tar -C /wowee-build-src -xf - - -cd /wowee-build-src - -echo "==> [linux] Fetching external SDKs (if needed)..." -if [ ! -f extern/FidelityFX-FSR2/src/ffx-fsr2-api/ffx_fsr2.h ]; then - git clone --depth 1 \ - https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git \ - extern/FidelityFX-FSR2 || echo "Warning: FSR2 clone failed — continuing without FSR2" -fi - -SDK_REPO="${WOWEE_FFX_SDK_REPO:-https://github.com/Kelsidavis/FidelityFX-SDK.git}" -SDK_REF="${WOWEE_FFX_SDK_REF:-main}" -if [ ! -f "extern/FidelityFX-SDK/sdk/include/FidelityFX/host/ffx_frameinterpolation.h" ]; then - git clone --depth 1 --branch "${SDK_REF}" "${SDK_REPO}" extern/FidelityFX-SDK \ - || echo "Warning: FidelityFX-SDK clone failed — continuing without FSR3" -fi - -echo "==> [linux] Configuring with CMake (Release, Ninja, amd64)..." -cmake -S . -B "${OUT}" \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_C_COMPILER=gcc \ - -DCMAKE_CXX_COMPILER=g++ \ - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON - -echo "==> [linux] Building with ${NPROC} cores..." -cmake --build "${OUT}" --parallel "${NPROC}" - -echo "==> [linux] Creating Data symlink..." -mkdir -p "${OUT}/bin" -if [ ! -e "${OUT}/bin/Data" ]; then - # Relative symlink so it resolves correctly on the host: - # build/linux/bin/Data -> ../../../Data (project root) - ln -s ../../../Data "${OUT}/bin/Data" -fi - -echo "" -echo "==> [linux] Build complete. Artifacts in: ./build/linux/" -echo " Binary: ./build/linux/bin/wowee" diff --git a/container/build-macos.sh b/container/build-macos.sh deleted file mode 100755 index 3ead48e1..00000000 --- a/container/build-macos.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash -# macOS cross-compile entrypoint — runs INSIDE the macos container. -# Toolchain: osxcross + Apple Clang, target: arm64-apple-darwin (default) or -# x86_64-apple-darwin when MACOS_ARCH=x86_64. -# Bind-mounts: -# /src (ro) — project source -# /out (rw) — host ./build/macos - -set -euo pipefail - -SRC=/src -OUT=/out -NPROC=$(nproc) - -# Arch selection: arm64 (Apple Silicon) is the default primary target. -ARCH="${MACOS_ARCH:-arm64}" -case "${ARCH}" in - arm64) VCPKG_TRIPLET=arm64-osx-cross ;; - x86_64) VCPKG_TRIPLET=x64-osx-cross ;; - *) echo "ERROR: unsupported MACOS_ARCH '${ARCH}'. Use arm64 or x86_64." ; exit 1 ;; -esac - -# Auto-detect darwin target from osxcross binaries (e.g. arm64-apple-darwin24.5). -OSXCROSS_BIN=/opt/osxcross/target/bin -TARGET=$(basename "$(ls "${OSXCROSS_BIN}/${ARCH}-apple-darwin"*-clang 2>/dev/null | head -1)" | sed 's/-clang$//') -if [[ -z "${TARGET}" ]]; then - echo "ERROR: could not find osxcross ${ARCH} compiler in ${OSXCROSS_BIN}" >&2 - exit 1 -fi -echo "==> Detected osxcross target: ${TARGET}" - -echo "==> [macos/${ARCH}] Copying source tree..." -mkdir -p /wowee-build-src -tar -C "${SRC}" \ - --exclude='./build' --exclude='./logs' --exclude='./cache' \ - --exclude='./container' --exclude='./.git' \ - --exclude='./Data/character' --exclude='./Data/creature' \ - --exclude='./Data/db' --exclude='./Data/environment' \ - --exclude='./Data/interface' --exclude='./Data/item' \ - --exclude='./Data/misc' --exclude='./Data/sound' \ - --exclude='./Data/spell' --exclude='./Data/terrain' \ - --exclude='./Data/world' \ - -cf - . | tar -C /wowee-build-src -xf - - -cd /wowee-build-src - -echo "==> [macos/${ARCH}] Fetching external SDKs (if needed)..." -if [ ! -f extern/FidelityFX-FSR2/src/ffx-fsr2-api/ffx_fsr2.h ]; then - git clone --depth 1 \ - https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git \ - extern/FidelityFX-FSR2 || echo "Warning: FSR2 clone failed" -fi - -SDK_REPO="${WOWEE_FFX_SDK_REPO:-https://github.com/Kelsidavis/FidelityFX-SDK.git}" -SDK_REF="${WOWEE_FFX_SDK_REF:-main}" -if [ ! -f "extern/FidelityFX-SDK/sdk/include/FidelityFX/host/ffx_frameinterpolation.h" ]; then - git clone --depth 1 --branch "${SDK_REF}" "${SDK_REPO}" extern/FidelityFX-SDK \ - || echo "Warning: FidelityFX-SDK clone failed" -fi - -echo "==> [macos/${ARCH}] Configuring with CMake (Release, Ninja, osxcross ${TARGET})..." -cmake -S . -B "${OUT}" \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_SYSTEM_NAME=Darwin \ - -DCMAKE_OSX_ARCHITECTURES="${ARCH}" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-13.0}" \ - -DCMAKE_C_COMPILER="${OSXCROSS_BIN}/${TARGET}-clang" \ - -DCMAKE_CXX_COMPILER="${OSXCROSS_BIN}/${TARGET}-clang++" \ - -DCMAKE_AR="${OSXCROSS_BIN}/${TARGET}-ar" \ - -DCMAKE_RANLIB="${OSXCROSS_BIN}/${TARGET}-ranlib" \ - -DCMAKE_TOOLCHAIN_FILE="${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" \ - -DVCPKG_TARGET_TRIPLET="${VCPKG_TRIPLET}" \ - -DVCPKG_OVERLAY_TRIPLETS=/opt/vcpkg-triplets \ - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \ - -DWOWEE_ENABLE_ASAN=OFF - -echo "==> [macos/${ARCH}] Building with ${NPROC} cores..." -cmake --build "${OUT}" --parallel "${NPROC}" - -echo "" -echo "==> [macos/${ARCH}] Build complete. Artifacts in: ./build/macos/" -echo " Binary: ./build/macos/bin/wowee" diff --git a/container/build-windows.sh b/container/build-windows.sh deleted file mode 100755 index 7f0478ef..00000000 --- a/container/build-windows.sh +++ /dev/null @@ -1,110 +0,0 @@ -#!/bin/bash -# Windows cross-compile entrypoint — runs INSIDE the windows container. -# Toolchain: LLVM-MinGW (Clang + LLD), target: x86_64-w64-mingw32-ucrt -# Bind-mounts: -# /src (ro) — project source -# /out (rw) — host ./build/windows - -set -euo pipefail - -SRC=/src -OUT=/out -NPROC=$(nproc) -TARGET=x86_64-w64-mingw32 - -# vcpkg's MinGW applocal hook always appends a powershell.exe post-build step to -# copy DLLs next to each binary, even when VCPKG_APPLOCAL_DEPS=OFF. For the -# x64-mingw-static triplet the bin/ dir is empty (no DLLs) so the script does -# nothing — but it still needs to exit 0. Provide a no-op stub if the real -# PowerShell isn't available. -if ! command -v powershell.exe &>/dev/null; then - printf '#!/bin/sh\nexit 0\n' > /usr/local/bin/powershell.exe - chmod +x /usr/local/bin/powershell.exe -fi - -echo "==> [windows] Copying source tree..." -mkdir -p /wowee-build-src -tar -C "${SRC}" \ - --exclude='./build' --exclude='./logs' --exclude='./cache' \ - --exclude='./container' --exclude='./.git' \ - --exclude='./Data/character' --exclude='./Data/creature' \ - --exclude='./Data/db' --exclude='./Data/environment' \ - --exclude='./Data/interface' --exclude='./Data/item' \ - --exclude='./Data/misc' --exclude='./Data/sound' \ - --exclude='./Data/spell' --exclude='./Data/terrain' \ - --exclude='./Data/world' \ - -cf - . | tar -C /wowee-build-src -xf - - - -cd /wowee-build-src - -echo "==> [windows] Fetching external SDKs (if needed)..." -if [ ! -f extern/FidelityFX-FSR2/src/ffx-fsr2-api/ffx_fsr2.h ]; then - git clone --depth 1 \ - https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git \ - extern/FidelityFX-FSR2 || echo "Warning: FSR2 clone failed" -fi - -SDK_REPO="${WOWEE_FFX_SDK_REPO:-https://github.com/Kelsidavis/FidelityFX-SDK.git}" -SDK_REF="${WOWEE_FFX_SDK_REF:-main}" -if [ ! -f "extern/FidelityFX-SDK/sdk/include/FidelityFX/host/ffx_frameinterpolation.h" ]; then - git clone --depth 1 --branch "${SDK_REF}" "${SDK_REPO}" extern/FidelityFX-SDK \ - || echo "Warning: FidelityFX-SDK clone failed" -fi - -echo "==> [windows] Generating Vulkan import library for cross-compile..." -# Windows applications link against vulkan-1.dll (the Khronos Vulkan loader). -# The cross-compile toolchain only ships Vulkan *headers* (via vcpkg), not the -# import library. Generate a minimal libvulkan-1.a from the header prototypes -# so the linker can resolve vk* symbols → vulkan-1.dll at runtime. -# We use the host libvulkan-dev header for function name extraction — the Vulkan -# API prototypes are platform-independent. -VULKAN_IMP_DIR="${OUT}/vulkan-import" -if [ ! -f "${VULKAN_IMP_DIR}/libvulkan-1.a" ]; then - mkdir -p "${VULKAN_IMP_DIR}" - # Try vcpkg-installed header first (available on incremental builds), - # then fall back to the host libvulkan-dev header (always present in the image). - VK_HEADER="${OUT}/vcpkg_installed/x64-mingw-static/include/vulkan/vulkan_core.h" - if [ ! -f "${VK_HEADER}" ]; then - VK_HEADER="/usr/include/vulkan/vulkan_core.h" - fi - { - echo "LIBRARY vulkan-1.dll" - echo "EXPORTS" - grep -oP 'VKAPI_ATTR \S+ VKAPI_CALL \K(vk\w+)' "${VK_HEADER}" | sort -u | sed 's/^/ /' - } > "${VULKAN_IMP_DIR}/vulkan-1.def" - "${TARGET}-dlltool" -d "${VULKAN_IMP_DIR}/vulkan-1.def" \ - -l "${VULKAN_IMP_DIR}/libvulkan-1.a" -m i386:x86-64 - echo " Generated $(wc -l < "${VULKAN_IMP_DIR}/vulkan-1.def") export entries" -fi - -echo "==> [windows] Configuring with CMake (Release, Ninja, LLVM-MinGW cross)..." -# Lock pkg-config to the cross-compiled vcpkg packages only. -# Without this, CMake's Vulkan pkg-config fallback finds the *Linux* libvulkan-dev -# and injects /usr/include into every MinGW compile command, which then fails -# because the glibc-specific bits/libc-header-start.h is not in the MinGW sysroot. -export PKG_CONFIG_LIBDIR="${OUT}/vcpkg_installed/x64-mingw-static/lib/pkgconfig" -export PKG_CONFIG_PATH="" -cmake -S . -B "${OUT}" \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_SYSTEM_NAME=Windows \ - -DCMAKE_C_COMPILER="${TARGET}-clang" \ - -DCMAKE_CXX_COMPILER="${TARGET}-clang++" \ - -DCMAKE_RC_COMPILER="${TARGET}-windres" \ - -DCMAKE_AR="/opt/llvm-mingw/bin/llvm-ar" \ - -DCMAKE_RANLIB="/opt/llvm-mingw/bin/llvm-ranlib" \ - -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" \ - -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=lld" \ - -DCMAKE_TOOLCHAIN_FILE="${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" \ - -DVCPKG_TARGET_TRIPLET=x64-mingw-static \ - -DVCPKG_APPLOCAL_DEPS=OFF \ - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \ - -DWOWEE_ENABLE_ASAN=OFF - -echo "==> [windows] Building with ${NPROC} cores..." -cmake --build "${OUT}" --parallel "${NPROC}" - -echo "" -echo "==> [windows] Build complete. Artifacts in: ./build/windows/" -echo " Binary: ./build/windows/bin/wowee.exe" diff --git a/container/build-wowee.sh b/container/build-wowee.sh new file mode 100755 index 00000000..aabd8396 --- /dev/null +++ b/container/build-wowee.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -eu +set -o pipefail + +cp -r /WoWee-src /WoWee + +pushd /WoWee +./build.sh +popd + +pushd /WoWee/build +cmake --install . --prefix=/build +popd diff --git a/container/builder-linux.Dockerfile b/container/builder-linux.Dockerfile deleted file mode 100644 index 1f9e12dc..00000000 --- a/container/builder-linux.Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -FROM ubuntu:24.04 - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - cmake \ - ninja-build \ - build-essential \ - pkg-config \ - git \ - python3 \ - glslang-tools \ - spirv-tools \ - libsdl2-dev \ - libglew-dev \ - libglm-dev \ - libssl-dev \ - zlib1g-dev \ - libavformat-dev \ - libavcodec-dev \ - libswscale-dev \ - libavutil-dev \ - libvulkan-dev \ - vulkan-tools \ - libstorm-dev \ - libunicorn-dev && \ - rm -rf /var/lib/apt/lists/* - -COPY build-linux.sh /build-platform.sh -RUN chmod +x /build-platform.sh - -ENTRYPOINT ["/build-platform.sh"] diff --git a/container/builder-macos.Dockerfile b/container/builder-macos.Dockerfile deleted file mode 100644 index cb3dcb9f..00000000 --- a/container/builder-macos.Dockerfile +++ /dev/null @@ -1,142 +0,0 @@ -FROM ubuntu:24.04 AS sdk-fetcher - -# Stage 1: Fetch macOS SDK from Apple's public software update catalog. -# This avoids requiring the user to supply the SDK tarball manually. -# The SDK is downloaded, extracted, and packaged as a .tar.gz. - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - ca-certificates \ - python3 \ - python3-defusedxml \ - cpio \ - tar \ - gzip \ - xz-utils && \ - rm -rf /var/lib/apt/lists/* - -COPY macos/sdk-fetcher.py /opt/sdk-fetcher.py -RUN python3 /opt/sdk-fetcher.py /opt/sdk - -# --------------------------------------------------------------------------- - -FROM ubuntu:24.04 AS builder - -# Stage 2: macOS cross-compile image using osxcross + Clang 18. -# -# Target triplets (auto-detected from osxcross): -# arm64-apple-darwinNN (Apple Silicon) -# x86_64-apple-darwinNN (Intel) -# Default: arm64. Override with MACOS_ARCH=x86_64 env var at run time. - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - ca-certificates \ - cmake \ - ninja-build \ - git \ - python3 \ - curl \ - wget \ - xz-utils \ - zip \ - unzip \ - tar \ - make \ - patch \ - libssl-dev \ - zlib1g-dev \ - pkg-config \ - libbz2-dev \ - libxml2-dev \ - libz-dev \ - liblzma-dev \ - uuid-dev \ - python3-lxml \ - gnupg \ - software-properties-common && \ - wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ - echo "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main" > /etc/apt/sources.list.d/llvm-18.list && \ - apt-get update && \ - apt-get install -y --no-install-recommends \ - clang-18 \ - lld-18 \ - llvm-18 && \ - ln -sf /usr/bin/clang-18 /usr/bin/clang && \ - ln -sf /usr/bin/clang++-18 /usr/bin/clang++ && \ - ln -sf /usr/bin/lld-18 /usr/bin/lld && \ - ln -sf /usr/bin/ld.lld-18 /usr/bin/ld.lld && \ - ln -sf /usr/bin/llvm-ar-18 /usr/bin/llvm-ar && \ - rm -rf /var/lib/apt/lists/* - -# Build osxcross with SDK from stage 1 -RUN git clone --depth 1 https://github.com/tpoechtrager/osxcross.git /opt/osxcross - -COPY --from=sdk-fetcher /opt/sdk/ /opt/osxcross/tarballs/ - -ENV MACOSX_DEPLOYMENT_TARGET=13.0 -RUN cd /opt/osxcross && \ - UNATTENDED=1 ./build.sh && \ - rm -rf /opt/osxcross/build /opt/osxcross/tarballs - -ENV PATH="/opt/osxcross/target/bin:${PATH}" -ENV OSXCROSS_TARGET_DIR="/opt/osxcross/target" -ENV MACOSX_DEPLOYMENT_TARGET=13.0 - -# Create unprefixed symlinks for macOS tools that vcpkg/CMake expect -RUN cd /opt/osxcross/target/bin && \ - for tool in install_name_tool otool lipo codesign; do \ - src="$(ls *-apple-darwin*-"${tool}" 2>/dev/null | head -1)"; \ - if [ -n "$src" ]; then \ - ln -sf "$src" "$tool"; \ - fi; \ - done - -# Custom osxcross toolchain + vcpkg triplets -COPY macos/osxcross-toolchain.cmake /opt/osxcross-toolchain.cmake -COPY macos/triplets/ /opt/vcpkg-triplets/ - -# Extra tools needed by vcpkg's Mach-O rpath fixup and ffmpeg x86 asm -RUN apt-get update && \ - apt-get install -y --no-install-recommends file nasm && \ - rm -rf /var/lib/apt/lists/* - -# vcpkg — macOS cross triplets (arm64-osx-cross / x64-osx-cross) -ENV VCPKG_ROOT=/opt/vcpkg -RUN git clone --depth 1 https://github.com/microsoft/vcpkg.git "${VCPKG_ROOT}" && \ - "${VCPKG_ROOT}/bootstrap-vcpkg.sh" -disableMetrics - -# Pre-install deps for both arches; the launcher script picks the right one at run time. -RUN "${VCPKG_ROOT}/vcpkg" install \ - sdl2[vulkan] \ - openssl \ - glew \ - glm \ - zlib \ - ffmpeg \ - --triplet arm64-osx-cross \ - --overlay-triplets=/opt/vcpkg-triplets - -RUN "${VCPKG_ROOT}/vcpkg" install \ - sdl2[vulkan] \ - openssl \ - glew \ - glm \ - zlib \ - ffmpeg \ - --triplet x64-osx-cross \ - --overlay-triplets=/opt/vcpkg-triplets - -# Vulkan SDK headers (MoltenVK is the runtime — headers only needed to compile) -RUN apt-get update && \ - apt-get install -y --no-install-recommends libvulkan-dev glslang-tools && \ - rm -rf /var/lib/apt/lists/* - -COPY build-macos.sh /build-platform.sh -RUN chmod +x /build-platform.sh - -ENTRYPOINT ["/build-platform.sh"] diff --git a/container/builder-ubuntu.Dockerfile b/container/builder-ubuntu.Dockerfile new file mode 100644 index 00000000..26f32b50 --- /dev/null +++ b/container/builder-ubuntu.Dockerfile @@ -0,0 +1,25 @@ +FROM ubuntu:24.04 + +RUN apt-get update && \ + apt install -y \ + cmake \ + build-essential \ + pkg-config \ + git \ + libsdl2-dev \ + libglew-dev \ + libglm-dev \ + libssl-dev \ + zlib1g-dev \ + libavformat-dev \ + libavcodec-dev \ + libswscale-dev \ + libavutil-dev \ + libvulkan-dev \ + vulkan-tools \ + libstorm-dev && \ + rm -rf /var/lib/apt/lists/* + +COPY build-wowee.sh / + +ENTRYPOINT ./build-wowee.sh diff --git a/container/builder-windows.Dockerfile b/container/builder-windows.Dockerfile deleted file mode 100644 index 0c5abf83..00000000 --- a/container/builder-windows.Dockerfile +++ /dev/null @@ -1,67 +0,0 @@ -FROM ubuntu:24.04 - -# Windows cross-compile using LLVM-MinGW — best-in-class Clang/LLD toolchain -# targeting x86_64-w64-mingw32. Produces native .exe/.dll without MSVC or Wine. -# LLVM-MinGW ships: clang, clang++, lld, libc++ / libunwind headers, winpthreads. - -ENV DEBIAN_FRONTEND=noninteractive -ENV LLVM_MINGW_VERSION=20240619 -ENV LLVM_MINGW_URL=https://github.com/mstorsjo/llvm-mingw/releases/download/${LLVM_MINGW_VERSION}/llvm-mingw-${LLVM_MINGW_VERSION}-ucrt-ubuntu-20.04-x86_64.tar.xz - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - ca-certificates \ - build-essential \ - cmake \ - ninja-build \ - git \ - python3 \ - curl \ - zip \ - unzip \ - tar \ - xz-utils \ - pkg-config \ - nasm \ - libssl-dev \ - zlib1g-dev && \ - rm -rf /var/lib/apt/lists/* - -# Install LLVM-MinGW toolchain -RUN curl -fsSL "${LLVM_MINGW_URL}" -o /tmp/llvm-mingw.tar.xz && \ - tar -xf /tmp/llvm-mingw.tar.xz -C /opt && \ - mv /opt/llvm-mingw-${LLVM_MINGW_VERSION}-ucrt-ubuntu-20.04-x86_64 /opt/llvm-mingw && \ - rm /tmp/llvm-mingw.tar.xz - -ENV PATH="/opt/llvm-mingw/bin:${PATH}" - -# Windows dependencies via vcpkg (static, x64-mingw-static triplet) -ENV VCPKG_ROOT=/opt/vcpkg -RUN git clone --depth 1 https://github.com/microsoft/vcpkg.git "${VCPKG_ROOT}" && \ - "${VCPKG_ROOT}/bootstrap-vcpkg.sh" -disableMetrics - -ENV VCPKG_DEFAULT_TRIPLET=x64-mingw-static -RUN "${VCPKG_ROOT}/vcpkg" install \ - sdl2[vulkan] \ - openssl \ - glew \ - glm \ - zlib \ - ffmpeg \ - --triplet x64-mingw-static - -# Vulkan SDK headers (loader is linked statically via SDL2's vulkan surface) -RUN apt-get update && \ - apt-get install -y --no-install-recommends libvulkan-dev glslang-tools && \ - rm -rf /var/lib/apt/lists/* - -# Provide a no-op powershell.exe so vcpkg's MinGW applocal post-build hook -# exits cleanly. The x64-mingw-static triplet is fully static (no DLLs to -# copy), so the script has nothing to do — it just needs to not fail. -RUN printf '#!/bin/sh\nexit 0\n' > /usr/local/bin/powershell.exe && \ - chmod +x /usr/local/bin/powershell.exe - -COPY build-windows.sh /build-platform.sh -RUN chmod +x /build-platform.sh - -ENTRYPOINT ["/build-platform.sh"] diff --git a/container/macos/osxcross-toolchain.cmake b/container/macos/osxcross-toolchain.cmake deleted file mode 100644 index c432830d..00000000 --- a/container/macos/osxcross-toolchain.cmake +++ /dev/null @@ -1,62 +0,0 @@ -# osxcross CMake toolchain file for cross-compiling to macOS from Linux. -# Used by vcpkg triplets and the WoWee build. -# Auto-detects SDK, darwin version, and arch from the osxcross installation -# and the VCPKG_OSX_ARCHITECTURES / CMAKE_OSX_ARCHITECTURES setting. - -set(CMAKE_SYSTEM_NAME Darwin) - -# ── osxcross paths ────────────────────────────────────────────────── -set(_target_dir "/opt/osxcross/target") -if(DEFINED ENV{OSXCROSS_TARGET_DIR}) - set(_target_dir "$ENV{OSXCROSS_TARGET_DIR}") -endif() - -# Auto-detect SDK (pick the newest if several are present) -file(GLOB _sdk_dirs "${_target_dir}/SDK/MacOSX*.sdk") -list(SORT _sdk_dirs) -list(GET _sdk_dirs -1 _sdk_dir) -set(CMAKE_OSX_SYSROOT "${_sdk_dir}" CACHE PATH "" FORCE) - -# Deployment target -set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0" CACHE STRING "" FORCE) -if(DEFINED ENV{MACOSX_DEPLOYMENT_TARGET}) - set(CMAKE_OSX_DEPLOYMENT_TARGET "$ENV{MACOSX_DEPLOYMENT_TARGET}" CACHE STRING "" FORCE) -endif() - -# ── auto-detect darwin version from compiler names ────────────────── -file(GLOB _darwin_compilers "${_target_dir}/bin/*-apple-darwin*-clang") -list(GET _darwin_compilers 0 _first_compiler) -get_filename_component(_compiler_name "${_first_compiler}" NAME) -string(REGEX MATCH "apple-darwin[0-9.]+" _darwin_part "${_compiler_name}") - -# ── pick architecture ─────────────────────────────────────────────── -# CMAKE_OSX_ARCHITECTURES is set by vcpkg from VCPKG_OSX_ARCHITECTURES -if(CMAKE_OSX_ARCHITECTURES STREQUAL "arm64") - set(_arch "arm64") -elseif(CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64") - set(_arch "x86_64") -elseif(DEFINED ENV{OSXCROSS_ARCH}) - set(_arch "$ENV{OSXCROSS_ARCH}") -else() - set(_arch "arm64") -endif() - -set(_host "${_arch}-${_darwin_part}") -set(CMAKE_SYSTEM_PROCESSOR "${_arch}" CACHE STRING "" FORCE) - -# ── compilers ─────────────────────────────────────────────────────── -set(CMAKE_C_COMPILER "${_target_dir}/bin/${_host}-clang" CACHE FILEPATH "" FORCE) -set(CMAKE_CXX_COMPILER "${_target_dir}/bin/${_host}-clang++" CACHE FILEPATH "" FORCE) - -# ── tools ─────────────────────────────────────────────────────────── -set(CMAKE_AR "${_target_dir}/bin/${_host}-ar" CACHE FILEPATH "" FORCE) -set(CMAKE_RANLIB "${_target_dir}/bin/${_host}-ranlib" CACHE FILEPATH "" FORCE) -set(CMAKE_STRIP "${_target_dir}/bin/${_host}-strip" CACHE FILEPATH "" FORCE) -set(CMAKE_INSTALL_NAME_TOOL "${_target_dir}/bin/${_host}-install_name_tool" CACHE FILEPATH "" FORCE) - -# ── search paths ──────────────────────────────────────────────────── -set(CMAKE_FIND_ROOT_PATH "${_sdk_dir}" "${_target_dir}") -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/container/macos/sdk-fetcher.py b/container/macos/sdk-fetcher.py deleted file mode 100644 index 3b556c27..00000000 --- a/container/macos/sdk-fetcher.py +++ /dev/null @@ -1,380 +0,0 @@ -#!/usr/bin/env python3 -"""Download and extract macOS SDK from Apple's Command Line Tools package. - -Apple publishes Command Line Tools (CLT) packages via their publicly -accessible software update catalog. This script downloads the latest CLT, -extracts just the macOS SDK, and packages it as a .tar.gz tarball suitable -for osxcross. - -No Apple ID or paid developer account required. - -Usage: - python3 sdk-fetcher.py [output_dir] - -The script prints the absolute path of the resulting tarball to stdout. -All progress / status messages go to stderr. -If a cached SDK tarball already exists in output_dir, it is reused. - -Dependencies: python3 (>= 3.6), cpio, tar, gzip -Optional: bsdtar (libarchive-tools) or xar -- faster XAR extraction. - Falls back to a pure-Python XAR parser when neither is available. -""" - -import glob -import gzip -import lzma -import os -import plistlib -import re -import shutil -import struct -import subprocess -import sys -import tempfile -import urllib.request -import zlib - -try: - import defusedxml.ElementTree as ET -except ImportError as exc: - raise ImportError( - "defusedxml is required: pip install defusedxml" - ) from exc - -# -- Configuration ----------------------------------------------------------- - -CATALOG_URLS = [ - # Try newest catalog first; first successful fetch wins. - "https://swscan.apple.com/content/catalogs/others/" - "index-16-15-14-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-" - "mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz", - - "https://swscan.apple.com/content/catalogs/others/" - "index-15-14-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-" - "mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz", - - "https://swscan.apple.com/content/catalogs/others/" - "index-14-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-" - "mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz", -] - -USER_AGENT = "Software%20Update" - - -# -- Helpers ----------------------------------------------------------------- - -def _validate_url(url): - """Reject non-HTTPS URLs to prevent file:// and other scheme attacks.""" - if not url.startswith("https://"): - raise ValueError(f"Refusing non-HTTPS URL: {url}") - - -def log(msg): - print(msg, file=sys.stderr, flush=True) - - -# -- 1) Catalog & URL discovery ---------------------------------------------- - -def find_sdk_pkg_url(): - """Search Apple catalogs for the latest CLTools_macOSNMOS_SDK.pkg URL.""" - for cat_url in CATALOG_URLS: - short = cat_url.split("/index-")[1][:25] + "..." - log(f" Trying catalog: {short}") - try: - _validate_url(cat_url) - req = urllib.request.Request(cat_url, headers={"User-Agent": USER_AGENT}) - with urllib.request.urlopen(req, timeout=60) as resp: # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected - raw = gzip.decompress(resp.read()) - catalog = plistlib.loads(raw) - except Exception as exc: - log(f" -> fetch failed: {exc}") - continue - - products = catalog.get("Products", {}) - candidates = [] - for pid, product in products.items(): - post_date = str(product.get("PostDate", "")) - for pkg in product.get("Packages", []): - url = pkg.get("URL", "") - size = pkg.get("Size", 0) - if "CLTools_macOSNMOS_SDK" in url and url.endswith(".pkg"): - candidates.append((post_date, url, size, pid)) - - if not candidates: - log(f" -> no CLTools SDK packages in this catalog, trying next...") - continue - - candidates.sort(reverse=True) - _date, url, size, pid = candidates[0] - log(f"==> Found: CLTools_macOSNMOS_SDK (product {pid}, {size // 1048576} MB)") - return url - - log("ERROR: No CLTools SDK packages found in any Apple catalog.") - sys.exit(1) - - -# -- 2) Download ------------------------------------------------------------- - -def download(url, dest): - """Download *url* to *dest* with a basic progress indicator.""" - _validate_url(url) - req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) - with urllib.request.urlopen(req, timeout=600) as resp: # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected - total = int(resp.headers.get("Content-Length", 0)) - done = 0 - with open(dest, "wb") as f: - while True: - chunk = resp.read(1 << 20) - if not chunk: - break - f.write(chunk) - done += len(chunk) - if total: - pct = done * 100 // total - log(f"\r {done // 1048576} / {total // 1048576} MB ({pct}%)") - log("") - - -# -- 3) XAR extraction ------------------------------------------------------- - -def extract_xar(pkg_path, dest_dir): - """Extract a XAR (.pkg) archive -- external tool or pure-Python fallback.""" - for tool in ("bsdtar", "xar"): - if shutil.which(tool): - log(f"==> Extracting .pkg with {tool}...") - r = subprocess.run([tool, "-xf", pkg_path, "-C", dest_dir], - capture_output=True) - if r.returncode == 0: - return - log(f" {tool} exited {r.returncode}, trying next method...") - - log("==> Extracting .pkg with built-in Python XAR parser...") - _extract_xar_python(pkg_path, dest_dir) - - -def _extract_xar_python(pkg_path, dest_dir): - """Pure-Python XAR extractor (no external dependencies).""" - with open(pkg_path, "rb") as f: - raw = f.read(28) - if len(raw) < 28: - raise ValueError("File too small to be a valid XAR archive") - magic, hdr_size, _ver, toc_clen, _toc_ulen, _ck = struct.unpack( - ">4sHHQQI", raw, - ) - if magic != b"xar!": - raise ValueError(f"Not a XAR file (magic: {magic!r})") - - f.seek(hdr_size) - toc_xml = zlib.decompress(f.read(toc_clen)) - heap_off = hdr_size + toc_clen - - root = ET.fromstring(toc_xml) - toc = root.find("toc") - if toc is None: - raise ValueError("Malformed XAR: no element") - - def _walk(elem, base): - for fe in elem.findall("file"): - name = fe.findtext("name", "") - ftype = fe.findtext("type", "file") - path = os.path.join(base, name) - - if ftype == "directory": - os.makedirs(path, exist_ok=True) - _walk(fe, path) - continue - - de = fe.find("data") - if de is None: - continue - offset = int(de.findtext("offset", "0")) - size = int(de.findtext("size", "0")) - enc_el = de.find("encoding") - enc = enc_el.get("style", "") if enc_el is not None else "" - - os.makedirs(os.path.dirname(path), exist_ok=True) - f.seek(heap_off + offset) - - if "gzip" in enc: - with open(path, "wb") as out: - out.write(zlib.decompress(f.read(size), 15 + 32)) - elif "bzip2" in enc: - import bz2 - with open(path, "wb") as out: - out.write(bz2.decompress(f.read(size))) - else: - with open(path, "wb") as out: - rem = size - while rem > 0: - blk = f.read(min(rem, 1 << 20)) - if not blk: - break - out.write(blk) - rem -= len(blk) - - _walk(toc, dest_dir) - - -# -- 4) Payload extraction (pbzx / gzip cpio) -------------------------------- - -def _pbzx_stream(path): - """Yield decompressed chunks from a pbzx-compressed file.""" - with open(path, "rb") as f: - if f.read(4) != b"pbzx": - raise ValueError("Not a pbzx file") - f.read(8) - while True: - hdr = f.read(16) - if len(hdr) < 16: - break - _usize, csize = struct.unpack(">QQ", hdr) - data = f.read(csize) - if len(data) < csize: - break - if csize == _usize: - yield data - else: - yield lzma.decompress(data) - - -def _gzip_stream(path): - """Yield decompressed chunks from a gzip file.""" - with gzip.open(path, "rb") as f: - while True: - chunk = f.read(1 << 20) - if not chunk: - break - yield chunk - - -def _raw_stream(path): - """Yield raw 1 MiB chunks (last resort).""" - with open(path, "rb") as f: - while True: - chunk = f.read(1 << 20) - if not chunk: - break - yield chunk - - -def extract_payload(payload_path, out_dir): - """Decompress a CLT Payload (pbzx or gzip cpio) into *out_dir*.""" - with open(payload_path, "rb") as pf: - magic = pf.read(4) - - if magic == b"pbzx": - log(" Payload format: pbzx (LZMA chunks)") - stream = _pbzx_stream(payload_path) - elif magic[:2] == b"\x1f\x8b": - log(" Payload format: gzip") - stream = _gzip_stream(payload_path) - else: - log(f" Payload format: unknown (magic: {magic.hex()}), trying raw cpio...") - stream = _raw_stream(payload_path) - - proc = subprocess.Popen( - ["cpio", "-id", "--quiet"], - stdin=subprocess.PIPE, - cwd=out_dir, - stderr=subprocess.PIPE, - ) - for chunk in stream: - try: - proc.stdin.write(chunk) - except BrokenPipeError: - break - proc.stdin.close() - proc.wait() - - -# -- Main -------------------------------------------------------------------- - -def main(): - output_dir = os.path.abspath(sys.argv[1]) if len(sys.argv) > 1 else os.getcwd() - os.makedirs(output_dir, exist_ok=True) - - # Re-use a previously fetched SDK if present. - cached = glob.glob(os.path.join(output_dir, "MacOSX*.sdk.tar.*")) - if cached: - cached.sort() - result = os.path.realpath(cached[-1]) - log(f"==> Using cached SDK: {os.path.basename(result)}") - print(result) - return - - work = tempfile.mkdtemp(prefix="fetch-macos-sdk-") - - try: - # 1 -- Locate SDK package URL from Apple's catalog - log("==> Searching Apple software-update catalogs...") - sdk_url = find_sdk_pkg_url() - - # 2 -- Download (just the SDK component, ~55 MB) - pkg = os.path.join(work, "sdk.pkg") - log("==> Downloading CLTools SDK package...") - download(sdk_url, pkg) - - # 3 -- Extract the flat .pkg (XAR format) to get the Payload - pkg_dir = os.path.join(work, "pkg") - os.makedirs(pkg_dir) - extract_xar(pkg, pkg_dir) - os.unlink(pkg) - - # 4 -- Locate the Payload file - log("==> Locating SDK payload...") - sdk_payload = None - for dirpath, _dirs, files in os.walk(pkg_dir): - if "Payload" in files: - sdk_payload = os.path.join(dirpath, "Payload") - log(f" Found: {os.path.relpath(sdk_payload, pkg_dir)}") - break - - if sdk_payload is None: - log("ERROR: No Payload found in extracted package") - sys.exit(1) - - # 5 -- Decompress Payload -> cpio -> filesystem - sdk_root = os.path.join(work, "sdk") - os.makedirs(sdk_root) - log("==> Extracting SDK from payload (this may take a minute)...") - extract_payload(sdk_payload, sdk_root) - shutil.rmtree(pkg_dir) - - # 6 -- Find MacOSX*.sdk directory - sdk_found = None - for dirpath, dirs, _files in os.walk(sdk_root): - for d in dirs: - if re.match(r"MacOSX\d+(\.\d+)?\.sdk$", d): - sdk_found = os.path.join(dirpath, d) - break - if sdk_found: - break - - if not sdk_found: - log("ERROR: MacOSX*.sdk directory not found. Extracted contents:") - for dp, ds, fs in os.walk(sdk_root): - depth = dp.replace(sdk_root, "").count(os.sep) - if depth < 4: - log(f" {' ' * depth}{os.path.basename(dp)}/") - sys.exit(1) - - sdk_name = os.path.basename(sdk_found) - log(f"==> Found: {sdk_name}") - - # 7 -- Package as .tar.gz - tarball = os.path.join(output_dir, f"{sdk_name}.tar.gz") - log(f"==> Packaging: {sdk_name}.tar.gz ...") - subprocess.run( - ["tar", "-czf", tarball, "-C", os.path.dirname(sdk_found), sdk_name], - check=True, - ) - - log(f"==> macOS SDK ready: {tarball}") - print(tarball) - - finally: - shutil.rmtree(work, ignore_errors=True) - - -if __name__ == "__main__": - main() diff --git a/container/macos/triplets/arm64-osx-cross.cmake b/container/macos/triplets/arm64-osx-cross.cmake deleted file mode 100644 index 5f38d553..00000000 --- a/container/macos/triplets/arm64-osx-cross.cmake +++ /dev/null @@ -1,10 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE arm64) -set(VCPKG_CRT_LINKAGE dynamic) -set(VCPKG_LIBRARY_LINKAGE static) -set(VCPKG_CMAKE_SYSTEM_NAME Darwin) - -set(VCPKG_OSX_ARCHITECTURES arm64) -set(VCPKG_OSX_DEPLOYMENT_TARGET 13.0) - -# osxcross toolchain -set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE /opt/osxcross-toolchain.cmake) diff --git a/container/macos/triplets/x64-osx-cross.cmake b/container/macos/triplets/x64-osx-cross.cmake deleted file mode 100644 index 84161bba..00000000 --- a/container/macos/triplets/x64-osx-cross.cmake +++ /dev/null @@ -1,10 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE x64) -set(VCPKG_CRT_LINKAGE dynamic) -set(VCPKG_LIBRARY_LINKAGE static) -set(VCPKG_CMAKE_SYSTEM_NAME Darwin) - -set(VCPKG_OSX_ARCHITECTURES x86_64) -set(VCPKG_OSX_DEPLOYMENT_TARGET 13.0) - -# osxcross toolchain -set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE /opt/osxcross-toolchain.cmake) diff --git a/container/run-linux.ps1 b/container/run-linux.ps1 deleted file mode 100644 index 67ce067f..00000000 --- a/container/run-linux.ps1 +++ /dev/null @@ -1,64 +0,0 @@ -# run-linux.ps1 — Build WoWee for Linux (amd64) inside a Docker container. -# -# Usage (run from project root): -# .\container\run-linux.ps1 [-RebuildImage] -# -# Environment variables: -# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) -# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) - -param( - [switch]$RebuildImage -) - -$ErrorActionPreference = "Stop" - -$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition -$ProjectRoot = (Resolve-Path "$ScriptDir\..").Path - -$ImageName = "wowee-builder-linux" -$BuildOutput = "$ProjectRoot\build\linux" - -# Verify Docker is available -if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { - Write-Error "docker is not installed or not in PATH." - exit 1 -} - -# Build the image (skip if already present and -RebuildImage not given) -$imageExists = docker image inspect $ImageName 2>$null -if ($RebuildImage -or -not $imageExists) { - Write-Host "==> Building Docker image: $ImageName" - docker build ` - -f "$ScriptDir\builder-linux.Dockerfile" ` - -t $ImageName ` - "$ScriptDir" - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -} else { - Write-Host "==> Using existing Docker image: $ImageName" -} - -# Create output directory on the host -New-Item -ItemType Directory -Force -Path $BuildOutput | Out-Null - -Write-Host "==> Starting Linux build (output: $BuildOutput)" - -$dockerArgs = @( - "run", "--rm", - "--mount", "type=bind,src=$ProjectRoot,dst=/src,readonly", - "--mount", "type=bind,src=$BuildOutput,dst=/out" -) - -if ($env:WOWEE_FFX_SDK_REPO) { - $dockerArgs += @("--env", "WOWEE_FFX_SDK_REPO=$env:WOWEE_FFX_SDK_REPO") -} -if ($env:WOWEE_FFX_SDK_REF) { - $dockerArgs += @("--env", "WOWEE_FFX_SDK_REF=$env:WOWEE_FFX_SDK_REF") -} - -$dockerArgs += $ImageName - -& docker @dockerArgs -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - -Write-Host "==> Linux build complete. Artifacts in: $BuildOutput" diff --git a/container/run-linux.sh b/container/run-linux.sh deleted file mode 100755 index db45bea7..00000000 --- a/container/run-linux.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash -# run-linux.sh — Build WoWee for Linux (amd64) inside a Docker container. -# -# Usage (run from project root): -# ./container/run-linux.sh [--rebuild-image] -# -# Environment variables: -# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) -# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) -# REBUILD_IMAGE — Set to 1 to force a fresh docker build (same as --rebuild-image) - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" - -IMAGE_NAME="wowee-builder-linux" -BUILD_OUTPUT="${PROJECT_ROOT}/build/linux" - -# Parse arguments -REBUILD_IMAGE="${REBUILD_IMAGE:-0}" -for arg in "$@"; do - case "$arg" in - --rebuild-image) REBUILD_IMAGE=1 ;; - *) echo "Unknown argument: $arg" >&2; exit 1 ;; - esac -done - -# Verify Docker is available -if ! command -v docker &>/dev/null; then - echo "Error: docker is not installed or not in PATH." >&2 - exit 1 -fi - -# Build the image (skip if already present and --rebuild-image not given) -if [[ "$REBUILD_IMAGE" == "1" ]] || ! docker image inspect "$IMAGE_NAME" &>/dev/null; then - echo "==> Building Docker image: ${IMAGE_NAME}" - docker build \ - -f "${SCRIPT_DIR}/builder-linux.Dockerfile" \ - -t "$IMAGE_NAME" \ - "${SCRIPT_DIR}" -else - echo "==> Using existing Docker image: ${IMAGE_NAME}" -fi - -# Create output directory on the host -mkdir -p "$BUILD_OUTPUT" - -echo "==> Starting Linux build (output: ${BUILD_OUTPUT})" - -docker run --rm \ - --mount "type=bind,src=${PROJECT_ROOT},dst=/src,readonly" \ - --mount "type=bind,src=${BUILD_OUTPUT},dst=/out" \ - ${WOWEE_FFX_SDK_REPO:+--env "WOWEE_FFX_SDK_REPO=${WOWEE_FFX_SDK_REPO}"} \ - ${WOWEE_FFX_SDK_REF:+--env "WOWEE_FFX_SDK_REF=${WOWEE_FFX_SDK_REF}"} \ - "$IMAGE_NAME" - -echo "==> Linux build complete. Artifacts in: ${BUILD_OUTPUT}" diff --git a/container/run-macos.ps1 b/container/run-macos.ps1 deleted file mode 100644 index b690edfb..00000000 --- a/container/run-macos.ps1 +++ /dev/null @@ -1,71 +0,0 @@ -# run-macos.ps1 — Cross-compile WoWee for macOS (arm64 or x86_64) inside a Docker container. -# -# Usage (run from project root): -# .\container\run-macos.ps1 [-RebuildImage] [-Arch arm64|x86_64] -# -# The macOS SDK is fetched automatically inside the Docker build from Apple's -# public software update catalog. No manual SDK download required. -# -# Environment variables: -# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) -# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) - -param( - [switch]$RebuildImage, - [ValidateSet("arm64", "x86_64")] - [string]$Arch = "arm64" -) - -$ErrorActionPreference = "Stop" - -$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition -$ProjectRoot = (Resolve-Path "$ScriptDir\..").Path - -$ImageName = "wowee-builder-macos" -$BuildOutput = "$ProjectRoot\build\macos" - -# Verify Docker is available -if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { - Write-Error "docker is not installed or not in PATH." - exit 1 -} - -# Build the image (skip if already present and -RebuildImage not given) -$imageExists = docker image inspect $ImageName 2>$null -if ($RebuildImage -or -not $imageExists) { - Write-Host "==> Building Docker image: $ImageName" - Write-Host " (SDK will be fetched automatically from Apple's catalog)" - docker build ` - -f "$ScriptDir\builder-macos.Dockerfile" ` - -t $ImageName ` - "$ScriptDir" - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -} else { - Write-Host "==> Using existing Docker image: $ImageName" -} - -# Create output directory on the host -New-Item -ItemType Directory -Force -Path $BuildOutput | Out-Null - -Write-Host "==> Starting macOS cross-compile build (arch=$Arch, output: $BuildOutput)" - -$dockerArgs = @( - "run", "--rm", - "--mount", "type=bind,src=$ProjectRoot,dst=/src,readonly", - "--mount", "type=bind,src=$BuildOutput,dst=/out", - "--env", "MACOS_ARCH=$Arch" -) - -if ($env:WOWEE_FFX_SDK_REPO) { - $dockerArgs += @("--env", "WOWEE_FFX_SDK_REPO=$env:WOWEE_FFX_SDK_REPO") -} -if ($env:WOWEE_FFX_SDK_REF) { - $dockerArgs += @("--env", "WOWEE_FFX_SDK_REF=$env:WOWEE_FFX_SDK_REF") -} - -$dockerArgs += $ImageName - -& docker @dockerArgs -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - -Write-Host "==> macOS cross-compile build complete. Artifacts in: $BuildOutput" diff --git a/container/run-macos.sh b/container/run-macos.sh deleted file mode 100755 index 63e1b971..00000000 --- a/container/run-macos.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env bash -# run-macos.sh — Cross-compile WoWee for macOS (arm64 or x86_64) inside a Docker container. -# -# Usage (run from project root): -# ./container/run-macos.sh [--rebuild-image] -# -# The macOS SDK is fetched automatically inside the Docker build from Apple's -# public software update catalog. No manual SDK download required. -# -# Environment variables: -# MACOS_ARCH — Target arch: arm64 (default) or x86_64 -# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) -# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) -# REBUILD_IMAGE — Set to 1 to force a fresh docker build (same as --rebuild-image) -# -# Toolchain: osxcross (Clang + Apple ld) -# vcpkg triplets: arm64-osx-cross (arm64) / x64-osx-cross (x86_64) - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" - -IMAGE_NAME="wowee-builder-macos" -MACOS_ARCH="${MACOS_ARCH:-arm64}" -BUILD_OUTPUT="${PROJECT_ROOT}/build/macos" - -# Parse arguments -REBUILD_IMAGE="${REBUILD_IMAGE:-0}" -for arg in "$@"; do - case "$arg" in - --rebuild-image) REBUILD_IMAGE=1 ;; - *) echo "Unknown argument: $arg" >&2; exit 1 ;; - esac -done - -# Validate arch -if [[ "$MACOS_ARCH" != "arm64" && "$MACOS_ARCH" != "x86_64" ]]; then - echo "Error: MACOS_ARCH must be 'arm64' or 'x86_64' (got: ${MACOS_ARCH})" >&2 - exit 1 -fi - -# Verify Docker is available -if ! command -v docker &>/dev/null; then - echo "Error: docker is not installed or not in PATH." >&2 - exit 1 -fi - -# Build the image (skip if already present and --rebuild-image not given) -if [[ "$REBUILD_IMAGE" == "1" ]] || ! docker image inspect "$IMAGE_NAME" &>/dev/null; then - echo "==> Building Docker image: ${IMAGE_NAME}" - echo " (SDK will be fetched automatically from Apple's catalog)" - docker build \ - -f "${SCRIPT_DIR}/builder-macos.Dockerfile" \ - -t "$IMAGE_NAME" \ - "${SCRIPT_DIR}" -else - echo "==> Using existing Docker image: ${IMAGE_NAME}" -fi - -# Create output directory on the host -mkdir -p "$BUILD_OUTPUT" - -echo "==> Starting macOS cross-compile build (arch=${MACOS_ARCH}, output: ${BUILD_OUTPUT})" - -docker run --rm \ - --mount "type=bind,src=${PROJECT_ROOT},dst=/src,readonly" \ - --mount "type=bind,src=${BUILD_OUTPUT},dst=/out" \ - --env "MACOS_ARCH=${MACOS_ARCH}" \ - ${WOWEE_FFX_SDK_REPO:+--env "WOWEE_FFX_SDK_REPO=${WOWEE_FFX_SDK_REPO}"} \ - ${WOWEE_FFX_SDK_REF:+--env "WOWEE_FFX_SDK_REF=${WOWEE_FFX_SDK_REF}"} \ - "$IMAGE_NAME" - -echo "==> macOS cross-compile build complete. Artifacts in: ${BUILD_OUTPUT}" diff --git a/container/run-windows.ps1 b/container/run-windows.ps1 deleted file mode 100644 index 1ce6c724..00000000 --- a/container/run-windows.ps1 +++ /dev/null @@ -1,64 +0,0 @@ -# run-windows.ps1 — Cross-compile WoWee for Windows (x86_64) inside a Docker container. -# -# Usage (run from project root): -# .\container\run-windows.ps1 [-RebuildImage] -# -# Environment variables: -# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) -# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) - -param( - [switch]$RebuildImage -) - -$ErrorActionPreference = "Stop" - -$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition -$ProjectRoot = (Resolve-Path "$ScriptDir\..").Path - -$ImageName = "wowee-builder-windows" -$BuildOutput = "$ProjectRoot\build\windows" - -# Verify Docker is available -if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { - Write-Error "docker is not installed or not in PATH." - exit 1 -} - -# Build the image (skip if already present and -RebuildImage not given) -$imageExists = docker image inspect $ImageName 2>$null -if ($RebuildImage -or -not $imageExists) { - Write-Host "==> Building Docker image: $ImageName" - docker build ` - -f "$ScriptDir\builder-windows.Dockerfile" ` - -t $ImageName ` - "$ScriptDir" - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -} else { - Write-Host "==> Using existing Docker image: $ImageName" -} - -# Create output directory on the host -New-Item -ItemType Directory -Force -Path $BuildOutput | Out-Null - -Write-Host "==> Starting Windows cross-compile build (output: $BuildOutput)" - -$dockerArgs = @( - "run", "--rm", - "--mount", "type=bind,src=$ProjectRoot,dst=/src,readonly", - "--mount", "type=bind,src=$BuildOutput,dst=/out" -) - -if ($env:WOWEE_FFX_SDK_REPO) { - $dockerArgs += @("--env", "WOWEE_FFX_SDK_REPO=$env:WOWEE_FFX_SDK_REPO") -} -if ($env:WOWEE_FFX_SDK_REF) { - $dockerArgs += @("--env", "WOWEE_FFX_SDK_REF=$env:WOWEE_FFX_SDK_REF") -} - -$dockerArgs += $ImageName - -& docker @dockerArgs -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - -Write-Host "==> Windows cross-compile build complete. Artifacts in: $BuildOutput" diff --git a/container/run-windows.sh b/container/run-windows.sh deleted file mode 100755 index b7a89ff7..00000000 --- a/container/run-windows.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash -# run-windows.sh — Cross-compile WoWee for Windows (x86_64) inside a Docker container. -# -# Usage (run from project root): -# ./container/run-windows.sh [--rebuild-image] -# -# Environment variables: -# WOWEE_FFX_SDK_REPO — FidelityFX SDK git repo URL (passed through to container) -# WOWEE_FFX_SDK_REF — FidelityFX SDK git ref / tag (passed through to container) -# REBUILD_IMAGE — Set to 1 to force a fresh docker build (same as --rebuild-image) -# -# Toolchain: LLVM-MinGW (Clang + LLD) targeting x86_64-w64-mingw32-ucrt -# vcpkg triplet: x64-mingw-static - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" - -IMAGE_NAME="wowee-builder-windows" -BUILD_OUTPUT="${PROJECT_ROOT}/build/windows" - -# Parse arguments -REBUILD_IMAGE="${REBUILD_IMAGE:-0}" -for arg in "$@"; do - case "$arg" in - --rebuild-image) REBUILD_IMAGE=1 ;; - *) echo "Unknown argument: $arg" >&2; exit 1 ;; - esac -done - -# Verify Docker is available -if ! command -v docker &>/dev/null; then - echo "Error: docker is not installed or not in PATH." >&2 - exit 1 -fi - -# Build the image (skip if already present and --rebuild-image not given) -if [[ "$REBUILD_IMAGE" == "1" ]] || ! docker image inspect "$IMAGE_NAME" &>/dev/null; then - echo "==> Building Docker image: ${IMAGE_NAME}" - docker build \ - -f "${SCRIPT_DIR}/builder-windows.Dockerfile" \ - -t "$IMAGE_NAME" \ - "${SCRIPT_DIR}" -else - echo "==> Using existing Docker image: ${IMAGE_NAME}" -fi - -# Create output directory on the host -mkdir -p "$BUILD_OUTPUT" - -echo "==> Starting Windows cross-compile build (output: ${BUILD_OUTPUT})" - -docker run --rm \ - --mount "type=bind,src=${PROJECT_ROOT},dst=/src,readonly" \ - --mount "type=bind,src=${BUILD_OUTPUT},dst=/out" \ - ${WOWEE_FFX_SDK_REPO:+--env "WOWEE_FFX_SDK_REPO=${WOWEE_FFX_SDK_REPO}"} \ - ${WOWEE_FFX_SDK_REF:+--env "WOWEE_FFX_SDK_REF=${WOWEE_FFX_SDK_REF}"} \ - "$IMAGE_NAME" - -echo "==> Windows cross-compile build complete. Artifacts in: ${BUILD_OUTPUT}" diff --git a/docs/ANIMATION_SYSTEM.md b/docs/ANIMATION_SYSTEM.md deleted file mode 100644 index 6dca06d7..00000000 --- a/docs/ANIMATION_SYSTEM.md +++ /dev/null @@ -1,110 +0,0 @@ -# Animation System - -Unified, FSM-based animation system for all characters (players, NPCs, companions). -Every character uses the same `CharacterAnimator` — there is no separate NPC/Mob animator. - -## Architecture - -``` -AnimationController (thin adapter — bridges Renderer ↔ CharacterAnimator) - └─ CharacterAnimator (FSM composer — implements ICharacterAnimator) - ├─ CombatFSM (stun, hit reaction, spell cast, melee, ranged, charge) - ├─ ActivityFSM (emote, loot, sit/stand/kneel/sleep) - ├─ LocomotionFSM (idle, walk, run, sprint, jump, swim, strafe) - └─ MountFSM (mount idle, mount run, flight) - -AnimationManager (registry of CharacterAnimator instances by ID) -AnimCapabilitySet (probed once per model — cached resolved anim IDs) -AnimCapabilityProbe (queries which animations a model supports) -``` - -### Priority Resolution - -`CharacterAnimator::resolveAnimation()` runs every frame. The first FSM to -return a valid `AnimOutput` wins: - -1. **Mount** — if mounted, return `MOUNT` (overrides everything) -2. **Combat** — stun > hit reaction > spell > charge > melee/ranged > combat idle -3. **Activity** — emote > loot > sit/stand transitions -4. **Locomotion** — run/walk/sprint/jump/swim/strafe/idle - -If no FSM produces a valid output, the last animation continues (STAY policy). - -### Overlay Layer - -After resolution, `applyOverlays()` substitutes stealth animation variants -(stealth idle, stealth walk, stealth run) without changing sub-FSM state. - -## File Map - -### Headers (`include/rendering/animation/`) - -| File | Purpose | -|---|---| -| `i_animator.hpp` | Base interface: `onEvent()`, `update()` | -| `i_character_animator.hpp` | 20 virtual methods (combat, spells, emotes, mounts, etc.) | -| `character_animator.hpp` | FSM composer — the single animator class | -| `locomotion_fsm.hpp` | Movement states: idle, walk, run, sprint, jump, swim | -| `combat_fsm.hpp` | Combat states: melee, ranged, spell cast, stun, hit reaction | -| `activity_fsm.hpp` | Activity states: emote, loot, sit/stand/kneel | -| `mount_fsm.hpp` | Mount states: idle, run, flight, taxi | -| `anim_capability_set.hpp` | Probed capability flags + resolved animation IDs | -| `anim_capability_probe.hpp` | Probes a model for available animations | -| `anim_event.hpp` | `AnimEvent` enum (MOVE_START, MOVE_STOP, JUMP, etc.) | -| `animation_manager.hpp` | Central registry of CharacterAnimator instances | -| `weapon_type.hpp` | WeaponLoadout, RangedWeaponType enums | -| `emote_registry.hpp` | Emote name → animation ID lookup | -| `footstep_driver.hpp` | Footstep sound event driver | -| `sfx_state_driver.hpp` | State-transition SFX (jump, land, swim enter/exit) | -| `i_anim_renderer.hpp` | Interface for renderer animation queries | - -### Sources (`src/rendering/animation/`) - -| File | Purpose | -|---|---| -| `character_animator.cpp` | ICharacterAnimator implementation + priority resolver | -| `locomotion_fsm.cpp` | Locomotion state transitions + resolve logic | -| `combat_fsm.cpp` | Combat state transitions + resolve logic | -| `activity_fsm.cpp` | Activity state transitions + resolve logic | -| `mount_fsm.cpp` | Mount state transitions + resolve logic | -| `anim_capability_probe.cpp` | Model animation probing | -| `animation_manager.cpp` | Registry CRUD + bulk update | -| `emote_registry.cpp` | Emote database | -| `footstep_driver.cpp` | Footstep timing logic | -| `sfx_state_driver.cpp` | SFX transition detection | - -### Controller (`include/rendering/animation_controller.hpp` + `src/rendering/animation_controller.cpp`) - -Thin adapter that: -- Collects per-frame input from camera/renderer → `CharacterAnimator::FrameInput` -- Forwards state changes (combat, emote, spell, mount, etc.) → `CharacterAnimator` -- Reads `AnimOutput` → applies via `CharacterRenderer` -- Owns footstep and SFX drivers - -## Key Types - -- **`AnimEvent`** — discrete events: `MOVE_START`, `MOVE_STOP`, `JUMP`, `LAND`, `MOUNT`, `DISMOUNT`, etc. -- **`AnimOutput`** — result of FSM resolution: `{animId, loop, valid}`. `valid=false` means STAY. -- **`AnimCapabilitySet`** — probed once per model load. Caches resolved IDs and capability flags. -- **`CharacterAnimator::FrameInput`** — per-frame input struct (movement flags, timers, animation state queries). - -## Adding a New Animation State - -1. Decide which FSM owns the state (combat, activity, locomotion, or mount). -2. Add the state enum to the FSM's `State` enum. -3. Add transitions in the FSM's `resolve()` method. -4. Add resolved ID fields to `AnimCapabilitySet` if the animation needs model probing. -5. If the state needs external triggering, add a method to `ICharacterAnimator` and implement in `CharacterAnimator`. - -## Tests - -Each FSM has its own test file in `tests/`: -- `test_locomotion_fsm.cpp` -- `test_combat_fsm.cpp` -- `test_activity_fsm.cpp` -- `test_anim_capability.cpp` - -Run all tests: -```bash -cd build && ctest --output-on-failure -``` diff --git a/docs/WARDEN_IMPLEMENTATION.md b/docs/WARDEN_IMPLEMENTATION.md index ff4f4a2e..d328c476 100644 --- a/docs/WARDEN_IMPLEMENTATION.md +++ b/docs/WARDEN_IMPLEMENTATION.md @@ -93,16 +93,13 @@ The RSA public modulus is extracted from WoW.exe (`.rdata` section at offset 0x0 ## Key Files ``` -include/game/warden_handler.hpp - Packet handler interface -src/game/warden_handler.cpp - handleWardenData + module manager init include/game/warden_module.hpp - Module loader interface src/game/warden_module.cpp - 8-step pipeline include/game/warden_emulator.hpp - Emulator interface src/game/warden_emulator.cpp - Unicorn Engine executor + API hooks include/game/warden_crypto.hpp - Crypto interface src/game/warden_crypto.cpp - RC4 / key derivation -include/game/warden_memory.hpp - PE image + memory patch interface -src/game/warden_memory.cpp - PE loader, runtime globals patching +src/game/game_handler.cpp - Packet handler (handleWardenData) ``` --- diff --git a/docs/WARDEN_QUICK_REFERENCE.md b/docs/WARDEN_QUICK_REFERENCE.md index faba1e71..17c127d3 100644 --- a/docs/WARDEN_QUICK_REFERENCE.md +++ b/docs/WARDEN_QUICK_REFERENCE.md @@ -58,11 +58,10 @@ strict Warden enforcement in that mode. ## Key Files ``` -include/game/warden_handler.hpp + src/game/warden_handler.cpp - Packet handler -include/game/warden_module.hpp + src/game/warden_module.cpp - Module loader (8-step pipeline) -include/game/warden_emulator.hpp + src/game/warden_emulator.cpp - Unicorn Engine executor -include/game/warden_crypto.hpp + src/game/warden_crypto.cpp - RC4/MD5/SHA1/RSA crypto -include/game/warden_memory.hpp + src/game/warden_memory.cpp - PE image + memory patching +src/game/warden_module.hpp/cpp - Module loader (8-step pipeline) +src/game/warden_emulator.hpp/cpp - Unicorn Engine executor +src/game/warden_crypto.hpp/cpp - RC4/MD5/SHA1/RSA crypto +src/game/game_handler.cpp - Packet handler (handleWardenData) ``` --- diff --git a/docs/architecture.md b/docs/architecture.md index b7da1c6e..65394b57 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -8,7 +8,7 @@ Wowee follows a modular architecture with clear separation of concerns: ┌─────────────────────────────────────────────┐ │ Application (main loop) │ │ - State management (auth/realms/game) │ -│ - Update cycle │ +│ - Update cycle (60 FPS) │ │ - Event dispatch │ └──────────────┬──────────────────────────────┘ │ @@ -16,8 +16,8 @@ Wowee follows a modular architecture with clear separation of concerns: │ │ ┌──────▼──────┐ ┌─────▼──────┐ │ Window │ │ Input │ -│ (SDL2 + │ │ (Keyboard/ │ -│ Vulkan) │ │ Mouse) │ +│ (SDL2) │ │ (Keyboard/ │ +│ │ │ Mouse) │ └──────┬──────┘ └─────┬──────┘ │ │ └───────┬────────┘ @@ -26,294 +26,517 @@ Wowee follows a modular architecture with clear separation of concerns: │ │ ┌───▼────────┐ ┌───────▼──────┐ │ Renderer │ │ UI Manager │ -│ (Vulkan) │ │ (ImGui) │ +│ (OpenGL) │ │ (ImGui) │ └───┬────────┘ └──────────────┘ │ - ├─ Camera + CameraController - ├─ TerrainRenderer (ADT streaming) - ├─ WMORenderer (buildings, collision) - ├─ M2Renderer (models, particles, ribbons) - ├─ CharacterRenderer (skeletal animation) - ├─ WaterRenderer (refraction, lava, slime) - ├─ SkyBox + StarField + Weather - ├─ LightingManager (Light.dbc volumes) - └─ SwimEffects, ChargeEffect, Lightning + ├─ Camera + ├─ Scene Graph + ├─ Shaders + ├─ Meshes + └─ Textures ``` ## Core Systems ### 1. Application Layer (`src/core/`) -**Application** (`application.hpp/cpp`) - Main controller -- Owns all subsystems (renderer, game handler, asset manager, UI) -- Manages application state (AUTH → REALM_SELECT → CHAR_SELECT → IN_WORLD) +**Application** - Main controller +- Owns all subsystems +- Manages application state - Runs update/render loop -- Populates `GameServices` struct and passes to `GameHandler` at construction +- Handles lifecycle (init/shutdown) -**Window** (`window.hpp/cpp`) - SDL2 + Vulkan wrapper -- Creates SDL2 window with Vulkan surface -- Owns `VkContext` (Vulkan device, swapchain, render passes) +**Window** - SDL2 wrapper +- Creates window and OpenGL context - Handles resize events +- Manages VSync and fullscreen -**Input** (`input.hpp/cpp`) - Input management -- Keyboard state tracking (SDL scancodes) -- Mouse position, buttons (1-based SDL indices), wheel delta -- Per-frame delta calculation +**Input** - Input management +- Keyboard state tracking +- Mouse position and buttons +- Mouse locking for camera control -**Logger** (`logger.hpp/cpp`) - Thread-safe logging +**Logger** - Logging system +- Thread-safe logging - Multiple log levels (DEBUG, INFO, WARNING, ERROR, FATAL) -- File output to `logs/wowee.log` -- Configurable via `WOWEE_LOG_LEVEL` env var +- Timestamp formatting ### 2. Rendering System (`src/rendering/`) -**Renderer** (`renderer.hpp/cpp`) - Main rendering coordinator -- Manages Vulkan pipeline state -- Coordinates frame rendering across all sub-renderers -- Owns camera, sky, weather, lighting, and all sub-renderers -- Shadow mapping with PCF filtering +**Renderer** - Main rendering coordinator +- Manages OpenGL state +- Coordinates frame rendering +- Owns camera and scene -**VkContext** (`vk_context.hpp/cpp`) - Vulkan infrastructure -- Device selection, queue families, swapchain -- Render passes, framebuffers, command pools -- Sampler cache (FNV-1a hashed dedup) -- Pipeline cache persistence for fast startup - -**Camera** (`camera.hpp/cpp`) - View/projection matrices +**Camera** - View/projection matrices - Position and orientation -- FOV, aspect ratio, near/far planes -- Sub-pixel jitter for TAA/FSR2 (column 2 NDC offset) -- Frustum extraction for culling +- FOV and aspect ratio +- View frustum (for culling) -**TerrainRenderer** - ADT terrain streaming -- Async chunk loading within configurable radius -- 4-layer texture splatting with alpha blending -- Frustum + distance culling -- Vegetation/foliage placement via deterministic RNG +**Scene** - Scene graph +- Mesh collection +- Spatial organization +- Visibility determination -**WMORenderer** - World Map Objects (buildings) -- Multi-material batch rendering -- Portal-based visibility culling -- Floor/wall collision (normal-based classification) -- Interior glass transparency, doodad placement +**Shader** - GLSL program wrapper +- Loads vertex/fragment shaders +- Uniform management +- Compilation and linking -**M2Renderer** - Models (creatures, doodads, spell effects) -- Skeletal animation with GPU bone transforms -- Particle emitters (WotLK FBlock format) -- Ribbon emitters (charge trails, enchant glows) -- Portal spin effects, foliage wind displacement -- Per-instance animation state +**Mesh** - Geometry container +- Vertex buffer (position, normal, texcoord) +- Index buffer +- VAO/VBO/EBO management -**CharacterRenderer** - Player/NPC character models -- GPU vertex skinning (256 bones) -- Race/gender-aware textures via CharSections.dbc -- Equipment rendering (geoset visibility per slot) -- Fallback textures (white/transparent/flat-normal) for missing assets +**Texture** - Texture management +- Loading (BLP via `AssetManager`, optional PNG overrides for development) +- OpenGL texture object +- Mipmap generation -**WaterRenderer** - Terrain and WMO water -- Refraction/reflection rendering -- Magma/slime with multi-octave FBM noise flow -- Beer-Lambert absorption - -**Skybox + StarField + Weather** -- Procedural sky dome with time-of-day lighting -- Star field with day/night fade (dusk 18:00–20:00, dawn 04:00–06:00) -- Rain/snow particle systems per zone (via zone weather table) - -**LightingManager** - Light.dbc volume sampling -- Time-of-day color bands (half-minutes, 0–2879) -- Distance-weighted light volume blending -- Fog color/distance parameters +**Material** - Surface properties +- Shader assignment +- Texture binding +- Color/properties ### 3. Networking (`src/network/`) -**TCPSocket** (`tcp_socket.hpp/cpp`) - Platform TCP -- Non-blocking I/O with per-frame recv budgets -- 4 KB recv buffer per call -- Portable across Linux/macOS/Windows +**Socket** (Abstract base class) +- Connection interface +- Packet send/receive +- Callback system -**WorldSocket** (`world_socket.hpp/cpp`) - WoW world connection -- RC4 header encryption (derived from SRP session key) -- Packet parsing with configurable per-frame budgets -- Compressed move packet handling +**TCPSocket** - Linux TCP sockets +- Non-blocking I/O +- Raw TCP (replaces WebSocket) +- Packet framing -**Packet** (`packet.hpp/cpp`) - Binary data container -- Read/write primitives (uint8–uint64, float, string, packed GUID) -- Bounds-checked reads (return 0 past end) +**Packet** - Binary data container +- Read/write primitives +- Byte order handling +- Opcode management ### 4. Authentication (`src/auth/`) -**AuthHandler** - Auth server protocol (port 3724) -- SRP6a challenge/proof flow -- Security flags: PIN (0x01), Matrix (0x02), Authenticator (0x04) -- Realm list retrieval +**AuthHandler** - Auth server protocol +- Connects to port 3724 +- SRP authentication flow +- Session key generation -**SRP** (`srp.hpp/cpp`) - Secure Remote Password -- SRP6a with 19-byte (152-bit) ephemeral -- OpenSSL BIGNUM math -- Session key generation (40 bytes) +**SRP** - Secure Remote Password +- SRP6a algorithm +- Big integer math +- Salt and verifier generation -**Integrity** - Client integrity verification -- Checksum computation for Warden compatibility +**Crypto** - Cryptographic functions +- SHA1 hashing (OpenSSL) +- Random number generation +- Encryption helpers ### 5. Game Logic (`src/game/`) -**GameHandler** (`game_handler.hpp/cpp`) - Central game state -- Dispatch table routing 664+ opcodes to domain handlers -- Owns all domain handlers via composition -- Receives dependencies via `GameServices` struct (no singleton access) +**GameHandler** - World server protocol +- Connects to port 8085 (configurable) +- Packet handlers for 100+ opcodes +- Session management with RC4 encryption +- Character enumeration and login flow -**Domain Handlers** (SOLID decomposition from GameHandler): -- `EntityController` - UPDATE_OBJECT parsing, entity spawn/despawn -- `MovementHandler` - Movement packets, speed, taxi, swimming, flying -- `CombatHandler` - Damage, healing, death, auto-attack, threat -- `SpellHandler` - Spell casting, cooldowns, auras, talents, pet spells -- `InventoryHandler` - Equipment, bags, bank, mail, auction, vendors -- `QuestHandler` - Quest accept/complete, objectives, progress tracking -- `SocialHandler` - Party, guild, LFG, friends, who, duel, trade -- `ChatHandler` - Chat messages, channels, emotes, system messages -- `WardenHandler` - Anti-cheat module management +**World** - Game world state +- Map loading with async terrain streaming +- Entity management (players, NPCs, creatures) +- Zone management and exploration +- Time-of-day synchronization -**OpcodeTable** - Expansion-agnostic opcode mapping -- `LogicalOpcode` enum → wire opcode via JSON config per expansion -- Runtime remapping for Classic/TBC/WotLK/Turtle protocol differences +**Player** - Player character +- Position and movement (WASD + spline movement) +- Stats tracking (health, mana, XP, level) +- Equipment and inventory (23 + 16 slots) +- Action queue and spell casting +- Death and resurrection handling -**Entity / EntityManager** - Entity lifecycle -- Shared entity base class with update fields (uint32 array) -- Player, Unit, GameObject subtypes -- GUID-based lookup, field extraction (health, level, display ID, etc.) +**Character** - Character data +- Race, class, gender, appearance +- Creation and customization +- 3D model preview +- Online character lifecycle and state synchronization -**TransportManager** - Transport path evaluation -- Catmull-Rom spline interpolation from TransportAnimation.dbc -- Clock-based motion with server time synchronization -- Time-closed looping paths (wrap point duplicated, no index wrapping) +**Entity** - Game entities +- NPCs and creatures with display info +- Animation state (idle, combat, walk, run) +- GUID management (player, creature, item, gameobject) +- Targeting and selection -**Expansion Helpers** (`game_utils.hpp`): -- `isActiveExpansion("classic")` / `isActiveExpansion("tbc")` / `isActiveExpansion("wotlk")` -- `isClassicLikeExpansion()` (Classic or Turtle WoW) -- `isPreWotlk()` (Classic, Turtle, or TBC) +**Inventory** - Item management +- Equipment slots (head, shoulders, chest, etc.) +- Backpack storage (16 slots) +- Item metadata (icons, stats, durability) +- Drag-drop system +- Auto-equip and unequip + +**NPC Interactions** - handled through `GameHandler` +- Gossip system +- Quest givers with markers (! and ?) +- Vendors (buy/sell) +- Trainers (placeholder) +- Combat animations + +**ZoneManager** - Zone and area tracking +- Map exploration +- Area discovery +- Zone change detection + +**Opcodes** - Protocol definitions +- 100+ Client→Server opcodes (CMSG_*) +- 100+ Server→Client opcodes (SMSG_*) +- WoW 3.3.5a (build 12340) specific ### 6. Asset Pipeline (`src/pipeline/`) **AssetManager** - Runtime asset access -- Extracted loose-file tree indexed by `Data/manifest.json` +- Loads an extracted loose-file tree indexed by `Data/manifest.json` - Layered resolution via optional overlay manifests (multi-expansion dedup) -- File cache with configurable budget (256 MB min, 12 GB max) -- PNG override support (checks for .png before .blp) +- File cache + path normalization **asset_extract (tool)** - MPQ extraction - Uses StormLib to extract MPQs into `Data/` and generate `manifest.json` -- Driven by `extract_assets.sh` / `extract_assets.ps1` +- Driven by `extract_assets.sh` -**BLPLoader** - Texture decompression -- DXT1/3/5 block compression (RGB565 color endpoints) -- Palette mode with 1/4/8-bit alpha -- Mipmap extraction +**BLPLoader** - Texture parser +- BLP format (Blizzard texture format) +- DXT1/3/5 compression support +- Mipmap extraction and generation +- OpenGL texture object creation -**M2Loader** - Model binary parsing -- Version-aware header (Classic v256 vs WotLK v264) -- Skeletal animation tracks (embedded vs external .anim files, flag 0x20) -- Compressed quaternions (int16 offset mapping) -- Particle emitters, ribbon emitters, attachment points -- Geoset support (group × 100 + variant encoding) +**M2Loader** - Model parser +- Character/creature models with materials +- Skeletal animation data (256 bones max) +- Bone hierarchies and transforms +- Animation sequences (idle, walk, run, attack, etc.) +- Particle emitters (WotLK FBlock format) +- Attachment points (weapons, mounts, etc.) +- Geoset support (hide/show body parts) +- Multiple texture units and render batches -**WMOLoader** - World object parsing -- Multi-group rendering with portal visibility -- Doodad placement (24-bit name index + 8-bit flags packing) -- Liquid data, collision geometry +**WMOLoader** - World object parser +- Buildings and structures +- Multi-material batches +- Portal system (visibility culling) +- Doodad placement (decorations) +- Group-based rendering +- Liquid data (indoor water) -**ADTLoader** - Terrain parsing -- 64×64 tiles per map, 16×16 chunks per tile (MCNK) -- MCVT height grid (145 vertices: 9 outer + 8 inner per row × 9 rows) -- Texture layers (up to 4 with alpha blending, RLE-compressed alpha maps) +**ADTLoader** - Terrain parser +- 64x64 tiles per map (map_XX_YY.adt) +- 16x16 chunks per tile (MCNK) +- Height map data (9x9 outer + 8x8 inner vertices) +- Texture layers (up to 4 per chunk with alpha blending) +- Liquid data (water/lava/slime with height and flags) +- Object placement (M2 and WMO references) +- Terrain holes - Async loading to prevent frame stalls -**DBCLoader** - Database table parsing -- Binary DBC format (fixed 4-byte uint32 fields + string block) -- CSV fallback for pre-extracted data -- Expansion-aware field layout via `dbc_layouts.json` -- 20+ DBC files: Spell, Item, Creature, Faction, Map, AreaTable, etc. +**DBCLoader** - Database parser +- 20+ DBC files loaded (Spell, Item, Creature, SkillLine, Faction, etc.) +- Type-safe record access +- String block parsing +- Memory-efficient caching +- Used for: + - Spell icons and tooltips (Spell.dbc, SpellIcon.dbc) + - Item data (Item.dbc, ItemDisplayInfo.dbc) + - Creature display info (CreatureDisplayInfo.dbc, CreatureModelData.dbc) + - Class and race info (ChrClasses.dbc, ChrRaces.dbc) + - Skill lines (SkillLine.dbc, SkillLineAbility.dbc) + - Faction and reputation (Faction.dbc) + - Map and area names (Map.dbc, AreaTable.dbc) ### 7. UI System (`src/ui/`) **UIManager** - ImGui coordinator -- ImGui initialization with SDL2/Vulkan backend -- Screen state management and transitions +- ImGui initialization with SDL2/OpenGL backend - Event handling and input routing +- Render dispatch with opacity control +- Screen state management -**Screens:** -- `AuthScreen` - Login with username/password, server address, security code -- `RealmScreen` - Realm list with population and type indicators -- `CharacterScreen` - Character selection with 3D animated preview -- `CharacterCreateScreen` - Race/class/gender/appearance customization -- `GameScreen` - Main HUD: chat, action bar, target frame, minimap, nameplates, combat text, tooltips -- `InventoryScreen` - Equipment paper doll, backpack, bag windows, item tooltips with stats -- `SpellbookScreen` - Tabbed spell list with icons, drag-drop to action bar -- `QuestLogScreen` - Quest list with objectives, details, and rewards -- `TalentScreen` - Talent tree UI with point allocation -- `SettingsScreen` - Graphics presets (LOW/MEDIUM/HIGH/ULTRA), audio, keybindings +**AuthScreen** - Login interface +- Username/password input fields +- Server address configuration +- Connection status and error messages -### 8. Audio System (`src/audio/`) +**RealmScreen** - Server selection +- Realm list display with names and types +- Population info (Low/Medium/High/Full) +- Realm type indicators (PvP/PvE/RP/RPPvP) +- Auto-select for single realm -**AudioEngine** - miniaudio-based playback -- WAV decode cache (256 entries, LRU eviction) -- 2D and 3D positional audio -- Sample rate preservation (explicit to avoid miniaudio pitch distortion) +**CharacterScreen** - Character selection +- Character list with 3D animated preview +- Stats panel (level, race, class, location) +- Create/delete character buttons +- Enter world button +- Auto-select for single character -**Sound Managers:** -- `AmbientSoundManager` - Wind, water, fire, birds, crickets, city ambience, bell tolls -- `ActivitySoundManager` - Swimming strokes, jumping, landing -- `MovementSoundManager` - Footsteps (terrain-aware), mount movement -- `MountSoundManager` - Mount-specific movement audio -- `MusicManager` - Zone music with day/night variants +**CharacterCreateScreen** - Character creation +- Race selection (all Alliance and Horde races) +- Class selection (class availability by race) +- Gender selection +- Appearance customization (face, skin, hair, color, features) +- Name input with validation +- 3D character preview -### 9. Warden Anti-Cheat (`src/game/`) +**GameScreen** - In-game HUD +- Chat window with message history and formatting +- Action bar (12 slots with icons, cooldowns, keybindings) +- Target frame (name, level, health, hostile/friendly coloring) +- Player stats (health, mana/rage/energy) +- Minimap with quest markers +- Experience bar -4-layer architecture: -- `WardenHandler` - Packet handling (SMSG/CMSG_WARDEN_DATA) -- `WardenModuleManager` - Module lifecycle and caching -- `WardenModule` - 8-step pipeline: decrypt (RC4), strip RSA-2048 signature, decompress (zlib), parse PE headers, relocate, resolve imports, execute -- `WardenEmulator` - Unicorn Engine x86 CPU emulation with Windows API interception -- `WardenMemory` - PE image loading with bounds-checked reads, runtime global patching +**InventoryScreen** - Inventory management +- Equipment paper doll (23 slots: head, shoulders, chest, etc.) +- Backpack grid (16 slots) +- Item icons with tooltips +- Drag-drop to equip/unequip +- Item stats and durability +- Gold display + +**SpellbookScreen** - Spells and abilities +- Tabbed interface (class specialties + General) +- Spell icons organized by SkillLine +- Spell tooltips (name, rank, cost, cooldown, description) +- Drag-drop to action bar +- Known spell tracking + +**QuestLogScreen** - Quest tracking +- Active quest list +- Quest objectives and progress +- Quest details (description, objectives, rewards) +- Abandon quest button +- Quest level and recommended party size + +**TalentScreen** - Talent trees +- Placeholder for talent system +- Tree visualization (TODO) +- Talent point allocation (TODO) + +**Settings Window** - Configuration +- UI opacity slider +- Graphics options (TODO) +- Audio controls (TODO) +- Keybinding customization (TODO) + +**Loading Screen** - Map loading progress +- Progress bar with percentage +- Background image (map-specific, TODO) +- Loading tips (TODO) +- Shown during world entry and map transitions + +## Data Flow Examples + +### Authentication Flow +``` +User Input (username/password) + ↓ +AuthHandler::authenticate() + ↓ +SRP::calculateVerifier() + ↓ +TCPSocket::send(LOGON_CHALLENGE) + ↓ +Server Response (LOGON_CHALLENGE) + ↓ +AuthHandler receives packet + ↓ +SRP::calculateProof() + ↓ +TCPSocket::send(LOGON_PROOF) + ↓ +Server Response (LOGON_PROOF) → Success + ↓ +Application::setState(REALM_SELECTION) +``` + +### Rendering Flow +``` +Application::render() + ↓ +Renderer::beginFrame() + ├─ glClearColor() - Clear screen + └─ glClear() - Clear buffers + ↓ +Renderer::renderWorld(world) + ├─ Update camera matrices + ├─ Frustum culling + ├─ For each visible chunk: + │ ├─ Bind shader + │ ├─ Set uniforms (matrices, lighting) + │ ├─ Bind textures + │ └─ Mesh::draw() → glDrawElements() + └─ For each entity: + ├─ Calculate bone transforms + └─ Render skinned mesh + ↓ +UIManager::render() + ├─ ImGui::NewFrame() + ├─ Render current UI screen + └─ ImGui::Render() + ↓ +Renderer::endFrame() + ↓ +Window::swapBuffers() +``` + +### Asset Loading Flow +``` +World::loadMap(mapId) + ↓ +AssetManager::readFile("World/Maps/{map}/map.adt") + ↓ +ADTLoader::load(adtData) + ├─ Parse MCNK chunks (terrain) + ├─ Parse MCLY chunks (textures) + ├─ Parse MCVT chunks (vertices) + └─ Parse MCNR chunks (normals) + ↓ +For each texture reference: + AssetManager::readFile(texturePath) + ↓ + BLPLoader::load(blpData) + ↓ + Texture::loadFromMemory(imageData) + ↓ +Create Mesh from vertices/normals/texcoords + ↓ +Add to Scene + ↓ +Renderer draws in next frame +``` ## Threading Model -- **Main thread**: Window events, game logic update, rendering -- **Async terrain**: Non-blocking chunk loading (std::async) -- **Network I/O**: Non-blocking recv in main thread with per-frame budgets -- **Normal maps**: Background CPU generation with mutex-protected result queue -- **GPU uploads**: Second Vulkan queue for parallel texture/buffer transfers +Currently **single-threaded** with async operations: +- Main thread: Window events, update, render +- Network I/O: Non-blocking in main thread (event-driven) +- Asset loading: Async terrain streaming (non-blocking chunk loads) + +**Async Systems Implemented:** +- Terrain streaming loads ADT chunks asynchronously to prevent frame stalls +- Network packets processed in batches per frame +- UI rendering deferred until after world rendering + +**Future multi-threading opportunities:** +- Asset loading thread pool (background texture/model decompression) +- Network thread (dedicated for socket I/O) +- Physics thread (if collision detection is added) +- Audio streaming thread ## Memory Management -- **Smart pointers**: `std::unique_ptr` / `std::shared_ptr` throughout -- **RAII**: All Vulkan resources wrapped with proper destructors -- **VMA**: Vulkan Memory Allocator for GPU memory -- **Object pooling**: Weather particles, combat text entries -- **DBC caching**: Lazy-loaded mutable caches in const getters +- **Smart pointers:** Used throughout (std::unique_ptr, std::shared_ptr) +- **RAII:** All resources (OpenGL, SDL) cleaned up automatically +- **No manual memory management:** No raw new/delete +- **OpenGL resources:** Wrapped in classes with proper destructors + +## Performance Considerations + +### Rendering +- **Frustum culling:** Only render visible chunks (terrain and WMO groups) +- **Distance culling:** WMO groups culled beyond 160 units +- **Batching:** Group draw calls by material and shader +- **LOD:** Distance-based level of detail (TODO) +- **Occlusion:** Portal-based visibility (WMO system) +- **GPU skinning:** Character animation computed on GPU (256 bones) +- **Instancing:** Future optimization for repeated models + +### Asset Streaming +- **Async loading:** Terrain chunks load asynchronously (prevents frame stalls) +- **Lazy loading:** Load chunks as player moves within streaming radius +- **Unloading:** Free distant chunks automatically +- **Caching:** Keep frequently used assets in memory (textures, models) +- **Priority queue:** Load visible chunks first + +### Network +- **Non-blocking I/O:** Never stall main thread +- **Packet buffering:** Handle multiple packets per frame +- **Batch processing:** Process received packets in batches +- **RC4 encryption:** Efficient header encryption (minimal overhead) +- **Compression:** Some packets are compressed (TODO) + +### Memory Management +- **Smart pointers:** Automatic cleanup, no memory leaks +- **Object pooling:** Reuse particle objects (weather system) +- **DBC caching:** Load once, access fast +- **Texture sharing:** Same texture used by multiple models + +## Error Handling + +- **Logging:** All errors logged with context +- **Graceful degradation:** Missing assets show placeholder +- **State recovery:** Network disconnect → back to auth screen +- **No crashes:** Exceptions caught at application level + +## Configuration + +Currently hardcoded, future config system: +- Window size and fullscreen +- Graphics quality settings +- Server addresses +- Keybindings +- Audio volume + +## Testing Strategy + +**Unit Testing** (TODO): +- Packet serialization/deserialization +- SRP math functions +- Asset parsers with sample files +- DBC record parsing +- Inventory slot calculations + +**Integration Testing** (TODO): +- Full auth flow against test server +- Realm list retrieval +- Character creation and selection +- Quest turn-in flow +- Vendor transactions + +**Manual Testing:** +- Visual verification of rendering (terrain, water, models, particles) +- Performance profiling (F1 performance HUD) +- Memory leak checking (valgrind) +- Online gameplay against AzerothCore/TrinityCore/MaNGOS servers +- UI interactions (drag-drop, click events) + +**Current Test Coverage:** +- Full authentication flow tested against live servers +- Character creation and selection verified +- Quest system tested (accept, track, turn-in) +- Vendor system tested (buy, sell) +- Combat system tested (targeting, auto-attack, spells) +- Inventory system tested (equip, unequip, drag-drop) ## Build System -**CMake** with modular targets: -- `wowee` - Main executable -- `asset_extract` - MPQ extraction tool (requires StormLib) -- `dbc_to_csv` / `auth_probe` / `blp_convert` - Utility tools +**CMake:** +- Modular target structure +- Automatic dependency discovery +- Cross-platform (Linux focus, but portable) +- Out-of-source builds **Dependencies:** -- SDL2, Vulkan SDK, OpenSSL, GLM, zlib (system) +- SDL2 (system) +- OpenGL/GLEW (system) +- OpenSSL (system) +- GLM (system or header-only) - ImGui (submodule in extern/) -- VMA, vk-bootstrap, stb_image (vendored in extern/) -- StormLib (system, optional — only for asset_extract) -- Unicorn Engine (system, optional — only for Warden emulation) -- FFmpeg (system, optional — for video playback) - -**CI**: GitHub Actions for Linux (x86-64, ARM64), Windows (MSYS2), macOS (ARM64) -**Container builds**: Docker cross-compilation for Linux, macOS (osxcross), Windows (LLVM-MinGW) +- StormLib (system, optional) ## Code Style - **C++20 standard** -- **Namespaces**: `wowee::core`, `wowee::rendering`, `wowee::game`, `wowee::ui`, `wowee::network`, `wowee::auth`, `wowee::audio`, `wowee::pipeline` -- **Naming**: PascalCase for classes, camelCase for functions/variables, kPascalCase for constants -- **Headers**: `.hpp` extension, `#pragma once` -- **Commits**: Conventional style (`feat:`, `fix:`, `refactor:`, `docs:`, `perf:`) +- **Namespaces:** wowee::core, wowee::rendering, etc. +- **Naming:** PascalCase for classes, camelCase for functions/variables +- **Headers:** .hpp extension +- **Includes:** Relative to project root + +--- + +This architecture provides a solid foundation for a full-featured native WoW client! diff --git a/docs/authentication.md b/docs/authentication.md index 19a6f9fe..ff514399 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -563,4 +563,5 @@ The client is now ready for character operations and world entry! 🎮 --- -**Implementation Status:** Complete — authentication, character enumeration, and world entry all working. +**Implementation Status:** 100% Complete for authentication +**Next Milestone:** Character enumeration and world entry diff --git a/docs/packet-framing.md b/docs/packet-framing.md index 157645fd..be7ee3cb 100644 --- a/docs/packet-framing.md +++ b/docs/packet-framing.md @@ -397,4 +397,6 @@ The authentication system can now reliably communicate with WoW 3.3.5a servers! --- -**Status:** ✅ Complete and tested against AzerothCore, TrinityCore, Mangos, and Turtle WoW. +**Status:** ✅ Complete and tested + +**Next Steps:** Test with live server and implement realm list protocol. diff --git a/docs/perf_baseline.md b/docs/perf_baseline.md deleted file mode 100644 index 88bdc743..00000000 --- a/docs/perf_baseline.md +++ /dev/null @@ -1,79 +0,0 @@ -# Performance Baseline — WoWee - -> Phase 0.3 deliverable. Measurements taken before any optimization work. -> Re-run after each phase to quantify improvement. - -## Tracy Profiler Integration - -Tracy v0.11.1 integrated under `WOWEE_ENABLE_TRACY` CMake option (default: OFF). -When enabled, zero-cost zone markers instrument the following critical paths: - -### Instrumented Zones - -| Zone Name | File | Purpose | -|-----------|------|---------| -| `Application::run` | src/core/application.cpp | Main loop entry | -| `Application::update` | src/core/application.cpp | Per-frame game logic | -| `Renderer::beginFrame` | src/rendering/renderer.cpp | Vulkan frame begin | -| `Renderer::endFrame` | src/rendering/renderer.cpp | Post-process + present | -| `Renderer::update` | src/rendering/renderer.cpp | Renderer per-frame update | -| `Renderer::renderWorld` | src/rendering/renderer.cpp | Main world draw call | -| `Renderer::renderShadowPass` | src/rendering/renderer.cpp | Shadow depth pass | -| `PostProcess::execute` | src/rendering/post_process_pipeline.cpp | FSR/FXAA post-process | -| `M2::computeBoneMatrices` | src/rendering/m2_renderer.cpp | CPU skeletal animation | -| `M2Renderer::update` | src/rendering/m2_renderer.cpp | M2 instance update + culling | -| `TerrainManager::update` | src/rendering/terrain_manager.cpp | Terrain streaming logic | -| `TerrainManager::processReadyTiles` | src/rendering/terrain_manager.cpp | GPU tile uploads | -| `ADTLoader::load` | src/pipeline/adt_loader.cpp | ADT binary parsing | -| `AssetManager::loadTexture` | src/pipeline/asset_manager.cpp | BLP texture loading | -| `AssetManager::loadDBC` | src/pipeline/asset_manager.cpp | DBC data file loading | -| `WorldSocket::update` | src/network/world_socket.cpp | Network packet dispatch | - -`FrameMark` placed at frame boundary in Application::update to track FPS. - -### How to Profile - -```bash -# Build with Tracy enabled -mkdir -p build_tracy && cd build_tracy -cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWOWEE_ENABLE_TRACY=ON -cmake --build . --parallel $(nproc) - -# Run the client — Tracy will broadcast on default port (8086) -cd bin && ./wowee - -# Connect with Tracy profiler GUI (separate download from https://github.com/wolfpld/tracy/releases) -# Or capture from CLI: tracy-capture -o trace.tracy -``` - -## Baseline Scenarios - -> **TODO:** Record measurements once profiler is connected to a running instance. -> Each scenario should record: avg FPS, frame time (p50/p95/p99), and per-zone timings. - -### Scenario 1: Stormwind (Heavy M2/WMO) -- **Location:** Stormwind City center -- **Load:** Dense M2 models (NPCs, doodads), multiple WMO interiors -- **Avg FPS:** _pending_ -- **Frame time (p50/p95/p99):** _pending_ -- **Top zones:** _pending_ - -### Scenario 2: The Barrens (Heavy Terrain) -- **Location:** Central Barrens -- **Load:** Many terrain tiles loaded, sparse M2, large draw distance -- **Avg FPS:** _pending_ -- **Frame time (p50/p95/p99):** _pending_ -- **Top zones:** _pending_ - -### Scenario 3: Dungeon Instance (WMO-only) -- **Location:** Any dungeon instance (e.g., Deadmines entrance) -- **Load:** WMO interior rendering, no terrain -- **Avg FPS:** _pending_ -- **Frame time (p50/p95/p99):** _pending_ -- **Top zones:** _pending_ - -## Notes - -- When `WOWEE_ENABLE_TRACY` is OFF (default), all `ZoneScopedN` / `FrameMark` macros expand to nothing — zero runtime overhead. -- Tracy requires a network connection to capture traces. Run the Tracy profiler GUI or `tracy-capture` CLI alongside the client. -- Debug builds are significantly slower due to -Og and no LTO; use RelWithDebInfo for representative measurements. diff --git a/docs/quickstart.md b/docs/quickstart.md index e5fc0b9a..47bef9d2 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -19,11 +19,17 @@ For a more honest snapshot of gaps and current direction, see `docs/status.md`. ### 1. Clone ```bash -git clone --recurse-submodules https://github.com/Kelsidavis/WoWee.git -cd WoWee +git clone https://github.com/Kelsidavis/WoWee.git +cd wowee ``` -### 2. Build +### 2. Install ImGui + +```bash +git clone https://github.com/ocornut/imgui.git extern/imgui +``` + +### 3. Build ```bash cmake -S . -B build -DCMAKE_BUILD_TYPE=Release @@ -90,7 +96,7 @@ Use `BUILD_INSTRUCTIONS.md` for distro-specific package lists. - Verify auth/world server is running - Check host/port settings -- Check server logs and client logs in `logs/wowee.log` +- Check server logs and client logs in `build/bin/logs/` ### Missing assets (models/textures/terrain) diff --git a/docs/server-setup.md b/docs/server-setup.md index ba59fd56..c185b943 100644 --- a/docs/server-setup.md +++ b/docs/server-setup.md @@ -609,6 +609,6 @@ Once you have a working local server connection: --- **Status**: Ready for local server testing -**Last Updated**: 2026-03-30 -**Client Version**: v1.8.9-preview -**Server Compatibility**: Vanilla 1.12, TBC 2.4.3, WotLK 3.3.5a (12340), Turtle WoW 1.17 +**Last Updated**: 2026-01-27 +**Client Version**: 1.0.3 +**Server Compatibility**: WoW 3.3.5a (12340) diff --git a/docs/srp-implementation.md b/docs/srp-implementation.md index e83505ce..084881ed 100644 --- a/docs/srp-implementation.md +++ b/docs/srp-implementation.md @@ -351,13 +351,13 @@ The expensive operation (session key computation) only happens once per login. 2. **No Plaintext Storage:** Password is immediately hashed, never stored 3. **Forward Secrecy:** Ephemeral keys (a, A) are generated per session 4. **Mutual Authentication:** Both client and server prove knowledge of password -5. **Secure Channel:** Session key K is used for RC4 header encryption after auth completes +5. **Secure Channel:** Session key K can be used for encryption (not implemented yet) ## References - [SRP Protocol](http://srp.stanford.edu/) - [WoWDev Wiki - SRP](https://wowdev.wiki/SRP) -- Implementation: `src/auth/srp.cpp`, `include/auth/srp.hpp` +- Original wowee: `/wowee/src/lib/crypto/srp.js` - OpenSSL BIGNUM: https://www.openssl.org/docs/man1.1.1/man3/BN_new.html --- diff --git a/docs/status.md b/docs/status.md index c337ca2f..8244b425 100644 --- a/docs/status.md +++ b/docs/status.md @@ -1,6 +1,6 @@ # Project Status -**Last updated**: 2026-03-30 +**Last updated**: 2026-03-07 ## What This Repo Is @@ -25,23 +25,20 @@ Implemented (working in normal use): - Talent tree UI with proper visuals and functionality - Pet tracking (SMSG_PET_SPELLS), dismiss pet button - Party: group invites, party list, out-of-range member health (SMSG_PARTY_MEMBER_STATS) -- Nameplates: NPC subtitles, guild names, elite/boss/rare borders, quest/raid indicators, cast bars, debuff dots -- Floating combat text: world-space damage/heal numbers above entities with 3D projection -- Target/focus frames: guild name, creature type, rank badges, combo points, cast bars - Map exploration: subzone-level fog-of-war reveal - Warden anti-cheat: full module execution via Unicorn Engine x86 emulation; module caching -- Audio: ambient, movement, combat, spell, and UI sound systems; NPC voice lines for all playable races (greeting/farewell/vendor/pissed/aggro/flee) -- Bag UI: independent bag windows (any bag closable independently), open-bag indicator on bag bar, server-synced bag sort, off-screen position reset, optional collapse-empty mode in aggregate view -- DBC auto-detection: CharSections.dbc field layout auto-detected at runtime (handles stock WotLK vs HD-textured clients) +- Audio: ambient, movement, combat, spell, and UI sound systems +- Bag UI: separate bag windows, open-bag indicator on bag bar, optional collapse-empty mode in aggregate bag view - Multi-expansion: Classic/Vanilla, TBC, WotLK, and Turtle WoW (1.17) protocol and asset variants -- CI: GitHub Actions for Linux (x86-64, ARM64), Windows (MSYS2 x86-64 + ARM64), macOS (ARM64); container builds via Podman +- CI: GitHub Actions for Linux (x86-64, ARM64), Windows (MSYS2), macOS (ARM64); container builds via Podman In progress / known gaps: - Transports: M2 transports (trams) working with position-delta riding; WMO transports (ships, zeppelins) working with path following; some edge cases remain -- Quest GO interaction: CMSG_GAMEOBJ_USE + CMSG_LOOT sent correctly, but some AzerothCore/ChromieCraft servers don't grant quest credit for chest-type GOs (server-side limitation) -- Visual edge cases: some M2/WMO rendering gaps (some particle effects) -- Water refraction: enabled by default; srcAccessMask barrier fix (2026-03-18) resolved prior VK_ERROR_DEVICE_LOST on AMD/Mali GPUs +- 3D positional audio: not implemented (mono/stereo only) +- Visual edge cases: some M2/WMO rendering gaps (character shin mesh, some particle effects) +- Interior rendering: WMO interior shadows disabled (too dark); lava steam particles sparse +- Water refraction: implemented but disabled by default (can cause VK_ERROR_DEVICE_LOST on some GPUs) ## Where To Look diff --git a/extern/FidelityFX-FSR2 b/extern/FidelityFX-FSR2 deleted file mode 160000 index 3d22aefd..00000000 --- a/extern/FidelityFX-FSR2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3d22aefd90fd861e5cee1c3cde18ff185e221f2d diff --git a/extern/FidelityFX-SDK b/extern/FidelityFX-SDK deleted file mode 160000 index ce81c674..00000000 --- a/extern/FidelityFX-SDK +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ce81c674d92d81ad1253841f39a359811dd738cf diff --git a/extern/VERSIONS.md b/extern/VERSIONS.md deleted file mode 100644 index 1d7d2d4b..00000000 --- a/extern/VERSIONS.md +++ /dev/null @@ -1,14 +0,0 @@ -# Vendored Library Versions - -Versions of third-party libraries vendored in `extern/`. Update this file -when upgrading any dependency so maintainers can track drift. - -| Library | Version | Source | Notes | -|---------|---------|--------|-------| -| Dear ImGui | 1.92.6 WIP | https://github.com/ocornut/imgui | Git submodule | -| vk-bootstrap | latest | https://github.com/charles-lunarg/vk-bootstrap | Git submodule | -| Vulkan Memory Allocator | 3.4.0 | https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator | Single header | -| miniaudio | 0.11.24 | https://miniaud.io/ | Single header | -| stb_image | 2.30 | https://github.com/nothings/stb | Single header | -| stb_image_write | 1.16 | https://github.com/nothings/stb | Single header | -| Lua | 5.1.5 | https://www.lua.org/ | Intentionally 5.1 for WoW addon API compatibility | diff --git a/extern/catch2/catch_amalgamated.cpp b/extern/catch2/catch_amalgamated.cpp deleted file mode 100644 index f45c18a0..00000000 --- a/extern/catch2/catch_amalgamated.cpp +++ /dev/null @@ -1,11811 +0,0 @@ - -// Copyright Catch2 Authors -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE.txt or copy at -// https://www.boost.org/LICENSE_1_0.txt) - -// SPDX-License-Identifier: BSL-1.0 - -// Catch v3.7.1 -// Generated: 2024-09-17 10:36:45.608896 -// ---------------------------------------------------------- -// This file is an amalgamation of multiple different files. -// You probably shouldn't edit it directly. -// ---------------------------------------------------------- - -#include "catch_amalgamated.hpp" - - -#ifndef CATCH_WINDOWS_H_PROXY_HPP_INCLUDED -#define CATCH_WINDOWS_H_PROXY_HPP_INCLUDED - - -#if defined(CATCH_PLATFORM_WINDOWS) - -// We might end up with the define made globally through the compiler, -// and we don't want to trigger warnings for this -#if !defined(NOMINMAX) -# define NOMINMAX -#endif -#if !defined(WIN32_LEAN_AND_MEAN) -# define WIN32_LEAN_AND_MEAN -#endif - -#include - -#endif // defined(CATCH_PLATFORM_WINDOWS) - -#endif // CATCH_WINDOWS_H_PROXY_HPP_INCLUDED - - - - -namespace Catch { - namespace Benchmark { - namespace Detail { - ChronometerConcept::~ChronometerConcept() = default; - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - - -// Adapted from donated nonius code. - - -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last) { - if (!cfg.benchmarkNoAnalysis()) { - std::vector samples; - samples.reserve(static_cast(last - first)); - for (auto current = first; current != last; ++current) { - samples.push_back( current->count() ); - } - - auto analysis = Catch::Benchmark::Detail::analyse_samples( - cfg.benchmarkConfidenceInterval(), - cfg.benchmarkResamples(), - samples.data(), - samples.data() + samples.size() ); - auto outliers = Catch::Benchmark::Detail::classify_outliers( - samples.data(), samples.data() + samples.size() ); - - auto wrap_estimate = [](Estimate e) { - return Estimate { - FDuration(e.point), - FDuration(e.lower_bound), - FDuration(e.upper_bound), - e.confidence_interval, - }; - }; - std::vector samples2; - samples2.reserve(samples.size()); - for (auto s : samples) { - samples2.push_back( FDuration( s ) ); - } - - return { - CATCH_MOVE(samples2), - wrap_estimate(analysis.mean), - wrap_estimate(analysis.standard_deviation), - outliers, - analysis.outlier_variance, - }; - } else { - std::vector samples; - samples.reserve(static_cast(last - first)); - - FDuration mean = FDuration(0); - int i = 0; - for (auto it = first; it < last; ++it, ++i) { - samples.push_back(*it); - mean += *it; - } - mean /= i; - - return SampleAnalysis{ - CATCH_MOVE(samples), - Estimate{ mean, mean, mean, 0.0 }, - Estimate{ FDuration( 0 ), - FDuration( 0 ), - FDuration( 0 ), - 0.0 }, - OutlierClassification{}, - 0.0 - }; - } - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - - - - -namespace Catch { - namespace Benchmark { - namespace Detail { - struct do_nothing { - void operator()() const {} - }; - - BenchmarkFunction::callable::~callable() = default; - BenchmarkFunction::BenchmarkFunction(): - f( new model{ {} } ){} - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - - - - -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - struct optimized_away_error : std::exception { - const char* what() const noexcept override; - }; - - const char* optimized_away_error::what() const noexcept { - return "could not measure benchmark, maybe it was optimized away"; - } - - void throw_optimized_away_error() { - Catch::throw_exception(optimized_away_error{}); - } - - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - - -// Adapted from donated nonius code. - - - -#include -#include -#include -#include -#include -#include - - -#if defined(CATCH_CONFIG_USE_ASYNC) -#include -#endif - -namespace Catch { - namespace Benchmark { - namespace Detail { - namespace { - - template - static sample - resample( URng& rng, - unsigned int resamples, - double const* first, - double const* last, - Estimator& estimator ) { - auto n = static_cast( last - first ); - Catch::uniform_integer_distribution dist( 0, n - 1 ); - - sample out; - out.reserve( resamples ); - std::vector resampled; - resampled.reserve( n ); - for ( size_t i = 0; i < resamples; ++i ) { - resampled.clear(); - for ( size_t s = 0; s < n; ++s ) { - resampled.push_back( first[dist( rng )] ); - } - const auto estimate = - estimator( resampled.data(), resampled.data() + resampled.size() ); - out.push_back( estimate ); - } - std::sort( out.begin(), out.end() ); - return out; - } - - static double outlier_variance( Estimate mean, - Estimate stddev, - int n ) { - double sb = stddev.point; - double mn = mean.point / n; - double mg_min = mn / 2.; - double sg = (std::min)( mg_min / 4., sb / std::sqrt( n ) ); - double sg2 = sg * sg; - double sb2 = sb * sb; - - auto c_max = [n, mn, sb2, sg2]( double x ) -> double { - double k = mn - x; - double d = k * k; - double nd = n * d; - double k0 = -n * nd; - double k1 = sb2 - n * sg2 + nd; - double det = k1 * k1 - 4 * sg2 * k0; - return static_cast( -2. * k0 / - ( k1 + std::sqrt( det ) ) ); - }; - - auto var_out = [n, sb2, sg2]( double c ) { - double nc = n - c; - return ( nc / n ) * ( sb2 - nc * sg2 ); - }; - - return (std::min)( var_out( 1 ), - var_out( - (std::min)( c_max( 0. ), - c_max( mg_min ) ) ) ) / - sb2; - } - - static double erf_inv( double x ) { - // Code accompanying the article "Approximating the erfinv - // function" in GPU Computing Gems, Volume 2 - double w, p; - - w = -log( ( 1.0 - x ) * ( 1.0 + x ) ); - - if ( w < 6.250000 ) { - w = w - 3.125000; - p = -3.6444120640178196996e-21; - p = -1.685059138182016589e-19 + p * w; - p = 1.2858480715256400167e-18 + p * w; - p = 1.115787767802518096e-17 + p * w; - p = -1.333171662854620906e-16 + p * w; - p = 2.0972767875968561637e-17 + p * w; - p = 6.6376381343583238325e-15 + p * w; - p = -4.0545662729752068639e-14 + p * w; - p = -8.1519341976054721522e-14 + p * w; - p = 2.6335093153082322977e-12 + p * w; - p = -1.2975133253453532498e-11 + p * w; - p = -5.4154120542946279317e-11 + p * w; - p = 1.051212273321532285e-09 + p * w; - p = -4.1126339803469836976e-09 + p * w; - p = -2.9070369957882005086e-08 + p * w; - p = 4.2347877827932403518e-07 + p * w; - p = -1.3654692000834678645e-06 + p * w; - p = -1.3882523362786468719e-05 + p * w; - p = 0.0001867342080340571352 + p * w; - p = -0.00074070253416626697512 + p * w; - p = -0.0060336708714301490533 + p * w; - p = 0.24015818242558961693 + p * w; - p = 1.6536545626831027356 + p * w; - } else if ( w < 16.000000 ) { - w = sqrt( w ) - 3.250000; - p = 2.2137376921775787049e-09; - p = 9.0756561938885390979e-08 + p * w; - p = -2.7517406297064545428e-07 + p * w; - p = 1.8239629214389227755e-08 + p * w; - p = 1.5027403968909827627e-06 + p * w; - p = -4.013867526981545969e-06 + p * w; - p = 2.9234449089955446044e-06 + p * w; - p = 1.2475304481671778723e-05 + p * w; - p = -4.7318229009055733981e-05 + p * w; - p = 6.8284851459573175448e-05 + p * w; - p = 2.4031110387097893999e-05 + p * w; - p = -0.0003550375203628474796 + p * w; - p = 0.00095328937973738049703 + p * w; - p = -0.0016882755560235047313 + p * w; - p = 0.0024914420961078508066 + p * w; - p = -0.0037512085075692412107 + p * w; - p = 0.005370914553590063617 + p * w; - p = 1.0052589676941592334 + p * w; - p = 3.0838856104922207635 + p * w; - } else { - w = sqrt( w ) - 5.000000; - p = -2.7109920616438573243e-11; - p = -2.5556418169965252055e-10 + p * w; - p = 1.5076572693500548083e-09 + p * w; - p = -3.7894654401267369937e-09 + p * w; - p = 7.6157012080783393804e-09 + p * w; - p = -1.4960026627149240478e-08 + p * w; - p = 2.9147953450901080826e-08 + p * w; - p = -6.7711997758452339498e-08 + p * w; - p = 2.2900482228026654717e-07 + p * w; - p = -9.9298272942317002539e-07 + p * w; - p = 4.5260625972231537039e-06 + p * w; - p = -1.9681778105531670567e-05 + p * w; - p = 7.5995277030017761139e-05 + p * w; - p = -0.00021503011930044477347 + p * w; - p = -0.00013871931833623122026 + p * w; - p = 1.0103004648645343977 + p * w; - p = 4.8499064014085844221 + p * w; - } - return p * x; - } - - static double - standard_deviation( double const* first, double const* last ) { - auto m = Catch::Benchmark::Detail::mean( first, last ); - double variance = - std::accumulate( first, - last, - 0., - [m]( double a, double b ) { - double diff = b - m; - return a + diff * diff; - } ) / - ( last - first ); - return std::sqrt( variance ); - } - - static sample jackknife( double ( *estimator )( double const*, - double const* ), - double* first, - double* last ) { - const auto second = first + 1; - sample results; - results.reserve( static_cast( last - first ) ); - - for ( auto it = first; it != last; ++it ) { - std::iter_swap( it, first ); - results.push_back( estimator( second, last ) ); - } - - return results; - } - - - } // namespace - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -namespace Catch { - namespace Benchmark { - namespace Detail { - - double weighted_average_quantile( int k, - int q, - double* first, - double* last ) { - auto count = last - first; - double idx = (count - 1) * k / static_cast(q); - int j = static_cast(idx); - double g = idx - j; - std::nth_element(first, first + j, last); - auto xj = first[j]; - if ( Catch::Detail::directCompare( g, 0 ) ) { - return xj; - } - - auto xj1 = *std::min_element(first + (j + 1), last); - return xj + g * (xj1 - xj); - } - - OutlierClassification - classify_outliers( double const* first, double const* last ) { - std::vector copy( first, last ); - - auto q1 = weighted_average_quantile( 1, 4, copy.data(), copy.data() + copy.size() ); - auto q3 = weighted_average_quantile( 3, 4, copy.data(), copy.data() + copy.size() ); - auto iqr = q3 - q1; - auto los = q1 - ( iqr * 3. ); - auto lom = q1 - ( iqr * 1.5 ); - auto him = q3 + ( iqr * 1.5 ); - auto his = q3 + ( iqr * 3. ); - - OutlierClassification o; - for ( ; first != last; ++first ) { - const double t = *first; - if ( t < los ) { - ++o.low_severe; - } else if ( t < lom ) { - ++o.low_mild; - } else if ( t > his ) { - ++o.high_severe; - } else if ( t > him ) { - ++o.high_mild; - } - ++o.samples_seen; - } - return o; - } - - double mean( double const* first, double const* last ) { - auto count = last - first; - double sum = 0.; - while (first != last) { - sum += *first; - ++first; - } - return sum / static_cast(count); - } - - double normal_cdf( double x ) { - return std::erfc( -x / std::sqrt( 2.0 ) ) / 2.0; - } - - double erfc_inv(double x) { - return erf_inv(1.0 - x); - } - - double normal_quantile(double p) { - static const double ROOT_TWO = std::sqrt(2.0); - - double result = 0.0; - assert(p >= 0 && p <= 1); - if (p < 0 || p > 1) { - return result; - } - - result = -erfc_inv(2.0 * p); - // result *= normal distribution standard deviation (1.0) * sqrt(2) - result *= /*sd * */ ROOT_TWO; - // result += normal disttribution mean (0) - return result; - } - - Estimate - bootstrap( double confidence_level, - double* first, - double* last, - sample const& resample, - double ( *estimator )( double const*, double const* ) ) { - auto n_samples = last - first; - - double point = estimator( first, last ); - // Degenerate case with a single sample - if ( n_samples == 1 ) - return { point, point, point, confidence_level }; - - sample jack = jackknife( estimator, first, last ); - double jack_mean = - mean( jack.data(), jack.data() + jack.size() ); - double sum_squares = 0, sum_cubes = 0; - for ( double x : jack ) { - auto difference = jack_mean - x; - auto square = difference * difference; - auto cube = square * difference; - sum_squares += square; - sum_cubes += cube; - } - - double accel = sum_cubes / ( 6 * std::pow( sum_squares, 1.5 ) ); - long n = static_cast( resample.size() ); - double prob_n = - std::count_if( resample.begin(), - resample.end(), - [point]( double x ) { return x < point; } ) / - static_cast( n ); - // degenerate case with uniform samples - if ( Catch::Detail::directCompare( prob_n, 0. ) ) { - return { point, point, point, confidence_level }; - } - - double bias = normal_quantile( prob_n ); - double z1 = normal_quantile( ( 1. - confidence_level ) / 2. ); - - auto cumn = [n]( double x ) -> long { - return std::lround( normal_cdf( x ) * - static_cast( n ) ); - }; - auto a = [bias, accel]( double b ) { - return bias + b / ( 1. - accel * b ); - }; - double b1 = bias + z1; - double b2 = bias - z1; - double a1 = a( b1 ); - double a2 = a( b2 ); - auto lo = static_cast( (std::max)( cumn( a1 ), 0l ) ); - auto hi = - static_cast( (std::min)( cumn( a2 ), n - 1 ) ); - - return { point, resample[lo], resample[hi], confidence_level }; - } - - bootstrap_analysis analyse_samples(double confidence_level, - unsigned int n_resamples, - double* first, - double* last) { - auto mean = &Detail::mean; - auto stddev = &standard_deviation; - -#if defined(CATCH_CONFIG_USE_ASYNC) - auto Estimate = [=](double(*f)(double const*, double const*)) { - std::random_device rd; - auto seed = rd(); - return std::async(std::launch::async, [=] { - SimplePcg32 rng( seed ); - auto resampled = resample(rng, n_resamples, first, last, f); - return bootstrap(confidence_level, first, last, resampled, f); - }); - }; - - auto mean_future = Estimate(mean); - auto stddev_future = Estimate(stddev); - - auto mean_estimate = mean_future.get(); - auto stddev_estimate = stddev_future.get(); -#else - auto Estimate = [=](double(*f)(double const* , double const*)) { - std::random_device rd; - auto seed = rd(); - SimplePcg32 rng( seed ); - auto resampled = resample(rng, n_resamples, first, last, f); - return bootstrap(confidence_level, first, last, resampled, f); - }; - - auto mean_estimate = Estimate(mean); - auto stddev_estimate = Estimate(stddev); -#endif // CATCH_USE_ASYNC - - auto n = static_cast(last - first); // seriously, one can't use integral types without hell in C++ - double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n); - - return { mean_estimate, stddev_estimate, outlier_variance }; - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - - - -#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 { - - Approx::Approx ( double value ) - : m_epsilon( static_cast(std::numeric_limits::epsilon())*100. ), - m_margin( 0.0 ), - m_scale( 0.0 ), - m_value( value ) - {} - - Approx Approx::custom() { - return Approx( 0 ); - } - - Approx Approx::operator-() const { - auto temp(*this); - temp.m_value = -temp.m_value; - return temp; - } - - - std::string Approx::toString() const { - ReusableStringStream rss; - rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; - return rss.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(std::isinf(m_value)? 0 : m_value))); - } - - void Approx::setMargin(double newMargin) { - CATCH_ENFORCE(newMargin >= 0, - "Invalid Approx::margin: " << newMargin << '.' - << " Approx::Margin has to be non-negative."); - m_margin = newMargin; - } - - void Approx::setEpsilon(double newEpsilon) { - CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0, - "Invalid Approx::epsilon: " << newEpsilon << '.' - << " Approx::epsilon has to be in [0, 1]"); - m_epsilon = newEpsilon; - } - -namespace literals { - Approx operator ""_a(long double val) { - return Approx(val); - } - Approx operator ""_a(unsigned long long val) { - return Approx(val); - } -} // end namespace literals - -std::string StringMaker::convert(Catch::Approx const& value) { - return value.toString(); -} - -} // end namespace Catch - - - -namespace Catch { - - AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const& _lazyExpression): - lazyExpression(_lazyExpression), - resultType(_resultType) {} - - std::string AssertionResultData::reconstructExpression() const { - - if( reconstructedExpression.empty() ) { - if( lazyExpression ) { - ReusableStringStream rss; - rss << lazyExpression; - reconstructedExpression = rss.str(); - } - } - return reconstructedExpression; - } - - AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData&& data ) - : m_info( info ), - m_resultData( CATCH_MOVE(data) ) - {} - - // Result was a success - bool AssertionResult::succeeded() const { - return Catch::isOk( m_resultData.resultType ); - } - - // Result was a success, or failure is suppressed - bool AssertionResult::isOk() const { - return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); - } - - ResultWas::OfType AssertionResult::getResultType() const { - return m_resultData.resultType; - } - - bool AssertionResult::hasExpression() const { - return !m_info.capturedExpression.empty(); - } - - bool AssertionResult::hasMessage() const { - return !m_resultData.message.empty(); - } - - std::string AssertionResult::getExpression() const { - // Possibly overallocating by 3 characters should be basically free - std::string expr; expr.reserve(m_info.capturedExpression.size() + 3); - if (isFalseTest(m_info.resultDisposition)) { - expr += "!("; - } - expr += m_info.capturedExpression; - if (isFalseTest(m_info.resultDisposition)) { - expr += ')'; - } - return expr; - } - - std::string AssertionResult::getExpressionInMacro() const { - if ( m_info.macroName.empty() ) { - return static_cast( m_info.capturedExpression ); - } - std::string expr; - 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; - } - - StringRef AssertionResult::getMessage() const { - return m_resultData.message; - } - SourceLineInfo AssertionResult::getSourceInfo() const { - return m_info.lineInfo; - } - - StringRef AssertionResult::getTestMacroName() const { - return m_info.macroName; - } - -} // end namespace Catch - - - -#include - -namespace Catch { - - namespace { - static bool enableBazelEnvSupport() { -#if defined( CATCH_CONFIG_BAZEL_SUPPORT ) - return true; -#else - return Detail::getEnv( "BAZEL_TEST" ) != nullptr; -#endif - } - - struct bazelShardingOptions { - unsigned int shardIndex, shardCount; - std::string shardFilePath; - }; - - static Optional readBazelShardingOptions() { - const auto bazelShardIndex = Detail::getEnv( "TEST_SHARD_INDEX" ); - const auto bazelShardTotal = Detail::getEnv( "TEST_TOTAL_SHARDS" ); - const auto bazelShardInfoFile = Detail::getEnv( "TEST_SHARD_STATUS_FILE" ); - - - const bool has_all = - bazelShardIndex && bazelShardTotal && bazelShardInfoFile; - if ( !has_all ) { - // We provide nice warning message if the input is - // misconfigured. - auto warn = []( const char* env_var ) { - Catch::cerr() - << "Warning: Bazel shard configuration is missing '" - << env_var << "'. Shard configuration is skipped.\n"; - }; - if ( !bazelShardIndex ) { - warn( "TEST_SHARD_INDEX" ); - } - if ( !bazelShardTotal ) { - warn( "TEST_TOTAL_SHARDS" ); - } - if ( !bazelShardInfoFile ) { - warn( "TEST_SHARD_STATUS_FILE" ); - } - return {}; - } - - auto shardIndex = parseUInt( bazelShardIndex ); - if ( !shardIndex ) { - Catch::cerr() - << "Warning: could not parse 'TEST_SHARD_INDEX' ('" << bazelShardIndex - << "') as unsigned int.\n"; - return {}; - } - auto shardTotal = parseUInt( bazelShardTotal ); - if ( !shardTotal ) { - Catch::cerr() - << "Warning: could not parse 'TEST_TOTAL_SHARD' ('" - << bazelShardTotal << "') as unsigned int.\n"; - return {}; - } - - return bazelShardingOptions{ - *shardIndex, *shardTotal, bazelShardInfoFile }; - - } - } // end namespace - - - bool operator==( ProcessedReporterSpec const& lhs, - ProcessedReporterSpec const& rhs ) { - return lhs.name == rhs.name && - lhs.outputFilename == rhs.outputFilename && - lhs.colourMode == rhs.colourMode && - lhs.customOptions == rhs.customOptions; - } - - Config::Config( ConfigData const& data ): - m_data( data ) { - // We need to trim filter specs to avoid trouble with superfluous - // whitespace (esp. important for bdd macros, as those are manually - // aligned with whitespace). - - for (auto& elem : m_data.testsOrTags) { - elem = trim(elem); - } - for (auto& elem : m_data.sectionsToRun) { - elem = trim(elem); - } - - // Insert the default reporter if user hasn't asked for a specific one - if ( m_data.reporterSpecifications.empty() ) { -#if defined( CATCH_CONFIG_DEFAULT_REPORTER ) - const auto default_spec = CATCH_CONFIG_DEFAULT_REPORTER; -#else - const auto default_spec = "console"; -#endif - auto parsed = parseReporterSpec(default_spec); - CATCH_ENFORCE( parsed, - "Cannot parse the provided default reporter spec: '" - << default_spec << '\'' ); - m_data.reporterSpecifications.push_back( std::move( *parsed ) ); - } - - if ( enableBazelEnvSupport() ) { - readBazelEnvVars(); - } - - // Bazel support can modify the test specs, so parsing has to happen - // after reading Bazel env vars. - TestSpecParser parser( ITagAliasRegistry::get() ); - if ( !m_data.testsOrTags.empty() ) { - m_hasTestFilters = true; - for ( auto const& testOrTags : m_data.testsOrTags ) { - parser.parse( testOrTags ); - } - } - m_testSpec = parser.testSpec(); - - - // We now fixup the reporter specs to handle default output spec, - // default colour spec, etc - bool defaultOutputUsed = false; - for ( auto const& reporterSpec : m_data.reporterSpecifications ) { - // We do the default-output check separately, while always - // using the default output below to make the code simpler - // and avoid superfluous copies. - if ( reporterSpec.outputFile().none() ) { - CATCH_ENFORCE( !defaultOutputUsed, - "Internal error: cannot use default output for " - "multiple reporters" ); - defaultOutputUsed = true; - } - - m_processedReporterSpecs.push_back( ProcessedReporterSpec{ - reporterSpec.name(), - reporterSpec.outputFile() ? *reporterSpec.outputFile() - : data.defaultOutputFilename, - reporterSpec.colourMode().valueOr( data.defaultColourMode ), - reporterSpec.customOptions() } ); - } - } - - Config::~Config() = default; - - - bool Config::listTests() const { return m_data.listTests; } - bool Config::listTags() const { return m_data.listTags; } - bool Config::listReporters() const { return m_data.listReporters; } - bool Config::listListeners() const { return m_data.listListeners; } - - std::vector const& Config::getTestsOrTags() const { return m_data.testsOrTags; } - std::vector const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } - - std::vector const& Config::getReporterSpecs() const { - return m_data.reporterSpecifications; - } - - std::vector const& - Config::getProcessedReporterSpecs() const { - return m_processedReporterSpecs; - } - - TestSpec const& Config::testSpec() const { return m_testSpec; } - bool Config::hasTestFilters() const { return m_hasTestFilters; } - - bool Config::showHelp() const { return m_data.showHelp; } - - // IConfig interface - bool Config::allowThrows() const { return !m_data.noThrow; } - StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } - bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } - bool Config::warnAboutMissingAssertions() const { - return !!( m_data.warnings & WarnAbout::NoAssertions ); - } - bool Config::warnAboutUnmatchedTestSpecs() const { - return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec ); - } - bool Config::zeroTestsCountAsSuccess() const { return m_data.allowZeroTests; } - ShowDurations Config::showDurations() const { return m_data.showDurations; } - double Config::minDuration() const { return m_data.minDuration; } - TestRunOrder Config::runOrder() const { return m_data.runOrder; } - uint32_t Config::rngSeed() const { return m_data.rngSeed; } - unsigned int Config::shardCount() const { return m_data.shardCount; } - unsigned int Config::shardIndex() const { return m_data.shardIndex; } - ColourMode Config::defaultColourMode() const { return m_data.defaultColourMode; } - bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } - int Config::abortAfter() const { return m_data.abortAfter; } - bool Config::showInvisibles() const { return m_data.showInvisibles; } - Verbosity Config::verbosity() const { return m_data.verbosity; } - - bool Config::skipBenchmarks() const { return m_data.skipBenchmarks; } - bool Config::benchmarkNoAnalysis() const { return m_data.benchmarkNoAnalysis; } - unsigned int Config::benchmarkSamples() const { return m_data.benchmarkSamples; } - double Config::benchmarkConfidenceInterval() const { return m_data.benchmarkConfidenceInterval; } - unsigned int Config::benchmarkResamples() const { return m_data.benchmarkResamples; } - std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); } - - void Config::readBazelEnvVars() { - // Register a JUnit reporter for Bazel. Bazel sets an environment - // variable with the path to XML output. If this file is written to - // during test, Bazel will not generate a default XML output. - // This allows the XML output file to contain higher level of detail - // than what is possible otherwise. - const auto bazelOutputFile = Detail::getEnv( "XML_OUTPUT_FILE" ); - - if ( bazelOutputFile ) { - m_data.reporterSpecifications.push_back( - { "junit", std::string( bazelOutputFile ), {}, {} } ); - } - - const auto bazelTestSpec = Detail::getEnv( "TESTBRIDGE_TEST_ONLY" ); - if ( bazelTestSpec ) { - // Presumably the test spec from environment should overwrite - // the one we got from CLI (if we got any) - m_data.testsOrTags.clear(); - m_data.testsOrTags.push_back( bazelTestSpec ); - } - - const auto bazelShardOptions = readBazelShardingOptions(); - if ( bazelShardOptions ) { - std::ofstream f( bazelShardOptions->shardFilePath, - std::ios_base::out | std::ios_base::trunc ); - if ( f.is_open() ) { - f << ""; - m_data.shardIndex = bazelShardOptions->shardIndex; - m_data.shardCount = bazelShardOptions->shardCount; - } - } - } - -} // end namespace Catch - - - - - -namespace Catch { - std::uint32_t getSeed() { - return getCurrentContext().getConfig()->rngSeed(); - } -} - - - -#include -#include - -namespace Catch { - - //////////////////////////////////////////////////////////////////////////// - - - ScopedMessage::ScopedMessage( MessageBuilder&& builder ): - m_info( CATCH_MOVE(builder.m_info) ) { - m_info.message = builder.m_stream.str(); - getResultCapture().pushScopedMessage( m_info ); - } - - ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept: - m_info( CATCH_MOVE( old.m_info ) ) { - old.m_moved = true; - } - - ScopedMessage::~ScopedMessage() { - if ( !uncaught_exceptions() && !m_moved ){ - getResultCapture().popScopedMessage(m_info); - } - } - - - Capturer::Capturer( StringRef macroName, - SourceLineInfo const& lineInfo, - ResultWas::OfType resultType, - StringRef names ): - m_resultCapture( getResultCapture() ) { - auto trimmed = [&] (size_t start, size_t end) { - while (names[start] == ',' || isspace(static_cast(names[start]))) { - ++start; - } - while (names[end] == ',' || isspace(static_cast(names[end]))) { - --end; - } - return names.substr(start, end - start + 1); - }; - auto skipq = [&] (size_t start, char quote) { - for (auto i = start + 1; i < names.size() ; ++i) { - if (names[i] == quote) - return i; - if (names[i] == '\\') - ++i; - } - CATCH_INTERNAL_ERROR("CAPTURE parsing encountered unmatched quote"); - }; - - size_t start = 0; - std::stack openings; - for (size_t pos = 0; pos < names.size(); ++pos) { - char c = names[pos]; - switch (c) { - case '[': - case '{': - case '(': - // It is basically impossible to disambiguate between - // comparison and start of template args in this context -// case '<': - openings.push(c); - break; - case ']': - case '}': - case ')': -// case '>': - openings.pop(); - break; - case '"': - case '\'': - pos = skipq(pos, c); - break; - case ',': - if (start != pos && openings.empty()) { - m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = static_cast(trimmed(start, pos)); - m_messages.back().message += " := "; - start = pos; - } - break; - default:; // noop - } - } - assert(openings.empty() && "Mismatched openings"); - m_messages.emplace_back(macroName, lineInfo, resultType); - m_messages.back().message = static_cast(trimmed(start, names.size() - 1)); - m_messages.back().message += " := "; - } - Capturer::~Capturer() { - if ( !uncaught_exceptions() ){ - assert( m_captured == m_messages.size() ); - for( size_t i = 0; i < m_captured; ++i ) - m_resultCapture.popScopedMessage( m_messages[i] ); - } - } - - void Capturer::captureValue( size_t index, std::string const& value ) { - assert( index < m_messages.size() ); - m_messages[index].message += value; - m_resultCapture.pushScopedMessage( m_messages[index] ); - m_captured++; - } - -} // end namespace Catch - - - - -#include - -namespace Catch { - - namespace { - - class RegistryHub : public IRegistryHub, - public IMutableRegistryHub, - private Detail::NonCopyable { - - public: // IRegistryHub - RegistryHub() = default; - ReporterRegistry const& getReporterRegistry() const override { - return m_reporterRegistry; - } - ITestCaseRegistry const& getTestCaseRegistry() const override { - return m_testCaseRegistry; - } - IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const 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 factory ) override { - m_reporterRegistry.registerReporter( name, CATCH_MOVE(factory) ); - } - void registerListener( Detail::unique_ptr factory ) override { - m_reporterRegistry.registerListener( CATCH_MOVE(factory) ); - } - void registerTest( Detail::unique_ptr&& testInfo, Detail::unique_ptr&& invoker ) override { - m_testCaseRegistry.registerTest( CATCH_MOVE(testInfo), CATCH_MOVE(invoker) ); - } - void registerTranslator( Detail::unique_ptr&& translator ) override { - m_exceptionTranslatorRegistry.registerTranslator( CATCH_MOVE(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 { -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - m_exceptionRegistry.add(std::current_exception()); -#else - CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); -#endif - } - IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override { - return m_enumValuesRegistry; - } - - private: - TestRegistry m_testCaseRegistry; - ReporterRegistry m_reporterRegistry; - ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; - TagAliasRegistry m_tagAliasRegistry; - StartupExceptionRegistry m_exceptionRegistry; - Detail::EnumValuesRegistry m_enumValuesRegistry; - }; - } - - using RegistryHubSingleton = Singleton; - - IRegistryHub const& getRegistryHub() { - return RegistryHubSingleton::get(); - } - IMutableRegistryHub& getMutableRegistryHub() { - return RegistryHubSingleton::getMutable(); - } - void cleanUp() { - cleanupSingletons(); - cleanUpContext(); - } - std::string translateActiveException() { - return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); - } - - -} // end namespace Catch - - - -#include -#include -#include -#include -#include - -namespace Catch { - - namespace { - static constexpr int TestFailureExitCode = 42; - static constexpr int UnspecifiedErrorExitCode = 1; - static constexpr int AllTestsSkippedExitCode = 4; - static constexpr int NoTestsRunExitCode = 2; - static constexpr int UnmatchedTestSpecExitCode = 3; - static constexpr int InvalidTestSpecExitCode = 5; - - - IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) { - auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config)); - CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\''); - - return reporter; - } - - IEventListenerPtr prepareReporters(Config const* config) { - if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty() - && config->getProcessedReporterSpecs().size() == 1) { - auto const& spec = config->getProcessedReporterSpecs()[0]; - return createReporter( - spec.name, - ReporterConfig( config, - makeStream( spec.outputFilename ), - spec.colourMode, - spec.customOptions ) ); - } - - auto multi = Detail::make_unique(config); - - auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); - for (auto const& listener : listeners) { - multi->addListener(listener->create(config)); - } - - for ( auto const& reporterSpec : config->getProcessedReporterSpecs() ) { - multi->addReporter( createReporter( - reporterSpec.name, - ReporterConfig( config, - makeStream( reporterSpec.outputFilename ), - reporterSpec.colourMode, - reporterSpec.customOptions ) ) ); - } - - return multi; - } - - class TestGroup { - public: - explicit TestGroup(IEventListenerPtr&& reporter, Config const* config): - m_reporter(reporter.get()), - m_config{config}, - m_context{config, CATCH_MOVE(reporter)} { - - assert( m_config->testSpec().getInvalidSpecs().empty() && - "Invalid test specs should be handled before running tests" ); - - auto const& allTestCases = getAllTestCasesSorted(*m_config); - auto const& testSpec = m_config->testSpec(); - if ( !testSpec.hasFilters() ) { - for ( auto const& test : allTestCases ) { - if ( !test.getTestCaseInfo().isHidden() ) { - m_tests.emplace( &test ); - } - } - } else { - m_matches = - testSpec.matchesByFilter( allTestCases, *m_config ); - for ( auto const& match : m_matches ) { - m_tests.insert( match.tests.begin(), - match.tests.end() ); - } - } - - m_tests = createShard(m_tests, m_config->shardCount(), m_config->shardIndex()); - } - - Totals execute() { - Totals totals; - for (auto const& testCase : m_tests) { - if (!m_context.aborting()) - totals += m_context.runTest(*testCase); - else - m_reporter->skipTest(testCase->getTestCaseInfo()); - } - - for (auto const& match : m_matches) { - if (match.tests.empty()) { - m_unmatchedTestSpecs = true; - m_reporter->noMatchingTestCases( match.name ); - } - } - - return totals; - } - - bool hadUnmatchedTestSpecs() const { - return m_unmatchedTestSpecs; - } - - - private: - IEventListener* m_reporter; - Config const* m_config; - RunContext m_context; - std::set m_tests; - TestSpec::Matches m_matches; - bool m_unmatchedTestSpecs = false; - }; - - void applyFilenamesAsTags() { - for (auto const& testInfo : getRegistryHub().getTestCaseRegistry().getAllInfos()) { - testInfo->addFilenameTag(); - } - } - - } // anon namespace - - Session::Session() { - static bool alreadyInstantiated = false; - if( alreadyInstantiated ) { - CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } - CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } - } - - // There cannot be exceptions at startup in no-exception mode. -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); - if ( !exceptions.empty() ) { - config(); - getCurrentMutableContext().setConfig(m_config.get()); - - m_startupExceptions = true; - auto errStream = makeStream( "%stderr" ); - auto colourImpl = makeColourImpl( - ColourMode::PlatformDefault, errStream.get() ); - auto guard = colourImpl->guardColour( Colour::Red ); - errStream->stream() << "Errors occurred during startup!" << '\n'; - // iterate over all exceptions and notify user - for ( const auto& ex_ptr : exceptions ) { - try { - std::rethrow_exception(ex_ptr); - } catch ( std::exception const& ex ) { - errStream->stream() << TextFlow::Column( ex.what() ).indent(2) << '\n'; - } - } - } -#endif - - alreadyInstantiated = true; - m_cli = makeCommandLineParser( m_configData ); - } - Session::~Session() { - Catch::cleanUp(); - } - - void Session::showHelp() const { - Catch::cout() - << "\nCatch2 v" << libraryVersion() << '\n' - << m_cli << '\n' - << "For more detailed usage please see the project docs\n\n" << std::flush; - } - void Session::libIdentify() { - Catch::cout() - << std::left << std::setw(16) << "description: " << "A Catch2 test executable\n" - << std::left << std::setw(16) << "category: " << "testframework\n" - << std::left << std::setw(16) << "framework: " << "Catch2\n" - << std::left << std::setw(16) << "version: " << libraryVersion() << '\n' << std::flush; - } - - int Session::applyCommandLine( int argc, char const * const * argv ) { - if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; } - - auto result = m_cli.parse( Clara::Args( argc, argv ) ); - - if( !result ) { - config(); - getCurrentMutableContext().setConfig(m_config.get()); - auto errStream = makeStream( "%stderr" ); - auto colour = makeColourImpl( ColourMode::PlatformDefault, errStream.get() ); - - errStream->stream() - << colour->guardColour( Colour::Red ) - << "\nError(s) in input:\n" - << TextFlow::Column( result.errorMessage() ).indent( 2 ) - << "\n\n"; - errStream->stream() << "Run with -? for usage\n\n" << std::flush; - return UnspecifiedErrorExitCode; - } - - if( m_configData.showHelp ) - showHelp(); - if( m_configData.libIdentify ) - libIdentify(); - - m_config.reset(); - return 0; - } - -#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) - int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { - - char **utf8Argv = new char *[ argc ]; - - for ( int i = 0; i < argc; ++i ) { - int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr ); - - utf8Argv[ i ] = new char[ bufSize ]; - - WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr ); - } - - int returnCode = applyCommandLine( argc, utf8Argv ); - - for ( int i = 0; i < argc; ++i ) - delete [] utf8Argv[ i ]; - - delete [] utf8Argv; - - return returnCode; - } -#endif - - void Session::useConfigData( ConfigData const& configData ) { - m_configData = configData; - m_config.reset(); - } - - int Session::run() { - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before starting\n" << std::flush; - static_cast(std::getchar()); - } - int exitCode = runInternal(); - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush; - static_cast(std::getchar()); - } - 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 = Detail::make_unique( m_configData ); - return *m_config; - } - - int Session::runInternal() { - if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; } - - if (m_configData.showHelp || m_configData.libIdentify) { - return 0; - } - - if ( m_configData.shardIndex >= m_configData.shardCount ) { - Catch::cerr() << "The shard count (" << m_configData.shardCount - << ") must be greater than the shard index (" - << m_configData.shardIndex << ")\n" - << std::flush; - return UnspecifiedErrorExitCode; - } - - CATCH_TRY { - config(); // Force config to be constructed - - seedRng( *m_config ); - - if (m_configData.filenamesAsTags) { - applyFilenamesAsTags(); - } - - // Set up global config instance before we start calling into other functions - getCurrentMutableContext().setConfig(m_config.get()); - - // Create reporter(s) so we can route listings through them - auto reporter = prepareReporters(m_config.get()); - - auto const& invalidSpecs = m_config->testSpec().getInvalidSpecs(); - if ( !invalidSpecs.empty() ) { - for ( auto const& spec : invalidSpecs ) { - reporter->reportInvalidTestSpec( spec ); - } - return InvalidTestSpecExitCode; - } - - - // Handle list request - if (list(*reporter, *m_config)) { - return 0; - } - - TestGroup tests { CATCH_MOVE(reporter), m_config.get() }; - auto const totals = tests.execute(); - - if ( tests.hadUnmatchedTestSpecs() - && m_config->warnAboutUnmatchedTestSpecs() ) { - // UnmatchedTestSpecExitCode - return UnmatchedTestSpecExitCode; - } - - if ( totals.testCases.total() == 0 - && !m_config->zeroTestsCountAsSuccess() ) { - return NoTestsRunExitCode; - } - - if ( totals.testCases.total() > 0 && - totals.testCases.total() == totals.testCases.skipped - && !m_config->zeroTestsCountAsSuccess() ) { - return AllTestsSkippedExitCode; - } - - if ( totals.assertions.failed ) { return TestFailureExitCode; } - return 0; - - } -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - catch( std::exception& ex ) { - Catch::cerr() << ex.what() << '\n' << std::flush; - return UnspecifiedErrorExitCode; - } -#endif - } - -} // end namespace Catch - - - - -namespace Catch { - - RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { - CATCH_TRY { - getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); - } CATCH_CATCH_ALL { - // Do not throw when constructing global objects, instead register the exception to be processed later - getMutableRegistryHub().registerStartupException(); - } - } - -} - - - -#include -#include -#include - -namespace Catch { - - namespace { - using TCP_underlying_type = uint8_t; - static_assert(sizeof(TestCaseProperties) == sizeof(TCP_underlying_type), - "The size of the TestCaseProperties is different from the assumed size"); - - constexpr TestCaseProperties operator|(TestCaseProperties lhs, TestCaseProperties rhs) { - return static_cast( - static_cast(lhs) | static_cast(rhs) - ); - } - - constexpr TestCaseProperties& operator|=(TestCaseProperties& lhs, TestCaseProperties rhs) { - lhs = static_cast( - static_cast(lhs) | static_cast(rhs) - ); - return lhs; - } - - constexpr TestCaseProperties operator&(TestCaseProperties lhs, TestCaseProperties rhs) { - return static_cast( - static_cast(lhs) & static_cast(rhs) - ); - } - - constexpr bool applies(TestCaseProperties tcp) { - static_assert(static_cast(TestCaseProperties::None) == 0, - "TestCaseProperties::None must be equal to 0"); - return tcp != TestCaseProperties::None; - } - - TestCaseProperties parseSpecialTag( StringRef tag ) { - if( !tag.empty() && tag[0] == '.' ) - return TestCaseProperties::IsHidden; - else if( tag == "!throws"_sr ) - return TestCaseProperties::Throws; - else if( tag == "!shouldfail"_sr ) - return TestCaseProperties::ShouldFail; - else if( tag == "!mayfail"_sr ) - return TestCaseProperties::MayFail; - else if( tag == "!nonportable"_sr ) - return TestCaseProperties::NonPortable; - else if( tag == "!benchmark"_sr ) - return TestCaseProperties::Benchmark | TestCaseProperties::IsHidden; - else - return TestCaseProperties::None; - } - bool isReservedTag( StringRef tag ) { - return parseSpecialTag( tag ) == TestCaseProperties::None - && tag.size() > 0 - && !std::isalnum( static_cast(tag[0]) ); - } - void enforceNotReservedTag( StringRef tag, SourceLineInfo const& _lineInfo ) { - CATCH_ENFORCE( !isReservedTag(tag), - "Tag name: [" << tag << "] is not allowed.\n" - << "Tag names starting with non alphanumeric characters are reserved\n" - << _lineInfo ); - } - - std::string makeDefaultName() { - static size_t counter = 0; - return "Anonymous test case " + std::to_string(++counter); - } - - constexpr StringRef extractFilenamePart(StringRef filename) { - size_t lastDot = filename.size(); - while (lastDot > 0 && filename[lastDot - 1] != '.') { - --lastDot; - } - // In theory we could have filename without any extension in it - if ( lastDot == 0 ) { return StringRef(); } - - --lastDot; - size_t nameStart = lastDot; - while (nameStart > 0 && filename[nameStart - 1] != '/' && filename[nameStart - 1] != '\\') { - --nameStart; - } - - return filename.substr(nameStart, lastDot - nameStart); - } - - // Returns the upper bound on size of extra tags ([#file]+[.]) - constexpr size_t sizeOfExtraTags(StringRef filepath) { - // [.] is 3, [#] is another 3 - const size_t extras = 3 + 3; - return extractFilenamePart(filepath).size() + extras; - } - } // end unnamed namespace - - bool operator<( Tag const& lhs, Tag const& rhs ) { - Detail::CaseInsensitiveLess cmp; - return cmp( lhs.original, rhs.original ); - } - bool operator==( Tag const& lhs, Tag const& rhs ) { - Detail::CaseInsensitiveEqualTo cmp; - return cmp( lhs.original, rhs.original ); - } - - Detail::unique_ptr - makeTestCaseInfo(StringRef _className, - NameAndTags const& nameAndTags, - SourceLineInfo const& _lineInfo ) { - return Detail::make_unique(_className, nameAndTags, _lineInfo); - } - - TestCaseInfo::TestCaseInfo(StringRef _className, - NameAndTags const& _nameAndTags, - SourceLineInfo const& _lineInfo): - name( _nameAndTags.name.empty() ? makeDefaultName() : _nameAndTags.name ), - className( _className ), - lineInfo( _lineInfo ) - { - StringRef originalTags = _nameAndTags.tags; - // We need to reserve enough space to store all of the tags - // (including optional hidden tag and filename tag) - auto requiredSize = originalTags.size() + sizeOfExtraTags(_lineInfo.file); - backingTags.reserve(requiredSize); - - // We cannot copy the tags directly, as we need to normalize - // some tags, so that [.foo] is copied as [.][foo]. - size_t tagStart = 0; - size_t tagEnd = 0; - bool inTag = false; - for (size_t idx = 0; idx < originalTags.size(); ++idx) { - auto c = originalTags[idx]; - if (c == '[') { - CATCH_ENFORCE( - !inTag, - "Found '[' inside a tag while registering test case '" - << _nameAndTags.name << "' at " << _lineInfo ); - - inTag = true; - tagStart = idx; - } - if (c == ']') { - CATCH_ENFORCE( - inTag, - "Found unmatched ']' while registering test case '" - << _nameAndTags.name << "' at " << _lineInfo ); - - inTag = false; - tagEnd = idx; - assert(tagStart < tagEnd); - - // We need to check the tag for special meanings, copy - // it over to backing storage and actually reference the - // backing storage in the saved tags - StringRef tagStr = originalTags.substr(tagStart+1, tagEnd - tagStart - 1); - CATCH_ENFORCE( !tagStr.empty(), - "Found an empty tag while registering test case '" - << _nameAndTags.name << "' at " - << _lineInfo ); - - enforceNotReservedTag(tagStr, lineInfo); - properties |= parseSpecialTag(tagStr); - // When copying a tag to the backing storage, we need to - // check if it is a merged hide tag, such as [.foo], and - // if it is, we need to handle it as if it was [foo]. - if (tagStr.size() > 1 && tagStr[0] == '.') { - tagStr = tagStr.substr(1, tagStr.size() - 1); - } - // We skip over dealing with the [.] tag, as we will add - // it later unconditionally and then sort and unique all - // the tags. - internalAppendTag(tagStr); - } - } - CATCH_ENFORCE( !inTag, - "Found an unclosed tag while registering test case '" - << _nameAndTags.name << "' at " << _lineInfo ); - - - // Add [.] if relevant - if (isHidden()) { - internalAppendTag("."_sr); - } - - // Sort and prepare tags - std::sort(begin(tags), end(tags)); - tags.erase(std::unique(begin(tags), end(tags)), - end(tags)); - } - - bool TestCaseInfo::isHidden() const { - return applies( properties & TestCaseProperties::IsHidden ); - } - bool TestCaseInfo::throws() const { - return applies( properties & TestCaseProperties::Throws ); - } - bool TestCaseInfo::okToFail() const { - return applies( properties & (TestCaseProperties::ShouldFail | TestCaseProperties::MayFail ) ); - } - bool TestCaseInfo::expectedToFail() const { - return applies( properties & (TestCaseProperties::ShouldFail) ); - } - - void TestCaseInfo::addFilenameTag() { - std::string combined("#"); - combined += extractFilenamePart(lineInfo.file); - internalAppendTag(combined); - } - - 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.original.size(); - } - ret.reserve(full_size); - for (const auto& tag : tags) { - ret.push_back('['); - ret += tag.original; - ret.push_back(']'); - } - - return ret; - } - - void TestCaseInfo::internalAppendTag(StringRef tagStr) { - backingTags += '['; - const auto backingStart = backingTags.size(); - backingTags += tagStr; - const auto backingEnd = backingTags.size(); - backingTags += ']'; - tags.emplace_back(StringRef(backingTags.c_str() + backingStart, backingEnd - backingStart)); - } - - bool operator<( TestCaseInfo const& lhs, TestCaseInfo const& rhs ) { - // We want to avoid redoing the string comparisons multiple times, - // so we store the result of a three-way comparison before using - // it in the actual comparison logic. - const auto cmpName = lhs.name.compare( rhs.name ); - if ( cmpName != 0 ) { - return cmpName < 0; - } - const auto cmpClassName = lhs.className.compare( rhs.className ); - if ( cmpClassName != 0 ) { - return cmpClassName < 0; - } - return lhs.tags < rhs.tags; - } - -} // end namespace Catch - - - -#include -#include -#include -#include - -namespace Catch { - - TestSpec::Pattern::Pattern( std::string const& name ) - : m_name( name ) - {} - - TestSpec::Pattern::~Pattern() = default; - - std::string const& TestSpec::Pattern::name() const { - return m_name; - } - - - TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString ) - : Pattern( filterString ) - , m_wildcardPattern( toLower( name ), CaseSensitive::No ) - {} - - bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { - return m_wildcardPattern.matches( testCase.name ); - } - - void TestSpec::NamePattern::serializeTo( std::ostream& out ) const { - out << '"' << name() << '"'; - } - - - TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString ) - : Pattern( filterString ) - , m_tag( tag ) - {} - - bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { - return std::find( begin( testCase.tags ), - end( testCase.tags ), - Tag( m_tag ) ) != end( testCase.tags ); - } - - void TestSpec::TagPattern::serializeTo( std::ostream& out ) const { - out << name(); - } - - bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { - bool should_use = !testCase.isHidden(); - for (auto const& pattern : m_required) { - should_use = true; - if (!pattern->matches(testCase)) { - return false; - } - } - for (auto const& pattern : m_forbidden) { - if (pattern->matches(testCase)) { - return false; - } - } - return should_use; - } - - void TestSpec::Filter::serializeTo( std::ostream& out ) const { - bool first = true; - for ( auto const& pattern : m_required ) { - if ( !first ) { - out << ' '; - } - out << *pattern; - first = false; - } - for ( auto const& pattern : m_forbidden ) { - if ( !first ) { - out << ' '; - } - out << *pattern; - first = false; - } - } - - - std::string TestSpec::extractFilterName( Filter const& filter ) { - Catch::ReusableStringStream sstr; - sstr << filter; - return sstr.str(); - } - - bool TestSpec::hasFilters() const { - return !m_filters.empty(); - } - - bool TestSpec::matches( TestCaseInfo const& testCase ) const { - return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } ); - } - - TestSpec::Matches TestSpec::matchesByFilter( std::vector const& testCases, IConfig const& config ) const { - Matches matches; - matches.reserve( m_filters.size() ); - for ( auto const& filter : m_filters ) { - std::vector currentMatches; - for ( auto const& test : testCases ) - if ( isThrowSafe( test, config ) && - filter.matches( test.getTestCaseInfo() ) ) - currentMatches.emplace_back( &test ); - matches.push_back( - FilterMatch{ extractFilterName( filter ), currentMatches } ); - } - return matches; - } - - const TestSpec::vectorStrings& TestSpec::getInvalidSpecs() const { - return m_invalidSpecs; - } - - void TestSpec::serializeTo( std::ostream& out ) const { - bool first = true; - for ( auto const& filter : m_filters ) { - if ( !first ) { - out << ','; - } - out << filter; - first = false; - } - } - -} - - - -#include - -namespace Catch { - - namespace { - static auto getCurrentNanosecondsSinceEpoch() -> uint64_t { - return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); - } - } // end unnamed namespace - - void Timer::start() { - m_nanoseconds = getCurrentNanosecondsSinceEpoch(); - } - auto Timer::getElapsedNanoseconds() const -> uint64_t { - return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; - } - auto Timer::getElapsedMicroseconds() const -> uint64_t { - return getElapsedNanoseconds()/1000; - } - auto Timer::getElapsedMilliseconds() const -> unsigned int { - return static_cast(getElapsedMicroseconds()/1000); - } - auto Timer::getElapsedSeconds() const -> double { - return getElapsedMicroseconds()/1000000.0; - } - - -} // namespace Catch - - - - -#include -#include - -namespace Catch { - -namespace Detail { - - namespace { - const int hexThreshold = 255; - - struct Endianness { - enum Arch { Big, Little }; - - static Arch which() { - int one = 1; - // If the lowest byte we read is non-zero, we can assume - // that little endian format is used. - auto value = *reinterpret_cast(&one); - return value ? Little : Big; - } - }; - - template - std::string fpToString(T value, int precision) { - if (Catch::isnan(value)) { - return "nan"; - } - - ReusableStringStream rss; - rss << std::setprecision(precision) - << std::fixed - << value; - std::string d = rss.str(); - std::size_t i = d.find_last_not_of('0'); - if (i != std::string::npos && i != d.size() - 1) { - if (d[i] == '.') - i++; - d = d.substr(0, i + 1); - } - return d; - } - } // end unnamed namespace - - std::string convertIntoString(StringRef string, bool escapeInvisibles) { - std::string ret; - // This is enough for the "don't escape invisibles" case, and a good - // lower bound on the "escape invisibles" case. - ret.reserve(string.size() + 2); - - if (!escapeInvisibles) { - ret += '"'; - ret += string; - ret += '"'; - return ret; - } - - ret += '"'; - for (char c : string) { - switch (c) { - case '\r': - ret.append("\\r"); - break; - case '\n': - ret.append("\\n"); - break; - case '\t': - ret.append("\\t"); - break; - case '\f': - ret.append("\\f"); - break; - default: - ret.push_back(c); - break; - } - } - ret += '"'; - - return ret; - } - - std::string convertIntoString(StringRef string) { - return convertIntoString(string, getCurrentContext().getConfig()->showInvisibles()); - } - - 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); - ReusableStringStream rss; - rss << "0x" << std::setfill('0') << std::hex; - for( ; i != end; i += inc ) - rss << std::setw(2) << static_cast(bytes[i]); - return rss.str(); - } -} // end Detail namespace - - - -//// ======================================================= //// -// -// Out-of-line defs for full specialization of StringMaker -// -//// ======================================================= //// - -std::string StringMaker::convert(const std::string& str) { - return Detail::convertIntoString( str ); -} - -#ifdef CATCH_CONFIG_CPP17_STRING_VIEW -std::string StringMaker::convert(std::string_view str) { - return Detail::convertIntoString( StringRef( str.data(), str.size() ) ); -} -#endif - -std::string StringMaker::convert(char const* str) { - if (str) { - return Detail::convertIntoString( str ); - } else { - return{ "{null string}" }; - } -} -std::string StringMaker::convert(char* str) { // NOLINT(readability-non-const-parameter) - if (str) { - return Detail::convertIntoString( str ); - } else { - return{ "{null string}" }; - } -} - -#ifdef CATCH_CONFIG_WCHAR -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); -} - -# ifdef CATCH_CONFIG_CPP17_STRING_VIEW -std::string StringMaker::convert(std::wstring_view str) { - return StringMaker::convert(std::wstring(str)); -} -# endif - -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}" }; - } -} -#endif - -#if defined(CATCH_CONFIG_CPP17_BYTE) -#include -std::string StringMaker::convert(std::byte value) { - return ::Catch::Detail::stringify(std::to_integer(value)); -} -#endif // defined(CATCH_CONFIG_CPP17_BYTE) - -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) { - ReusableStringStream rss; - rss << value; - if (value > Detail::hexThreshold) { - rss << " (0x" << std::hex << value << ')'; - } - return rss.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) { - ReusableStringStream rss; - rss << value; - if (value > Detail::hexThreshold) { - rss << " (0x" << std::hex << value << ')'; - } - return rss.str(); -} - -std::string StringMaker::convert(signed 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(char c) { - return ::Catch::Detail::stringify(static_cast(c)); -} -std::string StringMaker::convert(unsigned char value) { - return ::Catch::Detail::stringify(static_cast(value)); -} - -int StringMaker::precision = std::numeric_limits::max_digits10; - -std::string StringMaker::convert(float value) { - return Detail::fpToString(value, precision) + 'f'; -} - -int StringMaker::precision = std::numeric_limits::max_digits10; - -std::string StringMaker::convert(double value) { - return Detail::fpToString(value, precision); -} - -} // end namespace Catch - - - -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; - diff.skipped = skipped - other.skipped; - return diff; - } - - Counts& Counts::operator += ( Counts const& other ) { - passed += other.passed; - failed += other.failed; - failedButOk += other.failedButOk; - skipped += other.skipped; - return *this; - } - - std::uint64_t Counts::total() const { - return passed + failed + failedButOk + skipped; - } - bool Counts::allPassed() const { - return failed == 0 && failedButOk == 0 && skipped == 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 if ( diff.assertions.skipped > 0 ) - ++ diff.testCases.skipped; - else - ++diff.testCases.passed; - return diff; - } - -} - - - - -namespace Catch { - namespace Detail { - void registerTranslatorImpl( - Detail::unique_ptr&& translator ) { - getMutableRegistryHub().registerTranslator( - CATCH_MOVE( translator ) ); - } - } // namespace Detail -} // namespace Catch - - -#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( 3, 7, 1, "", 0 ); - return version; - } - -} - - - - -namespace Catch { - - const char* GeneratorException::what() const noexcept { - return m_msg; - } - -} // end namespace Catch - - - - -namespace Catch { - - IGeneratorTracker::~IGeneratorTracker() = default; - -namespace Generators { - -namespace Detail { - - [[noreturn]] - void throw_generator_exception(char const* msg) { - Catch::throw_exception(GeneratorException{ msg }); - } -} // end namespace Detail - - GeneratorUntypedBase::~GeneratorUntypedBase() = default; - - IGeneratorTracker* acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const& lineInfo ) { - return getResultCapture().acquireGeneratorTracker( generatorName, lineInfo ); - } - - IGeneratorTracker* createGeneratorTracker( StringRef generatorName, - SourceLineInfo lineInfo, - GeneratorBasePtr&& generator ) { - return getResultCapture().createGeneratorTracker( - generatorName, lineInfo, CATCH_MOVE( generator ) ); - } - -} // namespace Generators -} // namespace Catch - - - - -#include - -namespace Catch { - namespace Generators { - namespace Detail { - std::uint32_t getSeed() { return sharedRng()(); } - } // namespace Detail - - struct RandomFloatingGenerator::PImpl { - PImpl( long double a, long double b, uint32_t seed ): - rng( seed ), dist( a, b ) {} - - Catch::SimplePcg32 rng; - std::uniform_real_distribution dist; - }; - - RandomFloatingGenerator::RandomFloatingGenerator( - long double a, long double b, std::uint32_t seed) : - m_pimpl(Catch::Detail::make_unique(a, b, seed)) { - static_cast( next() ); - } - - RandomFloatingGenerator::~RandomFloatingGenerator() = - default; - bool RandomFloatingGenerator::next() { - m_current_number = m_pimpl->dist( m_pimpl->rng ); - return true; - } - } // namespace Generators -} // namespace Catch - - - - -namespace Catch { - IResultCapture::~IResultCapture() = default; -} - - - - -namespace Catch { - IConfig::~IConfig() = default; -} - - - - -namespace Catch { - IExceptionTranslator::~IExceptionTranslator() = default; - IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; -} - - - -#include - -namespace Catch { - namespace Generators { - - bool GeneratorUntypedBase::countedNext() { - auto ret = next(); - if ( ret ) { - m_stringReprCache.clear(); - ++m_currentElementIndex; - } - return ret; - } - - StringRef GeneratorUntypedBase::currentElementAsString() const { - if ( m_stringReprCache.empty() ) { - m_stringReprCache = stringifyImpl(); - } - return m_stringReprCache; - } - - } // namespace Generators -} // namespace Catch - - - - -namespace Catch { - IRegistryHub::~IRegistryHub() = default; - IMutableRegistryHub::~IMutableRegistryHub() = default; -} - - - -#include - -namespace Catch { - - ReporterConfig::ReporterConfig( - IConfig const* _fullConfig, - Detail::unique_ptr _stream, - ColourMode colourMode, - std::map customOptions ): - m_stream( CATCH_MOVE(_stream) ), - m_fullConfig( _fullConfig ), - m_colourMode( colourMode ), - m_customOptions( CATCH_MOVE( customOptions ) ) {} - - Detail::unique_ptr ReporterConfig::takeStream() && { - assert( m_stream ); - return CATCH_MOVE( m_stream ); - } - IConfig const * ReporterConfig::fullConfig() const { return m_fullConfig; } - ColourMode ReporterConfig::colourMode() const { return m_colourMode; } - - std::map const& - ReporterConfig::customOptions() const { - return m_customOptions; - } - - ReporterConfig::~ReporterConfig() = default; - - AssertionStats::AssertionStats( AssertionResult const& _assertionResult, - std::vector const& _infoMessages, - Totals const& _totals ) - : assertionResult( _assertionResult ), - infoMessages( _infoMessages ), - totals( _totals ) - { - if( assertionResult.hasMessage() ) { - // Copy message into messages list. - // !TBD This should have been done earlier, somewhere - MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); - builder.m_info.message = static_cast(assertionResult.getMessage()); - - infoMessages.push_back( CATCH_MOVE(builder.m_info) ); - } - } - - SectionStats::SectionStats( SectionInfo&& _sectionInfo, - Counts const& _assertions, - double _durationInSeconds, - bool _missingAssertions ) - : sectionInfo( CATCH_MOVE(_sectionInfo) ), - assertions( _assertions ), - durationInSeconds( _durationInSeconds ), - missingAssertions( _missingAssertions ) - {} - - - TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, - Totals const& _totals, - std::string&& _stdOut, - std::string&& _stdErr, - bool _aborting ) - : testInfo( &_testInfo ), - totals( _totals ), - stdOut( CATCH_MOVE(_stdOut) ), - stdErr( CATCH_MOVE(_stdErr) ), - aborting( _aborting ) - {} - - - TestRunStats::TestRunStats( TestRunInfo const& _runInfo, - Totals const& _totals, - bool _aborting ) - : runInfo( _runInfo ), - totals( _totals ), - aborting( _aborting ) - {} - - IEventListener::~IEventListener() = default; - -} // end namespace Catch - - - - -namespace Catch { - IReporterFactory::~IReporterFactory() = default; - EventListenerFactory::~EventListenerFactory() = default; -} - - - - -namespace Catch { - ITestCaseRegistry::~ITestCaseRegistry() = default; -} - - - -namespace Catch { - - AssertionHandler::AssertionHandler - ( StringRef macroName, - SourceLineInfo const& lineInfo, - StringRef capturedExpression, - ResultDisposition::Flags resultDisposition ) - : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, - m_resultCapture( getResultCapture() ) - { - m_resultCapture.notifyAssertionStarted( m_assertionInfo ); - } - - void AssertionHandler::handleExpr( ITransientExpression const& expr ) { - m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); - } - void AssertionHandler::handleMessage(ResultWas::OfType resultType, std::string&& message) { - m_resultCapture.handleMessage( m_assertionInfo, resultType, CATCH_MOVE(message), m_reaction ); - } - - auto AssertionHandler::allowThrows() const -> bool { - return getCurrentContext().getConfig()->allowThrows(); - } - - void AssertionHandler::complete() { - m_completed = true; - if( m_reaction.shouldDebugBreak ) { - - // If you find your debugger stopping you here then go one level up on the - // call-stack for the code that caused it (typically a failed assertion) - - // (To go back to the test and change execution, jump over the throw, next) - CATCH_BREAK_INTO_DEBUGGER(); - } - if (m_reaction.shouldThrow) { - throw_test_failure_exception(); - } - if ( m_reaction.shouldSkip ) { - throw_test_skip_exception(); - } - } - - void AssertionHandler::handleUnexpectedInflightException() { - m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); - } - - void AssertionHandler::handleExceptionThrownAsExpected() { - m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); - } - void AssertionHandler::handleExceptionNotThrownAsExpected() { - m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); - } - - void AssertionHandler::handleUnexpectedExceptionNotThrown() { - m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); - } - - void AssertionHandler::handleThrowingCallSkipped() { - m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); - } - - // 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 ) { - handleExceptionMatchExpr( handler, Matchers::Equals( str ) ); - } - -} // namespace Catch - - - - -#include - -namespace Catch { - namespace Detail { - - bool CaseInsensitiveLess::operator()( StringRef lhs, - StringRef rhs ) const { - return std::lexicographical_compare( - lhs.begin(), lhs.end(), - rhs.begin(), rhs.end(), - []( char l, char r ) { return toLower( l ) < toLower( r ); } ); - } - - bool - CaseInsensitiveEqualTo::operator()( StringRef lhs, - StringRef rhs ) const { - return std::equal( - lhs.begin(), lhs.end(), - rhs.begin(), rhs.end(), - []( char l, char r ) { return toLower( l ) == toLower( r ); } ); - } - - } // namespace Detail -} // namespace Catch - - - - -#include -#include - -namespace { - bool isOptPrefix( char c ) { - return c == '-' -#ifdef CATCH_PLATFORM_WINDOWS - || c == '/' -#endif - ; - } - - Catch::StringRef normaliseOpt( Catch::StringRef optName ) { - if ( optName[0] == '-' -#if defined(CATCH_PLATFORM_WINDOWS) - || optName[0] == '/' -#endif - ) { - return optName.substr( 1, optName.size() ); - } - - return optName; - } - - static size_t find_first_separator(Catch::StringRef sr) { - auto is_separator = []( char c ) { - return c == ' ' || c == ':' || c == '='; - }; - size_t pos = 0; - while (pos < sr.size()) { - if (is_separator(sr[pos])) { return pos; } - ++pos; - } - - return Catch::StringRef::npos; - } - -} // namespace - -namespace Catch { - namespace Clara { - namespace Detail { - - void TokenStream::loadBuffer() { - m_tokenBuffer.clear(); - - // Skip any empty strings - while ( it != itEnd && it->empty() ) { - ++it; - } - - if ( it != itEnd ) { - StringRef next = *it; - if ( isOptPrefix( next[0] ) ) { - auto delimiterPos = find_first_separator(next); - if ( delimiterPos != StringRef::npos ) { - m_tokenBuffer.push_back( - { TokenType::Option, - next.substr( 0, delimiterPos ) } ); - m_tokenBuffer.push_back( - { TokenType::Argument, - next.substr( delimiterPos + 1, next.size() ) } ); - } else { - if ( next.size() > 1 && next[1] != '-' && next.size() > 2 ) { - // Combined short args, e.g. "-ab" for "-a -b" - for ( size_t i = 1; i < next.size(); ++i ) { - m_tokenBuffer.push_back( - { TokenType::Option, - next.substr( i, 1 ) } ); - } - } else { - m_tokenBuffer.push_back( - { TokenType::Option, next } ); - } - } - } else { - m_tokenBuffer.push_back( - { TokenType::Argument, next } ); - } - } - } - - TokenStream::TokenStream( Args const& args ): - TokenStream( args.m_args.begin(), args.m_args.end() ) {} - - TokenStream::TokenStream( Iterator it_, Iterator itEnd_ ): - it( it_ ), itEnd( itEnd_ ) { - loadBuffer(); - } - - TokenStream& TokenStream::operator++() { - if ( m_tokenBuffer.size() >= 2 ) { - m_tokenBuffer.erase( m_tokenBuffer.begin() ); - } else { - if ( it != itEnd ) - ++it; - loadBuffer(); - } - return *this; - } - - ParserResult convertInto( std::string const& source, - std::string& target ) { - target = source; - return ParserResult::ok( ParseResultType::Matched ); - } - - ParserResult convertInto( std::string const& source, - bool& target ) { - std::string srcLC = toLower( source ); - - 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 ); - } - - size_t ParserBase::cardinality() const { return 1; } - - InternalParseResult ParserBase::parse( Args const& args ) const { - return parse( static_cast(args.exeName()), TokenStream( args ) ); - } - - ParseState::ParseState( ParseResultType type, - TokenStream remainingTokens ): - m_type( type ), m_remainingTokens( CATCH_MOVE(remainingTokens) ) {} - - ParserResult BoundFlagRef::setFlag( bool flag ) { - m_ref = flag; - return ParserResult::ok( ParseResultType::Matched ); - } - - ResultBase::~ResultBase() = default; - - bool BoundRef::isContainer() const { return false; } - - bool BoundRef::isFlag() const { return false; } - - bool BoundFlagRefBase::isFlag() const { return true; } - -} // namespace Detail - - Detail::InternalParseResult Arg::parse(std::string const&, - Detail::TokenStream tokens) const { - auto validationResult = validate(); - if (!validationResult) - return Detail::InternalParseResult(validationResult); - - auto token = *tokens; - if (token.type != Detail::TokenType::Argument) - return Detail::InternalParseResult::ok(Detail::ParseState( - ParseResultType::NoMatch, CATCH_MOVE(tokens))); - - assert(!m_ref->isFlag()); - auto valueRef = - static_cast(m_ref.get()); - - auto result = valueRef->setValue(static_cast(token.token)); - if ( !result ) - return Detail::InternalParseResult( result ); - else - return Detail::InternalParseResult::ok( - Detail::ParseState( ParseResultType::Matched, - CATCH_MOVE( ++tokens ) ) ); - } - - Opt::Opt(bool& ref) : - ParserRefImpl(std::make_shared(ref)) {} - - Detail::HelpColumns Opt::getHelpColumns() const { - ReusableStringStream 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 }; - } - - bool Opt::isMatch(StringRef optToken) const { - auto normalisedToken = normaliseOpt(optToken); - for (auto const& name : m_optNames) { - if (normaliseOpt(name) == normalisedToken) - return true; - } - return false; - } - - Detail::InternalParseResult Opt::parse(std::string const&, - Detail::TokenStream tokens) const { - auto validationResult = validate(); - if (!validationResult) - return Detail::InternalParseResult(validationResult); - - if (tokens && - tokens->type == Detail::TokenType::Option) { - auto const& token = *tokens; - if (isMatch(token.token)) { - if (m_ref->isFlag()) { - auto flagRef = - static_cast( - m_ref.get()); - auto result = flagRef->setFlag(true); - if (!result) - return Detail::InternalParseResult(result); - if (result.value() == - ParseResultType::ShortCircuitAll) - return Detail::InternalParseResult::ok(Detail::ParseState( - result.value(), CATCH_MOVE(tokens))); - } else { - auto valueRef = - static_cast( - m_ref.get()); - ++tokens; - if (!tokens) - return Detail::InternalParseResult::runtimeError( - "Expected argument following " + - token.token); - auto const& argToken = *tokens; - if (argToken.type != Detail::TokenType::Argument) - return Detail::InternalParseResult::runtimeError( - "Expected argument following " + - token.token); - const auto result = valueRef->setValue(static_cast(argToken.token)); - if (!result) - return Detail::InternalParseResult(result); - if (result.value() == - ParseResultType::ShortCircuitAll) - return Detail::InternalParseResult::ok(Detail::ParseState( - result.value(), CATCH_MOVE(tokens))); - } - return Detail::InternalParseResult::ok(Detail::ParseState( - ParseResultType::Matched, CATCH_MOVE(++tokens))); - } - } - return Detail::InternalParseResult::ok( - Detail::ParseState(ParseResultType::NoMatch, CATCH_MOVE(tokens))); - } - - Detail::Result Opt::validate() const { - if (m_optNames.empty()) - return Detail::Result::logicError("No options supplied to Opt"); - for (auto const& name : m_optNames) { - if (name.empty()) - return Detail::Result::logicError( - "Option name cannot be empty"); -#ifdef CATCH_PLATFORM_WINDOWS - if (name[0] != '-' && name[0] != '/') - return Detail::Result::logicError( - "Option name must begin with '-' or '/'"); -#else - if (name[0] != '-') - return Detail::Result::logicError( - "Option name must begin with '-'"); -#endif - } - return ParserRefImpl::validate(); - } - - ExeName::ExeName() : - m_name(std::make_shared("")) {} - - ExeName::ExeName(std::string& ref) : ExeName() { - m_ref = std::make_shared>(ref); - } - - Detail::InternalParseResult - ExeName::parse(std::string const&, - Detail::TokenStream tokens) const { - return Detail::InternalParseResult::ok( - Detail::ParseState(ParseResultType::NoMatch, CATCH_MOVE(tokens))); - } - - ParserResult ExeName::set(std::string const& newName) { - 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); - } - - - - - Parser& Parser::operator|=( Parser const& other ) { - 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; - } - - std::vector Parser::getHelpColumns() const { - std::vector cols; - cols.reserve( m_options.size() ); - for ( auto const& o : m_options ) { - cols.push_back(o.getHelpColumns()); - } - return cols; - } - - void Parser::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:\n"; - } - - auto rows = getHelpColumns(); - size_t consoleWidth = CATCH_CONFIG_CONSOLE_WIDTH; - size_t optWidth = 0; - for ( auto const& cols : rows ) - optWidth = ( std::max )( optWidth, cols.left.size() + 2 ); - - optWidth = ( std::min )( optWidth, consoleWidth / 2 ); - - for ( auto& cols : rows ) { - auto row = TextFlow::Column( CATCH_MOVE(cols.left) ) - .width( optWidth ) - .indent( 2 ) + - TextFlow::Spacer( 4 ) + - TextFlow::Column( static_cast(cols.descriptions) ) - .width( consoleWidth - 7 - optWidth ); - os << row << '\n'; - } - } - - Detail::Result Parser::validate() const { - 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 Detail::Result::ok(); - } - - Detail::InternalParseResult - Parser::parse( std::string const& exeName, - Detail::TokenStream tokens ) const { - - struct ParserInfo { - ParserBase const* parser = nullptr; - size_t count = 0; - }; - std::vector parseInfos; - parseInfos.reserve( m_options.size() + m_args.size() ); - for ( auto const& opt : m_options ) { - parseInfos.push_back( { &opt, 0 } ); - } - for ( auto const& arg : m_args ) { - parseInfos.push_back( { &arg, 0 } ); - } - - m_exeName.set( exeName ); - - auto result = Detail::InternalParseResult::ok( - Detail::ParseState( ParseResultType::NoMatch, CATCH_MOVE(tokens) ) ); - while ( result.value().remainingTokens() ) { - bool tokenParsed = false; - - for ( auto& parseInfo : parseInfos ) { - if ( parseInfo.parser->cardinality() == 0 || - parseInfo.count < parseInfo.parser->cardinality() ) { - result = parseInfo.parser->parse( - exeName, CATCH_MOVE(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 Detail::InternalParseResult::runtimeError( - "Unrecognised token: " + - result.value().remainingTokens()->token ); - } - // !TBD Check missing required options - return result; - } - - Args::Args(int argc, char const* const* argv) : - m_exeName(argv[0]), m_args(argv + 1, argv + argc) {} - - Args::Args(std::initializer_list args) : - m_exeName(*args.begin()), - m_args(args.begin() + 1, args.end()) {} - - - Help::Help( bool& showHelpFlag ): - Opt( [&]( bool flag ) { - showHelpFlag = flag; - return ParserResult::ok( ParseResultType::ShortCircuitAll ); - } ) { - static_cast ( *this )( - "display usage information" )["-?"]["-h"]["--help"] - .optional(); - } - - } // namespace Clara -} // namespace Catch - - - - -#include -#include - -namespace Catch { - - Clara::Parser makeCommandLineParser( ConfigData& config ) { - - using namespace Clara; - - auto const setWarning = [&]( std::string const& warning ) { - if ( warning == "NoAssertions" ) { - config.warnings = static_cast(config.warnings | WarnAbout::NoAssertions); - return ParserResult::ok( ParseResultType::Matched ); - } else if ( warning == "UnmatchedTestSpec" ) { - config.warnings = static_cast(config.warnings | WarnAbout::UnmatchedTestSpec); - return ParserResult::ok( ParseResultType::Matched ); - } - - return ParserResult ::runtimeError( - "Unrecognised warning option: '" + warning + '\'' ); - }; - 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 = '"' + CATCH_MOVE(line) + '"'; - config.testsOrTags.push_back( line ); - config.testsOrTags.emplace_back( "," ); - } - } - //Remove comma in the end - if(!config.testsOrTags.empty()) - config.testsOrTags.erase( config.testsOrTags.end()-1 ); - - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setTestOrder = [&]( std::string const& order ) { - if( startsWith( "declared", order ) ) - config.runOrder = TestRunOrder::Declared; - else if( startsWith( "lexical", order ) ) - config.runOrder = TestRunOrder::LexicographicallySorted; - else if( startsWith( "random", order ) ) - config.runOrder = TestRunOrder::Randomized; - else - return ParserResult::runtimeError( "Unrecognised ordering: '" + order + '\'' ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setRngSeed = [&]( std::string const& seed ) { - if( seed == "time" ) { - config.rngSeed = generateRandomSeed(GenerateFrom::Time); - return ParserResult::ok(ParseResultType::Matched); - } else if (seed == "random-device") { - config.rngSeed = generateRandomSeed(GenerateFrom::RandomDevice); - return ParserResult::ok(ParseResultType::Matched); - } - - // TODO: ideally we should be parsing uint32_t directly - // fix this later when we add new parse overload - auto parsedSeed = parseUInt( seed, 0 ); - if ( !parsedSeed ) { - return ParserResult::runtimeError( "Could not parse '" + seed + "' as seed" ); - } - config.rngSeed = *parsedSeed; - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setDefaultColourMode = [&]( std::string const& colourMode ) { - Optional maybeMode = Catch::Detail::stringToColourMode(toLower( colourMode )); - if ( !maybeMode ) { - return ParserResult::runtimeError( - "colour mode must be one of: default, ansi, win32, " - "or none. '" + - colourMode + "' is not recognised" ); - } - auto mode = *maybeMode; - if ( !isColourImplAvailable( mode ) ) { - return ParserResult::runtimeError( - "colour mode '" + colourMode + - "' is not supported in this binary" ); - } - config.defaultColourMode = mode; - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setWaitForKeypress = [&]( std::string const& keypress ) { - auto keypressLc = toLower( keypress ); - if (keypressLc == "never") - config.waitForKeypress = WaitForKeypress::Never; - else if( keypressLc == "start" ) - config.waitForKeypress = WaitForKeypress::BeforeStart; - else if( keypressLc == "exit" ) - config.waitForKeypress = WaitForKeypress::BeforeExit; - else if( keypressLc == "both" ) - config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; - else - return ParserResult::runtimeError( "keypress argument must be one of: never, start, exit or both. '" + keypress + "' not recognised" ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setVerbosity = [&]( std::string const& verbosity ) { - auto lcVerbosity = toLower( verbosity ); - if( lcVerbosity == "quiet" ) - config.verbosity = Verbosity::Quiet; - else if( lcVerbosity == "normal" ) - config.verbosity = Verbosity::Normal; - else if( lcVerbosity == "high" ) - config.verbosity = Verbosity::High; - else - return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + '\'' ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setReporter = [&]( std::string const& userReporterSpec ) { - if ( userReporterSpec.empty() ) { - return ParserResult::runtimeError( "Received empty reporter spec." ); - } - - Optional parsed = - parseReporterSpec( userReporterSpec ); - if ( !parsed ) { - return ParserResult::runtimeError( - "Could not parse reporter spec '" + userReporterSpec + - "'" ); - } - - auto const& reporterSpec = *parsed; - - auto const& factories = - getRegistryHub().getReporterRegistry().getFactories(); - auto result = factories.find( reporterSpec.name() ); - - if ( result == factories.end() ) { - return ParserResult::runtimeError( - "Unrecognized reporter, '" + reporterSpec.name() + - "'. Check available with --list-reporters" ); - } - - - const bool hadOutputFile = reporterSpec.outputFile().some(); - config.reporterSpecifications.push_back( CATCH_MOVE( *parsed ) ); - // It would be enough to check this only once at the very end, but - // there is not a place where we could call this check, so do it - // every time it could fail. For valid inputs, this is still called - // at most once. - if (!hadOutputFile) { - int n_reporters_without_file = 0; - for (auto const& spec : config.reporterSpecifications) { - if (spec.outputFile().none()) { - n_reporters_without_file++; - } - } - if (n_reporters_without_file > 1) { - return ParserResult::runtimeError( "Only one reporter may have unspecified output file." ); - } - } - - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setShardCount = [&]( std::string const& shardCount ) { - auto parsedCount = parseUInt( shardCount ); - if ( !parsedCount ) { - return ParserResult::runtimeError( - "Could not parse '" + shardCount + "' as shard count" ); - } - if ( *parsedCount == 0 ) { - return ParserResult::runtimeError( - "Shard count must be positive" ); - } - config.shardCount = *parsedCount; - return ParserResult::ok( ParseResultType::Matched ); - }; - - auto const setShardIndex = [&](std::string const& shardIndex) { - auto parsedIndex = parseUInt( shardIndex ); - if ( !parsedIndex ) { - return ParserResult::runtimeError( - "Could not parse '" + shardIndex + "' as shard index" ); - } - config.shardIndex = *parsedIndex; - return ParserResult::ok( ParseResultType::Matched ); - }; - - auto cli - = ExeName( config.processName ) - | Help( config.showHelp ) - | 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.defaultOutputFilename, "filename" ) - ["-o"]["--out"] - ( "default output filename" ) - | Opt( accept_many, setReporter, "name[::key=value]*" ) - ["-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( accept_many, setWarning, "warning name" ) - ["-w"]["--warn"] - ( "enable warnings" ) - | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) - ["-d"]["--durations"] - ( "show test durations" ) - | Opt( config.minDuration, "seconds" ) - ["-D"]["--min-duration"] - ( "show test durations for tests taking at least the given number of seconds" ) - | Opt( loadTestNamesFromFile, "filename" ) - ["-f"]["--input-file"] - ( "load test names to run from a file" ) - | Opt( config.filenamesAsTags ) - ["-#"]["--filenames-as-tags"] - ( "adds a tag for the filename" ) - | Opt( config.sectionsToRun, "section name" ) - ["-c"]["--section"] - ( "specify section to run" ) - | Opt( setVerbosity, "quiet|normal|high" ) - ["-v"]["--verbosity"] - ( "set output verbosity" ) - | Opt( config.listTests ) - ["--list-tests"] - ( "list all/matching test cases" ) - | Opt( config.listTags ) - ["--list-tags"] - ( "list all/matching tags" ) - | Opt( config.listReporters ) - ["--list-reporters"] - ( "list all available reporters" ) - | Opt( config.listListeners ) - ["--list-listeners"] - ( "list all listeners" ) - | Opt( setTestOrder, "decl|lex|rand" ) - ["--order"] - ( "test case order (defaults to decl)" ) - | Opt( setRngSeed, "'time'|'random-device'|number" ) - ["--rng-seed"] - ( "set a specific seed for random numbers" ) - | Opt( setDefaultColourMode, "ansi|win32|none|default" ) - ["--colour-mode"] - ( "what color mode should be used as default" ) - | Opt( config.libIdentify ) - ["--libidentify"] - ( "report name and version according to libidentify standard" ) - | Opt( setWaitForKeypress, "never|start|exit|both" ) - ["--wait-for-keypress"] - ( "waits for a keypress before exiting" ) - | Opt( config.skipBenchmarks) - ["--skip-benchmarks"] - ( "disable running benchmarks") - | Opt( config.benchmarkSamples, "samples" ) - ["--benchmark-samples"] - ( "number of samples to collect (default: 100)" ) - | Opt( config.benchmarkResamples, "resamples" ) - ["--benchmark-resamples"] - ( "number of resamples for the bootstrap (default: 100000)" ) - | Opt( config.benchmarkConfidenceInterval, "confidence interval" ) - ["--benchmark-confidence-interval"] - ( "confidence interval for the bootstrap (between 0 and 1, default: 0.95)" ) - | Opt( config.benchmarkNoAnalysis ) - ["--benchmark-no-analysis"] - ( "perform only measurements; do not perform any analysis" ) - | Opt( config.benchmarkWarmupTime, "benchmarkWarmupTime" ) - ["--benchmark-warmup-time"] - ( "amount of time in milliseconds spent on warming up each test (default: 100)" ) - | Opt( setShardCount, "shard count" ) - ["--shard-count"] - ( "split the tests to execute into this many groups" ) - | Opt( setShardIndex, "shard index" ) - ["--shard-index"] - ( "index of the group of tests to execute (see --shard-count)" ) - | Opt( config.allowZeroTests ) - ["--allow-running-no-tests"] - ( "Treat 'No tests run' as a success" ) - | Arg( config.testsOrTags, "test name|pattern|tags" ) - ( "which test or tests to use" ); - - return cli; - } - -} // end namespace Catch - - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - - - -#include -#include -#include - -namespace Catch { - - ColourImpl::~ColourImpl() = default; - - ColourImpl::ColourGuard ColourImpl::guardColour( Colour::Code colourCode ) { - return ColourGuard(colourCode, this ); - } - - void ColourImpl::ColourGuard::engageImpl( std::ostream& stream ) { - assert( &stream == &m_colourImpl->m_stream->stream() && - "Engaging colour guard for different stream than used by the " - "parent colour implementation" ); - static_cast( stream ); - - m_engaged = true; - m_colourImpl->use( m_code ); - } - - ColourImpl::ColourGuard::ColourGuard( Colour::Code code, - ColourImpl const* colour ): - m_colourImpl( colour ), m_code( code ) { - } - ColourImpl::ColourGuard::ColourGuard( ColourGuard&& rhs ) noexcept: - m_colourImpl( rhs.m_colourImpl ), - m_code( rhs.m_code ), - m_engaged( rhs.m_engaged ) { - rhs.m_engaged = false; - } - ColourImpl::ColourGuard& - ColourImpl::ColourGuard::operator=( ColourGuard&& rhs ) noexcept { - using std::swap; - swap( m_colourImpl, rhs.m_colourImpl ); - swap( m_code, rhs.m_code ); - swap( m_engaged, rhs.m_engaged ); - - return *this; - } - ColourImpl::ColourGuard::~ColourGuard() { - if ( m_engaged ) { - m_colourImpl->use( Colour::None ); - } - } - - ColourImpl::ColourGuard& - ColourImpl::ColourGuard::engage( std::ostream& stream ) & { - engageImpl( stream ); - return *this; - } - - ColourImpl::ColourGuard&& - ColourImpl::ColourGuard::engage( std::ostream& stream ) && { - engageImpl( stream ); - return CATCH_MOVE(*this); - } - - namespace { - //! A do-nothing implementation of colour, used as fallback for unknown - //! platforms, and when the user asks to deactivate all colours. - class NoColourImpl final : public ColourImpl { - public: - NoColourImpl( IStream* stream ): ColourImpl( stream ) {} - - private: - void use( Colour::Code ) const override {} - }; - } // namespace - - -} // namespace Catch - - -#if defined ( CATCH_CONFIG_COLOUR_WIN32 ) ///////////////////////////////////////// - -namespace Catch { -namespace { - - class Win32ColourImpl final : public ColourImpl { - public: - Win32ColourImpl(IStream* stream): - ColourImpl(stream) { - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), - &csbiInfo ); - originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); - originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); - } - - static bool useImplementationForStream(IStream const& stream) { - // Win32 text colour APIs can only be used on console streams - // We cannot check that the output hasn't been redirected, - // so we just check that the original stream is console stream. - return stream.isConsole(); - } - - private: - void use( Colour::Code _colourCode ) const 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::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); - - case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); - - default: - CATCH_ERROR( "Unknown colour requested" ); - } - } - - void setTextAttribute( WORD _textAttribute ) const { - SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), - _textAttribute | - originalBackgroundAttributes ); - } - WORD originalForegroundAttributes; - WORD originalBackgroundAttributes; - }; - -} // end anon namespace -} // end namespace Catch - -#endif // Windows/ ANSI/ None - - -#if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC ) -# define CATCH_INTERNAL_HAS_ISATTY -# include -#endif - -namespace Catch { -namespace { - - class ANSIColourImpl final : public ColourImpl { - public: - ANSIColourImpl( IStream* stream ): ColourImpl( stream ) {} - - static bool useImplementationForStream(IStream const& stream) { - // This is kinda messy due to trying to support a bunch of - // different platforms at once. - // The basic idea is that if we are asked to do autodetection (as - // opposed to being told to use posixy colours outright), then we - // only want to use the colours if we are writing to console. - // However, console might be redirected, so we make an attempt at - // checking for that on platforms where we know how to do that. - bool useColour = stream.isConsole(); -#if defined( CATCH_INTERNAL_HAS_ISATTY ) && \ - !( defined( __DJGPP__ ) && defined( __STRICT_ANSI__ ) ) - ErrnoGuard _; // for isatty - useColour = useColour && isatty( STDOUT_FILENO ); -# endif -# if defined( CATCH_PLATFORM_MAC ) || defined( CATCH_PLATFORM_IPHONE ) - useColour = useColour && !isDebuggerActive(); -# endif - - return useColour; - } - - private: - void use( Colour::Code _colourCode ) const override { - auto setColour = [&out = - m_stream->stream()]( char const* escapeCode ) { - // The escape sequence must be flushed to console, otherwise - // if stdin and stderr are intermixed, we'd get accidentally - // coloured output. - out << '\033' << escapeCode << std::flush; - }; - switch( _colourCode ) { - case Colour::None: - case Colour::White: return setColour( "[0m" ); - case Colour::Red: return setColour( "[0;31m" ); - case Colour::Green: return setColour( "[0;32m" ); - case Colour::Blue: return setColour( "[0;34m" ); - case Colour::Cyan: return setColour( "[0;36m" ); - case Colour::Yellow: return setColour( "[0;33m" ); - case Colour::Grey: return setColour( "[1;30m" ); - - case Colour::LightGrey: return setColour( "[0;37m" ); - case Colour::BrightRed: return setColour( "[1;31m" ); - case Colour::BrightGreen: return setColour( "[1;32m" ); - case Colour::BrightWhite: return setColour( "[1;37m" ); - case Colour::BrightYellow: return setColour( "[1;33m" ); - - case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); - default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); - } - } - }; - -} // end anon namespace -} // end namespace Catch - -namespace Catch { - - Detail::unique_ptr makeColourImpl( ColourMode colourSelection, - IStream* stream ) { -#if defined( CATCH_CONFIG_COLOUR_WIN32 ) - if ( colourSelection == ColourMode::Win32 ) { - return Detail::make_unique( stream ); - } -#endif - if ( colourSelection == ColourMode::ANSI ) { - return Detail::make_unique( stream ); - } - if ( colourSelection == ColourMode::None ) { - return Detail::make_unique( stream ); - } - - if ( colourSelection == ColourMode::PlatformDefault) { -#if defined( CATCH_CONFIG_COLOUR_WIN32 ) - if ( Win32ColourImpl::useImplementationForStream( *stream ) ) { - return Detail::make_unique( stream ); - } -#endif - if ( ANSIColourImpl::useImplementationForStream( *stream ) ) { - return Detail::make_unique( stream ); - } - return Detail::make_unique( stream ); - } - - CATCH_ERROR( "Could not create colour impl for selection " << static_cast(colourSelection) ); - } - - bool isColourImplAvailable( ColourMode colourSelection ) { - switch ( colourSelection ) { -#if defined( CATCH_CONFIG_COLOUR_WIN32 ) - case ColourMode::Win32: -#endif - case ColourMode::ANSI: - case ColourMode::None: - case ColourMode::PlatformDefault: - return true; - default: - return false; - } - } - - -} // end namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - - - - -namespace Catch { - - Context* Context::currentContext = nullptr; - - void cleanUpContext() { - delete Context::currentContext; - Context::currentContext = nullptr; - } - void Context::createContext() { - currentContext = new Context(); - } - - Context& getCurrentMutableContext() { - if ( !Context::currentContext ) { Context::createContext(); } - // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) - return *Context::currentContext; - } - - SimplePcg32& sharedRng() { - static SimplePcg32 s_rng; - return s_rng; - } - -} - - - - - -#include - -#if defined(CATCH_CONFIG_ANDROID_LOGWRITE) -#include - - namespace Catch { - void writeToDebugConsole( std::string const& text ) { - __android_log_write( ANDROID_LOG_DEBUG, "Catch", text.c_str() ); - } - } - -#elif defined(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 - - - -#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) - -# include -# include -# include -# include -# include - -#ifdef __apple_build_version__ - // These headers will only compile with AppleClang (XCode) - // For other compilers (Clang, GCC, ... ) we need to exclude them -# include -#endif - - namespace Catch { - #ifdef __apple_build_version__ - // The following function is taken directly from the following technical note: - // https://developer.apple.com/library/archive/qa/qa1361/_index.html - - // Returns true if the current process is being debugged (either - // running under the debugger or has a debugger attached post facto). - bool isDebuggerActive(){ - int mib[4]; - struct kinfo_proc info; - std::size_t size; - - // Initialize the flags so that, if sysctl fails for some bizarre - // reason, we get a predictable result. - - info.kp_proc.p_flag = 0; - - // 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\n" << std::flush; - return false; - } - - // We're being debugged if the P_TRACED flag is set. - - return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); - } - #else - bool isDebuggerActive() { - // We need to find another way to determine this for non-appleclang compilers on macOS - return false; - } - #endif - } // namespace Catch - -#elif defined(CATCH_PLATFORM_LINUX) - #include - #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 - - - - -namespace Catch { - - void ITransientExpression::streamReconstructedExpression( - std::ostream& os ) const { - // We can't make this function pure virtual to keep ITransientExpression - // constexpr, so we write error message instead - os << "Some class derived from ITransientExpression without overriding streamReconstructedExpression"; - } - - 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; - } -} - - - -#include - - -namespace Catch { -#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) - [[noreturn]] - void throw_exception(std::exception const& e) { - Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" - << "The message was: " << e.what() << '\n'; - std::terminate(); - } -#endif - - [[noreturn]] - void throw_logic_error(std::string const& msg) { - throw_exception(std::logic_error(msg)); - } - - [[noreturn]] - void throw_domain_error(std::string const& msg) { - throw_exception(std::domain_error(msg)); - } - - [[noreturn]] - void throw_runtime_error(std::string const& msg) { - throw_exception(std::runtime_error(msg)); - } - - - -} // namespace Catch; - - - -#include - -namespace Catch { - - IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() = default; - - namespace Detail { - - namespace { - // Extracts the actual name part of an enum instance - // In other words, it returns the Blue part of Bikeshed::Colour::Blue - StringRef extractInstanceName(StringRef enumInstance) { - // Find last occurrence of ":" - size_t name_start = enumInstance.size(); - while (name_start > 0 && enumInstance[name_start - 1] != ':') { - --name_start; - } - return enumInstance.substr(name_start, enumInstance.size() - name_start); - } - } - - std::vector parseEnums( StringRef enums ) { - auto enumValues = splitStringRef( enums, ',' ); - std::vector parsed; - parsed.reserve( enumValues.size() ); - for( auto const& enumValue : enumValues ) { - parsed.push_back(trim(extractInstanceName(enumValue))); - } - return parsed; - } - - EnumInfo::~EnumInfo() = default; - - StringRef EnumInfo::lookup( int value ) const { - for( auto const& valueToName : m_values ) { - if( valueToName.first == value ) - return valueToName.second; - } - return "{** unexpected enum value **}"_sr; - } - - Catch::Detail::unique_ptr makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector const& values ) { - auto enumInfo = Catch::Detail::make_unique(); - enumInfo->m_name = enumName; - enumInfo->m_values.reserve( values.size() ); - - const auto valueNames = Catch::Detail::parseEnums( allValueNames ); - assert( valueNames.size() == values.size() ); - std::size_t i = 0; - for( auto value : values ) - enumInfo->m_values.emplace_back(value, valueNames[i++]); - - return enumInfo; - } - - EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector const& values ) { - m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values)); - return *m_enumInfos.back(); - } - - } // Detail -} // Catch - - - - - -#include - -namespace Catch { - ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} - ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } -} - - - -#include - -namespace Catch { - -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - namespace { - static std::string tryTranslators( - std::vector< - Detail::unique_ptr> const& translators ) { - if ( translators.empty() ) { - std::rethrow_exception( std::current_exception() ); - } else { - return translators[0]->translate( translators.begin() + 1, - translators.end() ); - } - } - - } -#endif //!defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - - ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() = default; - - void ExceptionTranslatorRegistry::registerTranslator( Detail::unique_ptr&& translator ) { - m_translators.push_back( CATCH_MOVE( translator ) ); - } - -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - std::string ExceptionTranslatorRegistry::translateActiveException() const { - // Compiling a mixed mode project with MSVC means that CLR - // exceptions will be caught in (...) as well. However, these do - // do not fill-in std::current_exception and thus lead to crash - // when attempting rethrow. - // /EHa switch also causes structured exceptions to be caught - // here, but they fill-in current_exception properly, so - // at worst the output should be a little weird, instead of - // causing a crash. - if ( std::current_exception() == nullptr ) { - return "Non C++ exception. Possibly a CLR exception."; - } - - // First we try user-registered translators. If none of them can - // handle the exception, it will be rethrown handled by our defaults. - try { - return tryTranslators(m_translators); - } - // To avoid having to handle TFE explicitly everywhere, we just - // rethrow it so that it goes back up the caller. - catch( TestFailureException& ) { - std::rethrow_exception(std::current_exception()); - } - catch( TestSkipException& ) { - std::rethrow_exception(std::current_exception()); - } - catch( std::exception const& ex ) { - return ex.what(); - } - catch( std::string const& msg ) { - return msg; - } - catch( const char* msg ) { - return msg; - } - catch(...) { - return "Unknown exception"; - } - } - -#else // ^^ Exceptions are enabled // Exceptions are disabled vv - std::string ExceptionTranslatorRegistry::translateActiveException() const { - CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); - } -#endif - -} - - - -/** \file - * This file provides platform specific implementations of FatalConditionHandler - * - * This means that there is a lot of conditional compilation, and platform - * specific code. Currently, Catch2 supports a dummy handler (if no - * handler is desired), and 2 platform specific handlers: - * * Windows' SEH - * * POSIX signals - * - * Consequently, various pieces of code below are compiled if either of - * the platform specific handlers is enabled, or if none of them are - * enabled. It is assumed that both cannot be enabled at the same time, - * and doing so should cause a compilation error. - * - * If another platform specific handler is added, the compile guards - * below will need to be updated taking these assumptions into account. - */ - - - -#include - -#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS ) - -namespace Catch { - - // If neither SEH nor signal handling is required, the handler impls - // do not have to do anything, and can be empty. - void FatalConditionHandler::engage_platform() {} - void FatalConditionHandler::disengage_platform() noexcept {} - FatalConditionHandler::FatalConditionHandler() = default; - FatalConditionHandler::~FatalConditionHandler() = default; - -} // end namespace Catch - -#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS - -#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS ) -#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" -#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS - -#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) - -namespace { - //! Signals fatal error message to the run context - void reportFatal( char const * const message ) { - Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); - } - - //! Minimal size Catch2 needs for its own fatal error handling. - //! Picked empirically, so it might not be sufficient on all - //! platforms, and for all configurations. - constexpr std::size_t minStackSizeForErrors = 32 * 1024; -} // end unnamed namespace - -#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS - -#if defined( CATCH_CONFIG_WINDOWS_SEH ) - -namespace Catch { - - struct SignalDefs { DWORD id; const char* name; }; - - // There is no 1-1 mapping between signals and windows exceptions. - // Windows can easily distinguish between SO and SigSegV, - // but SigInt, SigTerm, etc are handled differently. - static SignalDefs signalDefs[] = { - { 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" }, - }; - - static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { - for (auto const& def : signalDefs) { - if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { - reportFatal(def.name); - } - } - // If its not an exception we care about, pass it along. - // This stops us from eating debugger breaks etc. - return EXCEPTION_CONTINUE_SEARCH; - } - - // Since we do not support multiple instantiations, we put these - // into global variables and rely on cleaning them up in outlined - // constructors/destructors - static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; - - - // For MSVC, we reserve part of the stack memory for handling - // memory overflow structured exception. - FatalConditionHandler::FatalConditionHandler() { - ULONG guaranteeSize = static_cast(minStackSizeForErrors); - if (!SetThreadStackGuarantee(&guaranteeSize)) { - // We do not want to fully error out, because needing - // the stack reserve should be rare enough anyway. - Catch::cerr() - << "Failed to reserve piece of stack." - << " Stack overflows will not be reported successfully."; - } - } - - // We do not attempt to unset the stack guarantee, because - // Windows does not support lowering the stack size guarantee. - FatalConditionHandler::~FatalConditionHandler() = default; - - - void FatalConditionHandler::engage_platform() { - // Register as a the top level exception filter. - previousTopLevelExceptionFilter = SetUnhandledExceptionFilter(topLevelExceptionFilter); - } - - void FatalConditionHandler::disengage_platform() noexcept { - if (SetUnhandledExceptionFilter(previousTopLevelExceptionFilter) != topLevelExceptionFilter) { - Catch::cerr() - << "Unexpected SEH unhandled exception filter on disengage." - << " The filter was restored, but might be rolled back unexpectedly."; - } - previousTopLevelExceptionFilter = nullptr; - } - -} // end namespace Catch - -#endif // CATCH_CONFIG_WINDOWS_SEH - -#if defined( CATCH_CONFIG_POSIX_SIGNALS ) - -#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" } - }; - -// Older GCCs trigger -Wmissing-field-initializers for T foo = {} -// which is zero initialization, but not explicit. We want to avoid -// that. -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - - static char* altStackMem = nullptr; - static std::size_t altStackSize = 0; - static stack_t oldSigStack{}; - static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; - - static void restorePreviousSignalHandlers() noexcept { - // We set signal handlers back to the previous ones. Hopefully - // nobody overwrote them in the meantime, and doesn't expect - // their signal handlers to live past ours given that they - // installed them after ours.. - for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { - sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - } - - static void handleSignal( int sig ) { - char const * name = ""; - for (auto const& def : signalDefs) { - if (sig == def.id) { - name = def.name; - break; - } - } - // We need to restore previous signal handlers and let them do - // their thing, so that the users can have the debugger break - // when a signal is raised, and so on. - restorePreviousSignalHandlers(); - reportFatal( name ); - raise( sig ); - } - - FatalConditionHandler::FatalConditionHandler() { - assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); - if (altStackSize == 0) { - altStackSize = std::max(static_cast(SIGSTKSZ), minStackSizeForErrors); - } - altStackMem = new char[altStackSize](); - } - - FatalConditionHandler::~FatalConditionHandler() { - delete[] altStackMem; - // We signal that another instance can be constructed by zeroing - // out the pointer. - altStackMem = nullptr; - } - - void FatalConditionHandler::engage_platform() { - stack_t sigStack; - sigStack.ss_sp = altStackMem; - sigStack.ss_size = altStackSize; - sigStack.ss_flags = 0; - sigaltstack(&sigStack, &oldSigStack); - struct sigaction sa = { }; - - 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]); - } - } - -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - - - void FatalConditionHandler::disengage_platform() noexcept { - restorePreviousSignalHandlers(); - } - -} // end namespace Catch - -#endif // CATCH_CONFIG_POSIX_SIGNALS - - - - -#include - -namespace Catch { - namespace Detail { - - uint32_t convertToBits(float f) { - static_assert(sizeof(float) == sizeof(uint32_t), "Important ULP matcher assumption violated"); - uint32_t i; - std::memcpy(&i, &f, sizeof(f)); - return i; - } - - uint64_t convertToBits(double d) { - static_assert(sizeof(double) == sizeof(uint64_t), "Important ULP matcher assumption violated"); - uint64_t i; - std::memcpy(&i, &d, sizeof(d)); - return i; - } - -#if defined( __GNUC__ ) || defined( __clang__ ) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wfloat-equal" -#endif - bool directCompare( float lhs, float rhs ) { return lhs == rhs; } - bool directCompare( double lhs, double rhs ) { return lhs == rhs; } -#if defined( __GNUC__ ) || defined( __clang__ ) -# pragma GCC diagnostic pop -#endif - - - } // end namespace Detail -} // end namespace Catch - - - - - - -#include - -namespace Catch { - namespace Detail { - -#if !defined (CATCH_CONFIG_GETENV) - char const* getEnv( char const* ) { return nullptr; } -#else - - char const* getEnv( char const* varName ) { -# if defined( _MSC_VER ) -# pragma warning( push ) -# pragma warning( disable : 4996 ) // use getenv_s instead of getenv -# endif - - return std::getenv( varName ); - -# if defined( _MSC_VER ) -# pragma warning( pop ) -# endif - } -#endif -} // namespace Detail -} // namespace Catch - - - - -#include -#include -#include -#include - -namespace Catch { - - Catch::IStream::~IStream() = default; - -namespace Detail { - namespace { - template - class StreamBufImpl final : public std::streambuf { - char data[bufferSize]; - WriterF m_writer; - - public: - StreamBufImpl() { - setp( data, data + sizeof(data) ); - } - - ~StreamBufImpl() noexcept override { - 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; - } - }; - - /////////////////////////////////////////////////////////////////////////// - - struct OutputDebugWriter { - - void operator()( std::string const& str ) { - if ( !str.empty() ) { - writeToDebugConsole( str ); - } - } - }; - - /////////////////////////////////////////////////////////////////////////// - - class FileStream final : public IStream { - std::ofstream m_ofs; - public: - FileStream( std::string const& filename ) { - m_ofs.open( filename.c_str() ); - CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << '\'' ); - m_ofs << std::unitbuf; - } - public: // IStream - std::ostream& stream() override { - return m_ofs; - } - }; - - /////////////////////////////////////////////////////////////////////////// - - class CoutStream final : public IStream { - std::ostream m_os; - public: - // Store the streambuf from cout up-front because - // cout may get redirected when running tests - CoutStream() : m_os( Catch::cout().rdbuf() ) {} - - public: // IStream - std::ostream& stream() override { return m_os; } - bool isConsole() const override { return true; } - }; - - class CerrStream : public IStream { - std::ostream m_os; - - public: - // Store the streambuf from cerr up-front because - // cout may get redirected when running tests - CerrStream(): m_os( Catch::cerr().rdbuf() ) {} - - public: // IStream - std::ostream& stream() override { return m_os; } - bool isConsole() const override { return true; } - }; - - /////////////////////////////////////////////////////////////////////////// - - class DebugOutStream final : public IStream { - Detail::unique_ptr> m_streamBuf; - std::ostream m_os; - public: - DebugOutStream() - : m_streamBuf( Detail::make_unique>() ), - m_os( m_streamBuf.get() ) - {} - - public: // IStream - std::ostream& stream() override { return m_os; } - }; - - } // unnamed namespace -} // namespace Detail - - /////////////////////////////////////////////////////////////////////////// - - auto makeStream( std::string const& filename ) -> Detail::unique_ptr { - if ( filename.empty() || filename == "-" ) { - return Detail::make_unique(); - } - if( filename[0] == '%' ) { - if ( filename == "%debug" ) { - return Detail::make_unique(); - } else if ( filename == "%stderr" ) { - return Detail::make_unique(); - } else if ( filename == "%stdout" ) { - return Detail::make_unique(); - } else { - CATCH_ERROR( "Unrecognised stream: '" << filename << '\'' ); - } - } - return Detail::make_unique( filename ); - } - -} - - - -namespace Catch { - void JsonUtils::indent( std::ostream& os, std::uint64_t level ) { - for ( std::uint64_t i = 0; i < level; ++i ) { - os << " "; - } - } - void JsonUtils::appendCommaNewline( std::ostream& os, - bool& should_comma, - std::uint64_t level ) { - if ( should_comma ) { os << ','; } - should_comma = true; - os << '\n'; - indent( os, level ); - } - - JsonObjectWriter::JsonObjectWriter( std::ostream& os ): - JsonObjectWriter{ os, 0 } {} - - JsonObjectWriter::JsonObjectWriter( std::ostream& os, - std::uint64_t indent_level ): - m_os{ os }, m_indent_level{ indent_level } { - m_os << '{'; - } - JsonObjectWriter::JsonObjectWriter( JsonObjectWriter&& source ) noexcept: - m_os{ source.m_os }, - m_indent_level{ source.m_indent_level }, - m_should_comma{ source.m_should_comma }, - m_active{ source.m_active } { - source.m_active = false; - } - - JsonObjectWriter::~JsonObjectWriter() { - if ( !m_active ) { return; } - - m_os << '\n'; - JsonUtils::indent( m_os, m_indent_level ); - m_os << '}'; - } - - JsonValueWriter JsonObjectWriter::write( StringRef key ) { - JsonUtils::appendCommaNewline( - m_os, m_should_comma, m_indent_level + 1 ); - - m_os << '"' << key << "\": "; - return JsonValueWriter{ m_os, m_indent_level + 1 }; - } - - JsonArrayWriter::JsonArrayWriter( std::ostream& os ): - JsonArrayWriter{ os, 0 } {} - JsonArrayWriter::JsonArrayWriter( std::ostream& os, - std::uint64_t indent_level ): - m_os{ os }, m_indent_level{ indent_level } { - m_os << '['; - } - JsonArrayWriter::JsonArrayWriter( JsonArrayWriter&& source ) noexcept: - m_os{ source.m_os }, - m_indent_level{ source.m_indent_level }, - m_should_comma{ source.m_should_comma }, - m_active{ source.m_active } { - source.m_active = false; - } - JsonArrayWriter::~JsonArrayWriter() { - if ( !m_active ) { return; } - - m_os << '\n'; - JsonUtils::indent( m_os, m_indent_level ); - m_os << ']'; - } - - JsonObjectWriter JsonArrayWriter::writeObject() { - JsonUtils::appendCommaNewline( - m_os, m_should_comma, m_indent_level + 1 ); - return JsonObjectWriter{ m_os, m_indent_level + 1 }; - } - - JsonArrayWriter JsonArrayWriter::writeArray() { - JsonUtils::appendCommaNewline( - m_os, m_should_comma, m_indent_level + 1 ); - return JsonArrayWriter{ m_os, m_indent_level + 1 }; - } - - JsonArrayWriter& JsonArrayWriter::write( bool value ) { - return writeImpl( value ); - } - - JsonValueWriter::JsonValueWriter( std::ostream& os ): - JsonValueWriter{ os, 0 } {} - - JsonValueWriter::JsonValueWriter( std::ostream& os, - std::uint64_t indent_level ): - m_os{ os }, m_indent_level{ indent_level } {} - - JsonObjectWriter JsonValueWriter::writeObject() && { - return JsonObjectWriter{ m_os, m_indent_level }; - } - - JsonArrayWriter JsonValueWriter::writeArray() && { - return JsonArrayWriter{ m_os, m_indent_level }; - } - - void JsonValueWriter::write( Catch::StringRef value ) && { - writeImpl( value, true ); - } - - void JsonValueWriter::write( bool value ) && { - writeImpl( value ? "true"_sr : "false"_sr, false ); - } - - void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) { - if ( quote ) { m_os << '"'; } - for (char c : value) { - // Escape list taken from https://www.json.org/json-en.html, - // string definition. - // Note that while forward slash _can_ be escaped, it does - // not have to be, if JSON is not further embedded somewhere - // where forward slash is meaningful. - if ( c == '"' ) { - m_os << "\\\""; - } else if ( c == '\\' ) { - m_os << "\\\\"; - } else if ( c == '\b' ) { - m_os << "\\b"; - } else if ( c == '\f' ) { - m_os << "\\f"; - } else if ( c == '\n' ) { - m_os << "\\n"; - } else if ( c == '\r' ) { - m_os << "\\r"; - } else if ( c == '\t' ) { - m_os << "\\t"; - } else { - m_os << c; - } - } - if ( quote ) { m_os << '"'; } - } - -} // namespace Catch - - - - -namespace Catch { - - 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; - } - -} // namespace Catch - - - - -#ifdef CATCH_CONFIG_WINDOWS_CRTDBG -#include - -namespace Catch { - - LeakDetector::LeakDetector() { - int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); - flag |= _CRTDBG_LEAK_CHECK_DF; - flag |= _CRTDBG_ALLOC_MEM_DF; - _CrtSetDbgFlag(flag); - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); - // Change this to leaking allocation's number to break there - _CrtSetBreakAlloc(-1); - } -} - -#else // ^^ Windows crt debug heap enabled // Windows crt debug heap disabled vv - - Catch::LeakDetector::LeakDetector() = default; - -#endif // CATCH_CONFIG_WINDOWS_CRTDBG - -Catch::LeakDetector::~LeakDetector() { - Catch::cleanUp(); -} - - - - -namespace Catch { - namespace { - - void listTests(IEventListener& reporter, IConfig const& config) { - auto const& testSpec = config.testSpec(); - auto matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); - reporter.listTests(matchedTestCases); - } - - void listTags(IEventListener& reporter, IConfig const& config) { - auto const& testSpec = config.testSpec(); - std::vector matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); - - std::map tagCounts; - for (auto const& testCase : matchedTestCases) { - for (auto const& tagName : testCase.getTestCaseInfo().tags) { - auto it = tagCounts.find(tagName.original); - if (it == tagCounts.end()) - it = tagCounts.insert(std::make_pair(tagName.original, TagInfo())).first; - it->second.add(tagName.original); - } - } - - std::vector infos; infos.reserve(tagCounts.size()); - for (auto& tagc : tagCounts) { - infos.push_back(CATCH_MOVE(tagc.second)); - } - - reporter.listTags(infos); - } - - void listReporters(IEventListener& reporter) { - std::vector descriptions; - - auto const& factories = getRegistryHub().getReporterRegistry().getFactories(); - descriptions.reserve(factories.size()); - for (auto const& fac : factories) { - descriptions.push_back({ fac.first, fac.second->getDescription() }); - } - - reporter.listReporters(descriptions); - } - - void listListeners(IEventListener& reporter) { - std::vector descriptions; - - auto const& factories = - getRegistryHub().getReporterRegistry().getListeners(); - descriptions.reserve( factories.size() ); - for ( auto const& fac : factories ) { - descriptions.push_back( { fac->getName(), fac->getDescription() } ); - } - - reporter.listListeners( descriptions ); - } - - } // end anonymous namespace - - void TagInfo::add( StringRef spelling ) { - ++count; - spellings.insert( spelling ); - } - - std::string TagInfo::all() const { - // 2 per tag for brackets '[' and ']' - size_t size = spellings.size() * 2; - for (auto const& spelling : spellings) { - size += spelling.size(); - } - - std::string out; out.reserve(size); - for (auto const& spelling : spellings) { - out += '['; - out += spelling; - out += ']'; - } - return out; - } - - bool list( IEventListener& reporter, Config const& config ) { - bool listed = false; - if (config.listTests()) { - listed = true; - listTests(reporter, config); - } - if (config.listTags()) { - listed = true; - listTags(reporter, config); - } - if (config.listReporters()) { - listed = true; - listReporters(reporter); - } - if ( config.listListeners() ) { - listed = true; - listListeners( reporter ); - } - return listed; - } - -} // end namespace Catch - - - -namespace Catch { - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS - static LeakDetector leakDetector; - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -} - -// Allow users of amalgamated .cpp file to remove our main and provide their own. -#if !defined(CATCH_AMALGAMATED_CUSTOM_MAIN) - -#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) -// Standard C/C++ Win32 Unicode wmain entry point -extern "C" int __cdecl wmain (int argc, wchar_t * argv[], wchar_t * []) { -#else -// Standard C/C++ main entry point -int main (int argc, char * argv[]) { -#endif - - // We want to force the linker not to discard the global variable - // and its constructor, as it (optionally) registers leak detector - (void)&Catch::leakDetector; - - return Catch::Session().run( argc, argv ); -} - -#endif // !defined(CATCH_AMALGAMATED_CUSTOM_MAIN - - - - -namespace Catch { - - MessageInfo::MessageInfo( StringRef _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ) - : macroName( _macroName ), - lineInfo( _lineInfo ), - type( _type ), - sequence( ++globalCount ) - {} - - // This may need protecting if threading support is added - unsigned int MessageInfo::globalCount = 0; - -} // end namespace Catch - - - -#include -#include -#include -#include - -#if defined( CATCH_CONFIG_NEW_CAPTURE ) -# if defined( _MSC_VER ) -# include //_dup and _dup2 -# define dup _dup -# define dup2 _dup2 -# define fileno _fileno -# else -# include // dup and dup2 -# endif -#endif - -namespace Catch { - - namespace { - //! A no-op implementation, used if no reporter wants output - //! redirection. - class NoopRedirect : public OutputRedirect { - void activateImpl() override {} - void deactivateImpl() override {} - std::string getStdout() override { return {}; } - std::string getStderr() override { return {}; } - void clearBuffers() override {} - }; - - /** - * Redirects specific stream's rdbuf with another's. - * - * Redirection can be stopped and started on-demand, assumes - * that the underlying stream's rdbuf aren't changed by other - * users. - */ - class RedirectedStreamNew { - std::ostream& m_originalStream; - std::ostream& m_redirectionStream; - std::streambuf* m_prevBuf; - - public: - RedirectedStreamNew( std::ostream& originalStream, - std::ostream& redirectionStream ): - m_originalStream( originalStream ), - m_redirectionStream( redirectionStream ), - m_prevBuf( m_originalStream.rdbuf() ) {} - - void startRedirect() { - m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); - } - void stopRedirect() { m_originalStream.rdbuf( m_prevBuf ); } - }; - - /** - * Redirects the `std::cout`, `std::cerr`, `std::clog` streams, - * but does not touch the actual `stdout`/`stderr` file descriptors. - */ - class StreamRedirect : public OutputRedirect { - ReusableStringStream m_redirectedOut, m_redirectedErr; - RedirectedStreamNew m_cout, m_cerr, m_clog; - - public: - StreamRedirect(): - m_cout( Catch::cout(), m_redirectedOut.get() ), - m_cerr( Catch::cerr(), m_redirectedErr.get() ), - m_clog( Catch::clog(), m_redirectedErr.get() ) {} - - void activateImpl() override { - m_cout.startRedirect(); - m_cerr.startRedirect(); - m_clog.startRedirect(); - } - void deactivateImpl() override { - m_cout.stopRedirect(); - m_cerr.stopRedirect(); - m_clog.stopRedirect(); - } - std::string getStdout() override { return m_redirectedOut.str(); } - std::string getStderr() override { return m_redirectedErr.str(); } - void clearBuffers() override { - m_redirectedOut.str( "" ); - m_redirectedErr.str( "" ); - } - }; - -#if defined( CATCH_CONFIG_NEW_CAPTURE ) - - // Windows's implementation of std::tmpfile is terrible (it tries - // to create a file inside system folder, thus requiring elevated - // privileges for the binary), so we have to use tmpnam(_s) and - // create the file ourselves there. - class TempFile { - public: - TempFile( TempFile const& ) = delete; - TempFile& operator=( TempFile const& ) = delete; - TempFile( TempFile&& ) = delete; - TempFile& operator=( TempFile&& ) = delete; - -# if defined( _MSC_VER ) - TempFile() { - if ( tmpnam_s( m_buffer ) ) { - CATCH_RUNTIME_ERROR( "Could not get a temp filename" ); - } - if ( fopen_s( &m_file, m_buffer, "wb+" ) ) { - char buffer[100]; - if ( strerror_s( buffer, errno ) ) { - CATCH_RUNTIME_ERROR( - "Could not translate errno to a string" ); - } - CATCH_RUNTIME_ERROR( "Could not open the temp file: '" - << m_buffer - << "' because: " << buffer ); - } - } -# else - TempFile() { - m_file = std::tmpfile(); - if ( !m_file ) { - CATCH_RUNTIME_ERROR( "Could not create a temp file." ); - } - } -# endif - - ~TempFile() { - // TBD: What to do about errors here? - std::fclose( m_file ); - // We manually create the file on Windows only, on Linux - // it will be autodeleted -# if defined( _MSC_VER ) - std::remove( m_buffer ); -# endif - } - - std::FILE* getFile() { return m_file; } - std::string getContents() { - ReusableStringStream sstr; - constexpr long buffer_size = 100; - char buffer[buffer_size + 1] = {}; - long current_pos = ftell( m_file ); - CATCH_ENFORCE( current_pos >= 0, - "ftell failed, errno: " << errno ); - std::rewind( m_file ); - while ( current_pos > 0 ) { - auto read_characters = - std::fread( buffer, - 1, - std::min( buffer_size, current_pos ), - m_file ); - buffer[read_characters] = '\0'; - sstr << buffer; - current_pos -= static_cast( read_characters ); - } - return sstr.str(); - } - - void clear() { std::rewind( m_file ); } - - private: - std::FILE* m_file = nullptr; - char m_buffer[L_tmpnam] = { 0 }; - }; - - /** - * Redirects the actual `stdout`/`stderr` file descriptors. - * - * Works by replacing the file descriptors numbered 1 and 2 - * with an open temporary file. - */ - class FileRedirect : public OutputRedirect { - TempFile m_outFile, m_errFile; - int m_originalOut = -1; - int m_originalErr = -1; - - // Flushes cout/cerr/clog streams and stdout/stderr FDs - void flushEverything() { - Catch::cout() << std::flush; - fflush( stdout ); - // Since we support overriding these streams, we flush cerr - // even though std::cerr is unbuffered - Catch::cerr() << std::flush; - Catch::clog() << std::flush; - fflush( stderr ); - } - - public: - FileRedirect(): - m_originalOut( dup( fileno( stdout ) ) ), - m_originalErr( dup( fileno( stderr ) ) ) { - CATCH_ENFORCE( m_originalOut >= 0, "Could not dup stdout" ); - CATCH_ENFORCE( m_originalErr >= 0, "Could not dup stderr" ); - } - - std::string getStdout() override { return m_outFile.getContents(); } - std::string getStderr() override { return m_errFile.getContents(); } - void clearBuffers() override { - m_outFile.clear(); - m_errFile.clear(); - } - - void activateImpl() override { - // We flush before starting redirect, to ensure that we do - // not capture the end of message sent before activation. - flushEverything(); - - int ret; - ret = dup2( fileno( m_outFile.getFile() ), fileno( stdout ) ); - CATCH_ENFORCE( ret >= 0, - "dup2 to stdout has failed, errno: " << errno ); - ret = dup2( fileno( m_errFile.getFile() ), fileno( stderr ) ); - CATCH_ENFORCE( ret >= 0, - "dup2 to stderr has failed, errno: " << errno ); - } - void deactivateImpl() override { - // We flush before ending redirect, to ensure that we - // capture all messages sent while the redirect was active. - flushEverything(); - - int ret; - ret = dup2( m_originalOut, fileno( stdout ) ); - CATCH_ENFORCE( - ret >= 0, - "dup2 of original stdout has failed, errno: " << errno ); - ret = dup2( m_originalErr, fileno( stderr ) ); - CATCH_ENFORCE( - ret >= 0, - "dup2 of original stderr has failed, errno: " << errno ); - } - }; - -#endif // CATCH_CONFIG_NEW_CAPTURE - - } // end namespace - - bool isRedirectAvailable( OutputRedirect::Kind kind ) { - switch ( kind ) { - // These two are always available - case OutputRedirect::None: - case OutputRedirect::Streams: - return true; -#if defined( CATCH_CONFIG_NEW_CAPTURE ) - case OutputRedirect::FileDescriptors: - return true; -#endif - default: - return false; - } - } - - Detail::unique_ptr makeOutputRedirect( bool actual ) { - if ( actual ) { - // TODO: Clean this up later -#if defined( CATCH_CONFIG_NEW_CAPTURE ) - return Detail::make_unique(); -#else - return Detail::make_unique(); -#endif - } else { - return Detail::make_unique(); - } - } - - RedirectGuard scopedActivate( OutputRedirect& redirectImpl ) { - return RedirectGuard( true, redirectImpl ); - } - - RedirectGuard scopedDeactivate( OutputRedirect& redirectImpl ) { - return RedirectGuard( false, redirectImpl ); - } - - OutputRedirect::~OutputRedirect() = default; - - RedirectGuard::RedirectGuard( bool activate, OutputRedirect& redirectImpl ): - m_redirect( &redirectImpl ), - m_activate( activate ), - m_previouslyActive( redirectImpl.isActive() ) { - - // Skip cases where there is no actual state change. - if ( m_activate == m_previouslyActive ) { return; } - - if ( m_activate ) { - m_redirect->activate(); - } else { - m_redirect->deactivate(); - } - } - - RedirectGuard::~RedirectGuard() noexcept( false ) { - if ( m_moved ) { return; } - // Skip cases where there is no actual state change. - if ( m_activate == m_previouslyActive ) { return; } - - if ( m_activate ) { - m_redirect->deactivate(); - } else { - m_redirect->activate(); - } - } - - RedirectGuard::RedirectGuard( RedirectGuard&& rhs ) noexcept: - m_redirect( rhs.m_redirect ), - m_activate( rhs.m_activate ), - m_previouslyActive( rhs.m_previouslyActive ), - m_moved( false ) { - rhs.m_moved = true; - } - - RedirectGuard& RedirectGuard::operator=( RedirectGuard&& rhs ) noexcept { - m_redirect = rhs.m_redirect; - m_activate = rhs.m_activate; - m_previouslyActive = rhs.m_previouslyActive; - m_moved = false; - rhs.m_moved = true; - return *this; - } - -} // namespace Catch - -#if defined( CATCH_CONFIG_NEW_CAPTURE ) -# if defined( _MSC_VER ) -# undef dup -# undef dup2 -# undef fileno -# endif -#endif - - - - -#include -#include - -namespace Catch { - - Optional parseUInt(std::string const& input, int base) { - auto trimmed = trim( input ); - // std::stoull is annoying and accepts numbers starting with '-', - // it just negates them into unsigned int - if ( trimmed.empty() || trimmed[0] == '-' ) { - return {}; - } - - CATCH_TRY { - size_t pos = 0; - const auto ret = std::stoull( trimmed, &pos, base ); - - // We did not consume the whole input, so there is an issue - // This can be bunch of different stuff, like multiple numbers - // in the input, or invalid digits/characters and so on. Either - // way, we do not want to return the partially parsed result. - if ( pos != trimmed.size() ) { - return {}; - } - // Too large - if ( ret > std::numeric_limits::max() ) { - return {}; - } - return static_cast(ret); - } - CATCH_CATCH_ANON( std::invalid_argument const& ) { - // no conversion could be performed - } - CATCH_CATCH_ANON( std::out_of_range const& ) { - // the input does not fit into an unsigned long long - } - return {}; - } - -} // namespace Catch - - - - -#include - -namespace Catch { - -#if !defined(CATCH_CONFIG_POLYFILL_ISNAN) - bool isnan(float f) { - return std::isnan(f); - } - bool isnan(double d) { - return std::isnan(d); - } -#else - // For now we only use this for embarcadero - bool isnan(float f) { - return std::_isnan(f); - } - bool isnan(double d) { - return std::_isnan(d); - } -#endif - -#if !defined( CATCH_CONFIG_GLOBAL_NEXTAFTER ) - float nextafter( float x, float y ) { return std::nextafter( x, y ); } - double nextafter( double x, double y ) { return std::nextafter( x, y ); } -#else - float nextafter( float x, float y ) { return ::nextafterf( x, y ); } - double nextafter( double x, double y ) { return ::nextafter( x, y ); } -#endif - -} // end namespace Catch - - - -namespace Catch { - -namespace { - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4146) // we negate uint32 during the rotate -#endif - // Safe rotr implementation thanks to John Regehr - uint32_t rotate_right(uint32_t val, uint32_t count) { - const uint32_t mask = 31; - count &= mask; - return (val >> count) | (val << (-count & mask)); - } - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - -} - - - SimplePcg32::SimplePcg32(result_type seed_) { - seed(seed_); - } - - - void SimplePcg32::seed(result_type seed_) { - m_state = 0; - (*this)(); - m_state += seed_; - (*this)(); - } - - void SimplePcg32::discard(uint64_t skip) { - // We could implement this to run in O(log n) steps, but this - // should suffice for our use case. - for (uint64_t s = 0; s < skip; ++s) { - static_cast((*this)()); - } - } - - SimplePcg32::result_type SimplePcg32::operator()() { - // prepare the output value - const uint32_t xorshifted = static_cast(((m_state >> 18u) ^ m_state) >> 27u); - const auto output = rotate_right(xorshifted, m_state >> 59u); - - // advance state - m_state = m_state * 6364136223846793005ULL + s_inc; - - return output; - } - - bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { - return lhs.m_state == rhs.m_state; - } - - bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { - return lhs.m_state != rhs.m_state; - } -} - - - - - -#include -#include - -namespace Catch { - - std::uint32_t generateRandomSeed( GenerateFrom from ) { - switch ( from ) { - case GenerateFrom::Time: - return static_cast( std::time( nullptr ) ); - - case GenerateFrom::Default: - case GenerateFrom::RandomDevice: { - std::random_device rd; - return Detail::fillBitsFrom( rd ); - } - - default: - CATCH_ERROR("Unknown generation method"); - } - } - -} // end namespace Catch - - - - -namespace Catch { - struct ReporterRegistry::ReporterRegistryImpl { - std::vector> listeners; - std::map - factories; - }; - - ReporterRegistry::ReporterRegistry(): - m_impl( Detail::make_unique() ) { - // Because it is impossible to move out of initializer list, - // we have to add the elements manually - m_impl->factories["Automake"] = - Detail::make_unique>(); - m_impl->factories["compact"] = - Detail::make_unique>(); - m_impl->factories["console"] = - Detail::make_unique>(); - m_impl->factories["JUnit"] = - Detail::make_unique>(); - m_impl->factories["SonarQube"] = - Detail::make_unique>(); - m_impl->factories["TAP"] = - Detail::make_unique>(); - m_impl->factories["TeamCity"] = - Detail::make_unique>(); - m_impl->factories["XML"] = - Detail::make_unique>(); - m_impl->factories["JSON"] = - Detail::make_unique>(); - } - - ReporterRegistry::~ReporterRegistry() = default; - - IEventListenerPtr - ReporterRegistry::create( std::string const& name, - ReporterConfig&& config ) const { - auto it = m_impl->factories.find( name ); - if ( it == m_impl->factories.end() ) return nullptr; - return it->second->create( CATCH_MOVE( config ) ); - } - - void ReporterRegistry::registerReporter( std::string const& name, - IReporterFactoryPtr factory ) { - CATCH_ENFORCE( name.find( "::" ) == name.npos, - "'::' is not allowed in reporter name: '" + name + - '\'' ); - auto ret = m_impl->factories.emplace( name, CATCH_MOVE( factory ) ); - CATCH_ENFORCE( ret.second, - "reporter using '" + name + - "' as name was already registered" ); - } - void ReporterRegistry::registerListener( - Detail::unique_ptr factory ) { - m_impl->listeners.push_back( CATCH_MOVE( factory ) ); - } - - std::map const& - ReporterRegistry::getFactories() const { - return m_impl->factories; - } - - std::vector> const& - ReporterRegistry::getListeners() const { - return m_impl->listeners; - } -} // namespace Catch - - - - - -#include - -namespace Catch { - - namespace { - struct kvPair { - StringRef key, value; - }; - - kvPair splitKVPair(StringRef kvString) { - auto splitPos = static_cast( - std::find( kvString.begin(), kvString.end(), '=' ) - - kvString.begin() ); - - return { kvString.substr( 0, splitPos ), - kvString.substr( splitPos + 1, kvString.size() ) }; - } - } - - namespace Detail { - std::vector splitReporterSpec( StringRef reporterSpec ) { - static constexpr auto separator = "::"; - static constexpr size_t separatorSize = 2; - - size_t separatorPos = 0; - auto findNextSeparator = [&reporterSpec]( size_t startPos ) { - static_assert( - separatorSize == 2, - "The code below currently assumes 2 char separator" ); - - auto currentPos = startPos; - do { - while ( currentPos < reporterSpec.size() && - reporterSpec[currentPos] != separator[0] ) { - ++currentPos; - } - if ( currentPos + 1 < reporterSpec.size() && - reporterSpec[currentPos + 1] == separator[1] ) { - return currentPos; - } - ++currentPos; - } while ( currentPos < reporterSpec.size() ); - - return static_cast( -1 ); - }; - - std::vector parts; - - while ( separatorPos < reporterSpec.size() ) { - const auto nextSeparator = findNextSeparator( separatorPos ); - parts.push_back( static_cast( reporterSpec.substr( - separatorPos, nextSeparator - separatorPos ) ) ); - - if ( nextSeparator == static_cast( -1 ) ) { - break; - } - separatorPos = nextSeparator + separatorSize; - } - - // Handle a separator at the end. - // This is not a valid spec, but we want to do validation in a - // centralized place - if ( separatorPos == reporterSpec.size() ) { - parts.emplace_back(); - } - - return parts; - } - - Optional stringToColourMode( StringRef colourMode ) { - if ( colourMode == "default" ) { - return ColourMode::PlatformDefault; - } else if ( colourMode == "ansi" ) { - return ColourMode::ANSI; - } else if ( colourMode == "win32" ) { - return ColourMode::Win32; - } else if ( colourMode == "none" ) { - return ColourMode::None; - } else { - return {}; - } - } - } // namespace Detail - - - bool operator==( ReporterSpec const& lhs, ReporterSpec const& rhs ) { - return lhs.m_name == rhs.m_name && - lhs.m_outputFileName == rhs.m_outputFileName && - lhs.m_colourMode == rhs.m_colourMode && - lhs.m_customOptions == rhs.m_customOptions; - } - - Optional parseReporterSpec( StringRef reporterSpec ) { - auto parts = Detail::splitReporterSpec( reporterSpec ); - - assert( parts.size() > 0 && "Split should never return empty vector" ); - - std::map kvPairs; - Optional outputFileName; - Optional colourMode; - - // First part is always reporter name, so we skip it - for ( size_t i = 1; i < parts.size(); ++i ) { - auto kv = splitKVPair( parts[i] ); - auto key = kv.key, value = kv.value; - - if ( key.empty() || value.empty() ) { // NOLINT(bugprone-branch-clone) - return {}; - } else if ( key[0] == 'X' ) { - // This is a reporter-specific option, we don't check these - // apart from basic sanity checks - if ( key.size() == 1 ) { - return {}; - } - - auto ret = kvPairs.emplace( std::string(kv.key), std::string(kv.value) ); - if ( !ret.second ) { - // Duplicated key. We might want to handle this differently, - // e.g. by overwriting the existing value? - return {}; - } - } else if ( key == "out" ) { - // Duplicated key - if ( outputFileName ) { - return {}; - } - outputFileName = static_cast( value ); - } else if ( key == "colour-mode" ) { - // Duplicated key - if ( colourMode ) { - return {}; - } - colourMode = Detail::stringToColourMode( value ); - // Parsing failed - if ( !colourMode ) { - return {}; - } - } else { - // Unrecognized option - return {}; - } - } - - return ReporterSpec{ CATCH_MOVE( parts[0] ), - CATCH_MOVE( outputFileName ), - CATCH_MOVE( colourMode ), - CATCH_MOVE( kvPairs ) }; - } - -ReporterSpec::ReporterSpec( - std::string name, - Optional outputFileName, - Optional colourMode, - std::map customOptions ): - m_name( CATCH_MOVE( name ) ), - m_outputFileName( CATCH_MOVE( outputFileName ) ), - m_colourMode( CATCH_MOVE( colourMode ) ), - m_customOptions( CATCH_MOVE( customOptions ) ) {} - -} // namespace Catch - - - -#include -#include -#include - -namespace Catch { - - // This class encapsulates the idea of a pool of ostringstreams that can be reused. - struct StringStreams { - std::vector> m_streams; - std::vector m_unused; - std::ostringstream m_referenceStream; // Used for copy state/ flags from - - auto add() -> std::size_t { - if( m_unused.empty() ) { - m_streams.push_back( Detail::make_unique() ); - return m_streams.size()-1; - } - else { - auto index = m_unused.back(); - m_unused.pop_back(); - return index; - } - } - - void release( std::size_t index ) { - m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state - m_unused.push_back(index); - } - }; - - ReusableStringStream::ReusableStringStream() - : m_index( Singleton::getMutable().add() ), - m_oss( Singleton::getMutable().m_streams[m_index].get() ) - {} - - ReusableStringStream::~ReusableStringStream() { - static_cast( m_oss )->str(""); - m_oss->clear(); - Singleton::getMutable().release( m_index ); - } - - std::string ReusableStringStream::str() const { - return static_cast( m_oss )->str(); - } - - void ReusableStringStream::str( std::string const& str ) { - static_cast( m_oss )->str( str ); - } - - -} - - - - -#include -#include - -namespace Catch { - - namespace Generators { - namespace { - struct GeneratorTracker final : TestCaseTracking::TrackerBase, - IGeneratorTracker { - GeneratorBasePtr m_generator; - - GeneratorTracker( - TestCaseTracking::NameAndLocation&& nameAndLocation, - TrackerContext& ctx, - ITracker* parent ): - TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ) {} - - static GeneratorTracker* - acquire( TrackerContext& ctx, - TestCaseTracking::NameAndLocationRef const& - nameAndLocation ) { - GeneratorTracker* tracker; - - ITracker& currentTracker = ctx.currentTracker(); - // Under specific circumstances, the generator we want - // to acquire is also the current tracker. If this is - // the case, we have to avoid looking through current - // tracker's children, and instead return the current - // tracker. - // A case where this check is important is e.g. - // for (int i = 0; i < 5; ++i) { - // int n = GENERATE(1, 2); - // } - // - // without it, the code above creates 5 nested generators. - if ( currentTracker.nameAndLocation() == nameAndLocation ) { - auto thisTracker = currentTracker.parent()->findChild( - nameAndLocation ); - assert( thisTracker ); - assert( thisTracker->isGeneratorTracker() ); - tracker = static_cast( thisTracker ); - } else if ( ITracker* childTracker = - currentTracker.findChild( - nameAndLocation ) ) { - assert( childTracker ); - assert( childTracker->isGeneratorTracker() ); - tracker = - static_cast( childTracker ); - } else { - return nullptr; - } - - if ( !tracker->isComplete() ) { tracker->open(); } - - return tracker; - } - - // TrackerBase interface - bool isGeneratorTracker() const override { return true; } - auto hasGenerator() const -> bool override { - return !!m_generator; - } - void close() override { - TrackerBase::close(); - // If a generator has a child (it is followed by a section) - // and none of its children have started, then we must wait - // until later to start consuming its values. - // This catches cases where `GENERATE` is placed between two - // `SECTION`s. - // **The check for m_children.empty cannot be removed**. - // doing so would break `GENERATE` _not_ followed by - // `SECTION`s. - const bool should_wait_for_child = [&]() { - // No children -> nobody to wait for - if ( m_children.empty() ) { return false; } - // If at least one child started executing, don't wait - if ( std::find_if( - m_children.begin(), - m_children.end(), - []( TestCaseTracking::ITrackerPtr const& - tracker ) { - return tracker->hasStarted(); - } ) != m_children.end() ) { - return false; - } - - // No children have started. We need to check if they - // _can_ start, and thus we should wait for them, or - // they cannot start (due to filters), and we shouldn't - // wait for them - ITracker* parent = m_parent; - // This is safe: there is always at least one section - // tracker in a test case tracking tree - while ( !parent->isSectionTracker() ) { - parent = parent->parent(); - } - assert( parent && - "Missing root (test case) level section" ); - - auto const& parentSection = - static_cast( *parent ); - auto const& filters = parentSection.getFilters(); - // No filters -> no restrictions on running sections - if ( filters.empty() ) { return true; } - - for ( auto const& child : m_children ) { - if ( child->isSectionTracker() && - std::find( filters.begin(), - filters.end(), - static_cast( - *child ) - .trimmedName() ) != - filters.end() ) { - return true; - } - } - return false; - }(); - - // This check is a bit tricky, because m_generator->next() - // has a side-effect, where it consumes generator's current - // value, but we do not want to invoke the side-effect if - // this generator is still waiting for any child to start. - assert( m_generator && "Tracker without generator" ); - if ( should_wait_for_child || - ( m_runState == CompletedSuccessfully && - m_generator->countedNext() ) ) { - m_children.clear(); - m_runState = Executing; - } - } - - // IGeneratorTracker interface - auto getGenerator() const -> GeneratorBasePtr const& override { - return m_generator; - } - void setGenerator( GeneratorBasePtr&& generator ) override { - m_generator = CATCH_MOVE( generator ); - } - }; - } // namespace - } - - RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter) - : m_runInfo(_config->name()), - m_config(_config), - m_reporter(CATCH_MOVE(reporter)), - m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, - m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences().shouldRedirectStdOut ) ), - m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) - { - getCurrentMutableContext().setResultCapture( this ); - m_reporter->testRunStarting(m_runInfo); - } - - RunContext::~RunContext() { - m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); - } - - Totals RunContext::runTest(TestCaseHandle const& testCase) { - const Totals prevTotals = m_totals; - - auto const& testInfo = testCase.getTestCaseInfo(); - m_reporter->testCaseStarting(testInfo); - testCase.prepareTestCase(); - m_activeTestCase = &testCase; - - - ITracker& rootTracker = m_trackerContext.startRun(); - assert(rootTracker.isSectionTracker()); - static_cast(rootTracker).addInitialFilters(m_config->getSectionsToRun()); - - // We intentionally only seed the internal RNG once per test case, - // before it is first invoked. The reason for that is a complex - // interplay of generator/section implementation details and the - // Random*Generator types. - // - // The issue boils down to us needing to seed the Random*Generators - // with different seed each, so that they return different sequences - // of random numbers. We do this by giving them a number from the - // shared RNG instance as their seed. - // - // However, this runs into an issue if the reseeding happens each - // time the test case is entered (as opposed to first time only), - // because multiple generators could get the same seed, e.g. in - // ```cpp - // TEST_CASE() { - // auto i = GENERATE(take(10, random(0, 100)); - // SECTION("A") { - // auto j = GENERATE(take(10, random(0, 100)); - // } - // SECTION("B") { - // auto k = GENERATE(take(10, random(0, 100)); - // } - // } - // ``` - // `i` and `j` would properly return values from different sequences, - // but `i` and `k` would return the same sequence, because their seed - // would be the same. - // (The reason their seeds would be the same is that the generator - // for k would be initialized when the test case is entered the second - // time, after the shared RNG instance was reset to the same value - // it had when the generator for i was initialized.) - seedRng( *m_config ); - - uint64_t testRuns = 0; - std::string redirectedCout; - std::string redirectedCerr; - do { - m_trackerContext.startCycle(); - m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocationRef(testInfo.name, testInfo.lineInfo)); - - m_reporter->testCasePartialStarting(testInfo, testRuns); - - const auto beforeRunTotals = m_totals; - runCurrentTest(); - std::string oneRunCout = m_outputRedirect->getStdout(); - std::string oneRunCerr = m_outputRedirect->getStderr(); - m_outputRedirect->clearBuffers(); - redirectedCout += oneRunCout; - redirectedCerr += oneRunCerr; - - const auto singleRunTotals = m_totals.delta(beforeRunTotals); - auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, CATCH_MOVE(oneRunCout), CATCH_MOVE(oneRunCerr), aborting()); - m_reporter->testCasePartialEnded(statsForOneRun, testRuns); - - ++testRuns; - } 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; - testCase.tearDownTestCase(); - m_reporter->testCaseEnded(TestCaseStats(testInfo, - deltaTotals, - CATCH_MOVE(redirectedCout), - CATCH_MOVE(redirectedCerr), - aborting())); - - m_activeTestCase = nullptr; - m_testCaseTracker = nullptr; - - return deltaTotals; - } - - - void RunContext::assertionEnded(AssertionResult&& result) { - if (result.getResultType() == ResultWas::Ok) { - m_totals.assertions.passed++; - m_lastAssertionPassed = true; - } else if (result.getResultType() == ResultWas::ExplicitSkip) { - m_totals.assertions.skipped++; - m_lastAssertionPassed = true; - } else if (!result.succeeded()) { - m_lastAssertionPassed = false; - if (result.isOk()) { - } - else if( m_activeTestCase->getTestCaseInfo().okToFail() ) - m_totals.assertions.failedButOk++; - else - m_totals.assertions.failed++; - } - else { - m_lastAssertionPassed = true; - } - - { - auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ); - } - - if ( result.getResultType() != ResultWas::Warning ) { - m_messageScopes.clear(); - } - - // Reset working state. assertion info will be reset after - // populateReaction is run if it is needed - m_lastResult = CATCH_MOVE( result ); - } - void RunContext::resetAssertionInfo() { - m_lastAssertionInfo.macroName = StringRef(); - m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; - m_lastAssertionInfo.resultDisposition = ResultDisposition::Normal; - } - - void RunContext::notifyAssertionStarted( AssertionInfo const& info ) { - auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->assertionStarting( info ); - } - - bool RunContext::sectionStarted( StringRef sectionName, - SourceLineInfo const& sectionLineInfo, - Counts& assertions ) { - ITracker& sectionTracker = - SectionTracker::acquire( m_trackerContext, - TestCaseTracking::NameAndLocationRef( - sectionName, sectionLineInfo ) ); - - if (!sectionTracker.isOpen()) - return false; - m_activeSections.push_back(§ionTracker); - - SectionInfo sectionInfo( sectionLineInfo, static_cast(sectionName) ); - m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; - - { - auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->sectionStarting( sectionInfo ); - } - - assertions = m_totals.assertions; - - return true; - } - IGeneratorTracker* - RunContext::acquireGeneratorTracker( StringRef generatorName, - SourceLineInfo const& lineInfo ) { - using namespace Generators; - GeneratorTracker* tracker = GeneratorTracker::acquire( - m_trackerContext, - TestCaseTracking::NameAndLocationRef( - generatorName, lineInfo ) ); - m_lastAssertionInfo.lineInfo = lineInfo; - return tracker; - } - - IGeneratorTracker* RunContext::createGeneratorTracker( - StringRef generatorName, - SourceLineInfo lineInfo, - Generators::GeneratorBasePtr&& generator ) { - - auto nameAndLoc = TestCaseTracking::NameAndLocation( static_cast( generatorName ), lineInfo ); - auto& currentTracker = m_trackerContext.currentTracker(); - assert( - currentTracker.nameAndLocation() != nameAndLoc && - "Trying to create tracker for a genreator that already has one" ); - - auto newTracker = Catch::Detail::make_unique( - CATCH_MOVE(nameAndLoc), m_trackerContext, ¤tTracker ); - auto ret = newTracker.get(); - currentTracker.addChild( CATCH_MOVE( newTracker ) ); - - ret->setGenerator( CATCH_MOVE( generator ) ); - ret->open(); - return ret; - } - - 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&& endInfo) { - Counts assertions = m_totals.assertions - endInfo.prevAssertions; - bool missingAssertions = testForMissingAssertions(assertions); - - if (!m_activeSections.empty()) { - m_activeSections.back()->close(); - m_activeSections.pop_back(); - } - - { - auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->sectionEnded( - SectionStats( CATCH_MOVE( endInfo.sectionInfo ), - assertions, - endInfo.durationInSeconds, - missingAssertions ) ); - } - - m_messages.clear(); - m_messageScopes.clear(); - } - - void RunContext::sectionEndedEarly(SectionEndInfo&& endInfo) { - if ( m_unfinishedSections.empty() ) { - m_activeSections.back()->fail(); - } else { - m_activeSections.back()->close(); - } - m_activeSections.pop_back(); - - m_unfinishedSections.push_back(CATCH_MOVE(endInfo)); - } - - void RunContext::benchmarkPreparing( StringRef name ) { - auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->benchmarkPreparing( name ); - } - void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { - auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->benchmarkStarting( info ); - } - void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) { - auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->benchmarkEnded( stats ); - } - void RunContext::benchmarkFailed( StringRef error ) { - auto _ = scopedDeactivate( *m_outputRedirect ); - m_reporter->benchmarkFailed( error ); - } - - void RunContext::pushScopedMessage(MessageInfo const & message) { - m_messages.push_back(message); - } - - void RunContext::popScopedMessage(MessageInfo const & message) { - m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); - } - - void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) { - m_messageScopes.emplace_back( CATCH_MOVE(builder) ); - } - - std::string RunContext::getCurrentTestName() const { - return m_activeTestCase - ? m_activeTestCase->getTestCaseInfo().name - : std::string(); - } - - const AssertionResult * RunContext::getLastResult() const { - return &(*m_lastResult); - } - - void RunContext::exceptionEarlyReported() { - m_shouldReportUnexpected = false; - } - - void RunContext::handleFatalErrorCondition( StringRef message ) { - // TODO: scoped deactivate here? Just give up and do best effort? - // the deactivation can break things further, OTOH so can the - // capture - auto _ = scopedDeactivate( *m_outputRedirect ); - - // First notify reporter that bad things happened - m_reporter->fatalErrorEncountered( message ); - - // Don't rebuild the result -- the stringification itself can cause more fatal errors - // Instead, fake a result data. - AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); - tempResult.message = static_cast(message); - AssertionResult result(m_lastAssertionInfo, CATCH_MOVE(tempResult)); - - assertionEnded(CATCH_MOVE(result) ); - resetAssertionInfo(); - - // Best effort cleanup for sections that have not been destructed yet - // Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly - while (!m_activeSections.empty()) { - auto nl = m_activeSections.back()->nameAndLocation(); - SectionEndInfo endInfo{ SectionInfo(CATCH_MOVE(nl.location), CATCH_MOVE(nl.name)), {}, 0.0 }; - sectionEndedEarly(CATCH_MOVE(endInfo)); - } - 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); - - Counts assertions; - assertions.failed = 1; - SectionStats testCaseSectionStats(CATCH_MOVE(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++; - m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); - } - - bool RunContext::lastAssertionPassed() { - return m_lastAssertionPassed; - } - - void RunContext::assertionPassed() { - m_lastAssertionPassed = true; - ++m_totals.assertions.passed; - resetAssertionInfo(); - m_messageScopes.clear(); - } - - bool RunContext::aborting() const { - return m_totals.assertions.failed >= static_cast(m_config->abortAfter()); - } - - void RunContext::runCurrentTest() { - auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); - m_reporter->sectionStarting(testCaseSection); - Counts prevAssertions = m_totals.assertions; - double duration = 0; - m_shouldReportUnexpected = true; - m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; - - Timer timer; - CATCH_TRY { - { - auto _ = scopedActivate( *m_outputRedirect ); - timer.start(); - invokeActiveTestCase(); - } - duration = timer.getElapsedSeconds(); - } CATCH_CATCH_ANON (TestFailureException&) { - // This just means the test was aborted due to failure - } CATCH_CATCH_ANON (TestSkipException&) { - // This just means the test was explicitly skipped - } CATCH_CATCH_ALL { - // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions - // are reported without translation at the point of origin. - if( m_shouldReportUnexpected ) { - AssertionReaction dummyReaction; - handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); - } - } - Counts assertions = m_totals.assertions - prevAssertions; - bool missingAssertions = testForMissingAssertions(assertions); - - m_testCaseTracker->close(); - handleUnfinishedSections(); - m_messages.clear(); - m_messageScopes.clear(); - - SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions); - m_reporter->sectionEnded(testCaseSectionStats); - } - - void RunContext::invokeActiveTestCase() { - // We need to engage a handler for signals/structured exceptions - // before running the tests themselves, or the binary can crash - // without failed test being reported. - FatalConditionHandlerGuard _(&m_fatalConditionhandler); - // We keep having issue where some compilers warn about an unused - // variable, even though the type has non-trivial constructor and - // destructor. This is annoying and ugly, but it makes them stfu. - (void)_; - - m_activeTestCase->invoke(); - } - - void RunContext::handleUnfinishedSections() { - // If sections ended prematurely due to an exception we stored their - // infos here so we can tear them down outside the unwind process. - for ( auto it = m_unfinishedSections.rbegin(), - itEnd = m_unfinishedSections.rend(); - it != itEnd; - ++it ) { - sectionEnded( CATCH_MOVE( *it ) ); - } - m_unfinishedSections.clear(); - } - - void RunContext::handleExpr( - AssertionInfo const& info, - ITransientExpression const& expr, - AssertionReaction& reaction - ) { - bool negated = isFalseTest( info.resultDisposition ); - bool result = expr.getResult() != negated; - - if( result ) { - if (!m_includeSuccessfulResults) { - assertionPassed(); - } - else { - reportExpr(info, ResultWas::Ok, &expr, negated); - } - } - else { - reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); - populateReaction( reaction ); - } - resetAssertionInfo(); - } - void RunContext::reportExpr( - AssertionInfo const &info, - ResultWas::OfType resultType, - ITransientExpression const *expr, - bool negated ) { - - m_lastAssertionInfo = info; - AssertionResultData data( resultType, LazyExpression( negated ) ); - - AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; - assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; - - assertionEnded( CATCH_MOVE(assertionResult) ); - } - - void RunContext::handleMessage( - AssertionInfo const& info, - ResultWas::OfType resultType, - std::string&& message, - AssertionReaction& reaction - ) { - m_lastAssertionInfo = info; - - AssertionResultData data( resultType, LazyExpression( false ) ); - data.message = CATCH_MOVE( message ); - AssertionResult assertionResult{ m_lastAssertionInfo, - CATCH_MOVE( data ) }; - - const auto isOk = assertionResult.isOk(); - assertionEnded( CATCH_MOVE(assertionResult) ); - if ( !isOk ) { - populateReaction( reaction ); - } else if ( resultType == ResultWas::ExplicitSkip ) { - // TODO: Need to handle this explicitly, as ExplicitSkip is - // considered "OK" - reaction.shouldSkip = true; - } - resetAssertionInfo(); - } - void RunContext::handleUnexpectedExceptionNotThrown( - AssertionInfo const& info, - AssertionReaction& reaction - ) { - handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); - } - - void RunContext::handleUnexpectedInflightException( - AssertionInfo const& info, - std::string&& message, - AssertionReaction& reaction - ) { - m_lastAssertionInfo = info; - - AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); - data.message = CATCH_MOVE(message); - AssertionResult assertionResult{ info, CATCH_MOVE(data) }; - assertionEnded( CATCH_MOVE(assertionResult) ); - populateReaction( reaction ); - resetAssertionInfo(); - } - - void RunContext::populateReaction( AssertionReaction& reaction ) { - reaction.shouldDebugBreak = m_config->shouldDebugBreak(); - reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); - } - - void RunContext::handleIncomplete( - AssertionInfo const& info - ) { - using namespace std::string_literals; - m_lastAssertionInfo = info; - - AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); - data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"s; - AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; - assertionEnded( CATCH_MOVE(assertionResult) ); - resetAssertionInfo(); - } - void RunContext::handleNonExpr( - AssertionInfo const &info, - ResultWas::OfType resultType, - AssertionReaction &reaction - ) { - m_lastAssertionInfo = info; - - AssertionResultData data( resultType, LazyExpression( false ) ); - AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; - - const auto isOk = assertionResult.isOk(); - assertionEnded( CATCH_MOVE(assertionResult) ); - if ( !isOk ) { populateReaction( reaction ); } - resetAssertionInfo(); - } - - - IResultCapture& getResultCapture() { - if (auto* capture = getCurrentContext().getResultCapture()) - return *capture; - else - CATCH_INTERNAL_ERROR("No result capture instance"); - } - - void seedRng(IConfig const& config) { - sharedRng().seed(config.rngSeed()); - } - - unsigned int rngSeed() { - return getCurrentContext().getConfig()->rngSeed(); - } - -} - - - -namespace Catch { - - Section::Section( SectionInfo&& info ): - m_info( CATCH_MOVE( info ) ), - m_sectionIncluded( - getResultCapture().sectionStarted( m_info.name, m_info.lineInfo, m_assertions ) ) { - // Non-"included" sections will not use the timing information - // anyway, so don't bother with the potential syscall. - if (m_sectionIncluded) { - m_timer.start(); - } - } - - Section::Section( SourceLineInfo const& _lineInfo, - StringRef _name, - const char* const ): - m_info( { "invalid", static_cast( -1 ) }, std::string{} ), - m_sectionIncluded( - getResultCapture().sectionStarted( _name, _lineInfo, m_assertions ) ) { - // We delay initialization the SectionInfo member until we know - // this section needs it, so we avoid allocating std::string for name. - // We also delay timer start to avoid the potential syscall unless we - // will actually use the result. - if ( m_sectionIncluded ) { - m_info.name = static_cast( _name ); - m_info.lineInfo = _lineInfo; - m_timer.start(); - } - } - - Section::~Section() { - if( m_sectionIncluded ) { - SectionEndInfo endInfo{ CATCH_MOVE(m_info), m_assertions, m_timer.getElapsedSeconds() }; - if ( uncaught_exceptions() ) { - getResultCapture().sectionEndedEarly( CATCH_MOVE(endInfo) ); - } else { - getResultCapture().sectionEnded( CATCH_MOVE( endInfo ) ); - } - } - } - - // This indicates whether the section should be executed or not - Section::operator bool() const { - return m_sectionIncluded; - } - - -} // end namespace Catch - - - -#include - -namespace Catch { - - namespace { - static auto getSingletons() -> std::vector*& { - static std::vector* g_singletons = nullptr; - if( !g_singletons ) - g_singletons = new std::vector(); - return g_singletons; - } - } - - ISingleton::~ISingleton() = default; - - void addSingleton(ISingleton* singleton ) { - getSingletons()->push_back( singleton ); - } - void cleanupSingletons() { - auto& singletons = getSingletons(); - for( auto singleton : *singletons ) - delete singleton; - delete singletons; - singletons = nullptr; - } - -} // namespace Catch - - - -#include -#include - -namespace Catch { - - bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { - return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); - } - bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { - // We can assume that the same file will usually have the same pointer. - // Thus, if the pointers are the same, there is no point in calling the strcmp - return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); - } - - 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; - } - -} // end namespace Catch - - - - -namespace Catch { -#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { - CATCH_TRY { - m_exceptions.push_back(exception); - } CATCH_CATCH_ALL { - // If we run out of memory during start-up there's really not a lot more we can do about it - std::terminate(); - } - } - - std::vector const& StartupExceptionRegistry::getExceptions() const noexcept { - return m_exceptions; - } -#endif - -} // end namespace Catch - - - - - -#include - -namespace Catch { - -// If you #define this you must implement these functions -#if !defined( CATCH_CONFIG_NOSTDOUT ) - std::ostream& cout() { return std::cout; } - std::ostream& cerr() { return std::cerr; } - std::ostream& clog() { return std::clog; } -#endif - -} // namespace Catch - - - -#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( StringRef s, char prefix ) { - return !s.empty() && s[0] == prefix; - } - bool endsWith( std::string const& s, std::string const& suffix ) { - return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); - } - bool endsWith( std::string const& s, char suffix ) { - return !s.empty() && s[s.size()-1] == suffix; - } - bool contains( std::string const& s, std::string const& infix ) { - return s.find( infix ) != std::string::npos; - } - void toLowerInPlace( std::string& s ) { - for ( char& c : s ) { - c = toLower( c ); - } - } - std::string toLower( std::string const& s ) { - std::string lc = s; - toLowerInPlace( lc ); - return lc; - } - char toLower(char c) { - return static_cast(std::tolower(static_cast(c))); - } - - 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(); - } - - StringRef trim(StringRef ref) { - const auto is_ws = [](char c) { - return c == ' ' || c == '\t' || c == '\n' || c == '\r'; - }; - size_t real_begin = 0; - while (real_begin < ref.size() && is_ws(ref[real_begin])) { ++real_begin; } - size_t real_end = ref.size(); - while (real_end > real_begin && is_ws(ref[real_end - 1])) { --real_end; } - - return ref.substr(real_begin, real_end - real_begin); - } - - bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { - std::size_t i = str.find( replaceThis ); - if (i == std::string::npos) { - return false; - } - std::size_t copyBegin = 0; - std::string origStr = CATCH_MOVE(str); - str.clear(); - // There is at least one replacement, so reserve with the best guess - // we can make without actually counting the number of occurences. - str.reserve(origStr.size() - replaceThis.size() + withThis.size()); - do { - str.append(origStr, copyBegin, i-copyBegin ); - str += withThis; - copyBegin = i + replaceThis.size(); - if( copyBegin < origStr.size() ) - i = origStr.find( replaceThis, copyBegin ); - else - i = std::string::npos; - } while( i != std::string::npos ); - if ( copyBegin < origStr.size() ) { - str.append(origStr, copyBegin, origStr.size() ); - } - return true; - } - - std::vector splitStringRef( StringRef str, char delimiter ) { - std::vector subStrings; - std::size_t start = 0; - for(std::size_t pos = 0; pos < str.size(); ++pos ) { - if( str[pos] == delimiter ) { - if( pos - start > 1 ) - subStrings.push_back( str.substr( start, pos-start ) ); - start = pos+1; - } - } - if( start < str.size() ) - subStrings.push_back( str.substr( start, str.size()-start ) ); - return subStrings; - } - - 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; - } - -} - - - -#include -#include -#include -#include - -namespace Catch { - StringRef::StringRef( char const* rawChars ) noexcept - : StringRef( rawChars, std::strlen(rawChars) ) - {} - - - bool StringRef::operator<(StringRef rhs) const noexcept { - if (m_size < rhs.m_size) { - return strncmp(m_start, rhs.m_start, m_size) <= 0; - } - return strncmp(m_start, rhs.m_start, rhs.m_size) < 0; - } - - int StringRef::compare( StringRef rhs ) const { - auto cmpResult = - strncmp( m_start, rhs.m_start, std::min( m_size, rhs.m_size ) ); - - // This means that strncmp found a difference before the strings - // ended, and we can return it directly - if ( cmpResult != 0 ) { - return cmpResult; - } - - // If strings are equal up to length, then their comparison results on - // their size - if ( m_size < rhs.m_size ) { - return -1; - } else if ( m_size > rhs.m_size ) { - return 1; - } else { - return 0; - } - } - - auto operator << ( std::ostream& os, StringRef str ) -> std::ostream& { - return os.write(str.data(), static_cast(str.size())); - } - - std::string operator+(StringRef lhs, StringRef rhs) { - std::string ret; - ret.reserve(lhs.size() + rhs.size()); - ret += lhs; - ret += rhs; - return ret; - } - - auto operator+=( std::string& lhs, StringRef rhs ) -> std::string& { - lhs.append(rhs.data(), rhs.size()); - return lhs; - } - -} // namespace Catch - - - -namespace Catch { - - TagAliasRegistry::~TagAliasRegistry() = default; - - 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() = default; - - ITagAliasRegistry const& ITagAliasRegistry::get() { - return getRegistryHub().getTagAliasRegistry(); - } - -} // end namespace Catch - - - - -namespace Catch { - TestCaseInfoHasher::TestCaseInfoHasher( hash_t seed ): m_seed( seed ) {} - - uint32_t TestCaseInfoHasher::operator()( TestCaseInfo const& t ) const { - // FNV-1a hash algorithm that is designed for uniqueness: - const hash_t prime = 1099511628211u; - hash_t hash = 14695981039346656037u; - for ( const char c : t.name ) { - hash ^= c; - hash *= prime; - } - for ( const char c : t.className ) { - hash ^= c; - hash *= prime; - } - for ( const Tag& tag : t.tags ) { - for ( const char c : tag.original ) { - hash ^= c; - hash *= prime; - } - } - hash ^= m_seed; - hash *= prime; - const uint32_t low{ static_cast( hash ) }; - const uint32_t high{ static_cast( hash >> 32 ) }; - return low * high; - } -} // namespace Catch - - - - -#include -#include - -namespace Catch { - - namespace { - static void enforceNoDuplicateTestCases( - std::vector const& tests ) { - auto testInfoCmp = []( TestCaseInfo const* lhs, - TestCaseInfo const* rhs ) { - return *lhs < *rhs; - }; - std::set seenTests( - testInfoCmp ); - for ( auto const& test : tests ) { - const auto infoPtr = &test.getTestCaseInfo(); - const auto prev = seenTests.insert( infoPtr ); - CATCH_ENFORCE( prev.second, - "error: test case \"" - << infoPtr->name << "\", with tags \"" - << infoPtr->tagsAsString() - << "\" already defined.\n" - << "\tFirst seen at " - << ( *prev.first )->lineInfo << "\n" - << "\tRedefined at " << infoPtr->lineInfo ); - } - } - - static bool matchTest( TestCaseHandle const& testCase, - TestSpec const& testSpec, - IConfig const& config ) { - return testSpec.matches( testCase.getTestCaseInfo() ) && - isThrowSafe( testCase, config ); - } - - } // end unnamed namespace - - std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { - switch (config.runOrder()) { - case TestRunOrder::Declared: - return unsortedTestCases; - - case TestRunOrder::LexicographicallySorted: { - std::vector sorted = unsortedTestCases; - std::sort( - sorted.begin(), - sorted.end(), - []( TestCaseHandle const& lhs, TestCaseHandle const& rhs ) { - return lhs.getTestCaseInfo() < rhs.getTestCaseInfo(); - } - ); - return sorted; - } - case TestRunOrder::Randomized: { - using TestWithHash = std::pair; - - TestCaseInfoHasher h{ config.rngSeed() }; - std::vector indexed_tests; - indexed_tests.reserve(unsortedTestCases.size()); - - for (auto const& handle : unsortedTestCases) { - indexed_tests.emplace_back(h(handle.getTestCaseInfo()), handle); - } - - std::sort( indexed_tests.begin(), - indexed_tests.end(), - []( TestWithHash const& lhs, TestWithHash const& rhs ) { - if ( lhs.first == rhs.first ) { - return lhs.second.getTestCaseInfo() < - rhs.second.getTestCaseInfo(); - } - return lhs.first < rhs.first; - } ); - - std::vector randomized; - randomized.reserve(indexed_tests.size()); - - for (auto const& indexed : indexed_tests) { - randomized.push_back(indexed.second); - } - - return randomized; - } - } - - CATCH_INTERNAL_ERROR("Unknown test order value!"); - } - - bool isThrowSafe( TestCaseHandle const& testCase, IConfig const& config ) { - return !testCase.getTestCaseInfo().throws() || config.allowThrows(); - } - - 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 ((!testSpec.hasFilters() && !testCase.getTestCaseInfo().isHidden()) || - (testSpec.hasFilters() && matchTest(testCase, testSpec, config))) { - filtered.push_back(testCase); - } - } - return createShard(filtered, config.shardCount(), config.shardIndex()); - } - std::vector const& getAllTestCasesSorted( IConfig const& config ) { - return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); - } - - TestRegistry::~TestRegistry() = default; - - void TestRegistry::registerTest(Detail::unique_ptr testInfo, Detail::unique_ptr testInvoker) { - m_handles.emplace_back(testInfo.get(), testInvoker.get()); - m_viewed_test_infos.push_back(testInfo.get()); - m_owned_test_infos.push_back(CATCH_MOVE(testInfo)); - m_invokers.push_back(CATCH_MOVE(testInvoker)); - } - - std::vector const& TestRegistry::getAllInfos() const { - return m_viewed_test_infos; - } - - std::vector const& TestRegistry::getAllTests() const { - return m_handles; - } - std::vector const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { - if( m_sortedFunctions.empty() ) - enforceNoDuplicateTestCases( m_handles ); - - if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { - m_sortedFunctions = sortTests( config, m_handles ); - m_currentSortOrder = config.runOrder(); - } - return m_sortedFunctions; - } - -} // end namespace Catch - - - - -#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&& _name, SourceLineInfo const& _location ) - : name( CATCH_MOVE(_name) ), - location( _location ) - {} - - - ITracker::~ITracker() = default; - - void ITracker::markAsNeedingAnotherRun() { - m_runState = NeedsAnotherRun; - } - - void ITracker::addChild( ITrackerPtr&& child ) { - m_children.push_back( CATCH_MOVE(child) ); - } - - ITracker* ITracker::findChild( NameAndLocationRef const& nameAndLocation ) { - auto it = std::find_if( - m_children.begin(), - m_children.end(), - [&nameAndLocation]( ITrackerPtr const& tracker ) { - auto const& tnameAndLoc = tracker->nameAndLocation(); - if ( tnameAndLoc.location.line != - nameAndLocation.location.line ) { - return false; - } - return tnameAndLoc == nameAndLocation; - } ); - return ( it != m_children.end() ) ? it->get() : nullptr; - } - - bool ITracker::isSectionTracker() const { return false; } - bool ITracker::isGeneratorTracker() const { return false; } - - bool ITracker::isOpen() const { - return m_runState != NotStarted && !isComplete(); - } - - bool ITracker::hasStarted() const { return m_runState != NotStarted; } - - void ITracker::openChild() { - if (m_runState != ExecutingChildren) { - m_runState = ExecutingChildren; - if (m_parent) { - m_parent->openChild(); - } - } - } - - ITracker& TrackerContext::startRun() { - using namespace std::string_literals; - m_rootTracker = Catch::Detail::make_unique( - NameAndLocation( "{root}"s, CATCH_INTERNAL_LINEINFO ), - *this, - nullptr ); - m_currentTracker = nullptr; - m_runState = Executing; - return *m_rootTracker; - } - - void TrackerContext::completeCycle() { - m_runState = CompletedCycle; - } - - bool TrackerContext::completedCycle() const { - return m_runState == CompletedCycle; - } - void TrackerContext::setCurrentTracker( ITracker* tracker ) { - m_currentTracker = tracker; - } - - - TrackerBase::TrackerBase( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent ): - ITracker(CATCH_MOVE(nameAndLocation), parent), - m_ctx( ctx ) - {} - - bool TrackerBase::isComplete() const { - return m_runState == CompletedSuccessfully || m_runState == Failed; - } - - 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( std::all_of(m_children.begin(), m_children.end(), [](ITrackerPtr const& t){ return t->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::moveToParent() { - assert( m_parent ); - m_ctx.setCurrentTracker( m_parent ); - } - void TrackerBase::moveToThis() { - m_ctx.setCurrentTracker( this ); - } - - SectionTracker::SectionTracker( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : TrackerBase( CATCH_MOVE(nameAndLocation), ctx, parent ), - m_trimmed_name(trim(StringRef(ITracker::nameAndLocation().name))) - { - if( parent ) { - while ( !parent->isSectionTracker() ) { - parent = parent->parent(); - } - - SectionTracker& parentSection = static_cast( *parent ); - addNextFilters( parentSection.m_filters ); - } - } - - bool SectionTracker::isComplete() const { - bool complete = true; - - if (m_filters.empty() - || m_filters[0].empty() - || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) { - complete = TrackerBase::isComplete(); - } - return complete; - } - - bool SectionTracker::isSectionTracker() const { return true; } - - SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocationRef const& nameAndLocation ) { - SectionTracker* tracker; - - ITracker& currentTracker = ctx.currentTracker(); - if ( ITracker* childTracker = - currentTracker.findChild( nameAndLocation ) ) { - assert( childTracker ); - assert( childTracker->isSectionTracker() ); - tracker = static_cast( childTracker ); - } else { - auto newTracker = Catch::Detail::make_unique( - NameAndLocation{ static_cast(nameAndLocation.name), - nameAndLocation.location }, - ctx, - ¤tTracker ); - tracker = newTracker.get(); - currentTracker.addChild( CATCH_MOVE( newTracker ) ); - } - - if ( !ctx.completedCycle() ) { - tracker->tryOpen(); - } - - return *tracker; - } - - void SectionTracker::tryOpen() { - if( !isComplete() ) - open(); - } - - void SectionTracker::addInitialFilters( std::vector const& filters ) { - if( !filters.empty() ) { - m_filters.reserve( m_filters.size() + filters.size() + 2 ); - m_filters.emplace_back(StringRef{}); // Root - should never be consulted - m_filters.emplace_back(StringRef{}); // 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()+1, filters.end() ); - } - - StringRef SectionTracker::trimmedName() const { - return m_trimmed_name; - } - -} // namespace TestCaseTracking - -} // namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - - - - -namespace Catch { - - void throw_test_failure_exception() { -#if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) - throw TestFailureException{}; -#else - CATCH_ERROR( "Test failure requires aborting test!" ); -#endif - } - - void throw_test_skip_exception() { -#if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) - throw Catch::TestSkipException(); -#else - CATCH_ERROR( "Explicitly skipping tests during runtime requires exceptions" ); -#endif - } - -} // namespace Catch - - - -#include -#include - -namespace Catch { - void ITestInvoker::prepareTestCase() {} - void ITestInvoker::tearDownTestCase() {} - ITestInvoker::~ITestInvoker() = default; - - namespace { - static StringRef extractClassName( StringRef classOrMethodName ) { - if ( !startsWith( classOrMethodName, '&' ) ) { - return classOrMethodName; - } - - // Remove the leading '&' to avoid having to special case it later - const auto methodName = - classOrMethodName.substr( 1, classOrMethodName.size() ); - - auto reverseStart = std::make_reverse_iterator( methodName.end() ); - auto reverseEnd = std::make_reverse_iterator( methodName.begin() ); - - // We make a simplifying assumption that ":" is only present - // in the input as part of "::" from C++ typenames (this is - // relatively safe assumption because the input is generated - // as stringification of type through preprocessor). - auto lastColons = std::find( reverseStart, reverseEnd, ':' ) + 1; - auto secondLastColons = - std::find( lastColons + 1, reverseEnd, ':' ); - - auto const startIdx = reverseEnd - secondLastColons; - auto const classNameSize = secondLastColons - lastColons - 1; - - return methodName.substr( - static_cast( startIdx ), - static_cast( classNameSize ) ); - } - - class TestInvokerAsFunction final : public ITestInvoker { - using TestType = void ( * )(); - TestType m_testAsFunction; - - public: - constexpr TestInvokerAsFunction( TestType testAsFunction ) noexcept: - m_testAsFunction( testAsFunction ) {} - - void invoke() const override { m_testAsFunction(); } - }; - - } // namespace - - Detail::unique_ptr makeTestInvoker( void(*testAsFunction)() ) { - return Detail::make_unique( testAsFunction ); - } - - AutoReg::AutoReg( Detail::unique_ptr invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept { - CATCH_TRY { - getMutableRegistryHub() - .registerTest( - makeTestCaseInfo( - extractClassName( classOrMethod ), - nameAndTags, - lineInfo), - CATCH_MOVE(invoker) - ); - } CATCH_CATCH_ALL { - // Do not throw when constructing global objects, instead register the exception to be processed later - getMutableRegistryHub().registerStartupException(); - } - } -} - - - - - -namespace Catch { - - TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} - - TestSpecParser& TestSpecParser::parse( std::string const& arg ) { - m_mode = None; - m_exclusion = false; - m_arg = m_tagAliases->expandAliases( arg ); - m_escapeChars.clear(); - m_substring.reserve(m_arg.size()); - m_patternName.reserve(m_arg.size()); - m_realPatternPos = 0; - - for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) - //if visitChar fails - if( !visitChar( m_arg[m_pos] ) ){ - m_testSpec.m_invalidSpecs.push_back(arg); - break; - } - endMode(); - return *this; - } - TestSpec TestSpecParser::testSpec() { - addFilter(); - return CATCH_MOVE(m_testSpec); - } - bool TestSpecParser::visitChar( char c ) { - if( (m_mode != EscapedName) && (c == '\\') ) { - escape(); - addCharToPattern(c); - return true; - }else if((m_mode != EscapedName) && (c == ',') ) { - return separate(); - } - - switch( m_mode ) { - case None: - if( processNoneChar( c ) ) - return true; - break; - case Name: - processNameChar( c ); - break; - case EscapedName: - endMode(); - addCharToPattern(c); - return true; - default: - case Tag: - case QuotedName: - if( processOtherChar( c ) ) - return true; - break; - } - - m_substring += c; - if( !isControlChar( c ) ) { - m_patternName += c; - m_realPatternPos++; - } - return true; - } - // Two of the processing methods return true to signal the caller to return - // without adding the given character to the current pattern strings - bool TestSpecParser::processNoneChar( char c ) { - switch( c ) { - case ' ': - return true; - case '~': - m_exclusion = true; - return false; - case '[': - startNewMode( Tag ); - return false; - case '"': - startNewMode( QuotedName ); - return false; - default: - startNewMode( Name ); - return false; - } - } - void TestSpecParser::processNameChar( char c ) { - if( c == '[' ) { - if( m_substring == "exclude:" ) - m_exclusion = true; - else - endMode(); - startNewMode( Tag ); - } - } - bool TestSpecParser::processOtherChar( char c ) { - if( !isControlChar( c ) ) - return false; - m_substring += c; - endMode(); - return true; - } - void TestSpecParser::startNewMode( Mode mode ) { - m_mode = mode; - } - void TestSpecParser::endMode() { - switch( m_mode ) { - case Name: - case QuotedName: - return addNamePattern(); - case Tag: - return addTagPattern(); - case EscapedName: - revertBackToLastMode(); - return; - case None: - default: - return startNewMode( None ); - } - } - void TestSpecParser::escape() { - saveLastMode(); - m_mode = EscapedName; - m_escapeChars.push_back(m_realPatternPos); - } - bool TestSpecParser::isControlChar( char c ) const { - switch( m_mode ) { - default: - return false; - case None: - return c == '~'; - case Name: - return c == '['; - case EscapedName: - return true; - case QuotedName: - return c == '"'; - case Tag: - return c == '[' || c == ']'; - } - } - - void TestSpecParser::addFilter() { - if( !m_currentFilter.m_required.empty() || !m_currentFilter.m_forbidden.empty() ) { - m_testSpec.m_filters.push_back( CATCH_MOVE(m_currentFilter) ); - m_currentFilter = TestSpec::Filter(); - } - } - - void TestSpecParser::saveLastMode() { - lastMode = m_mode; - } - - void TestSpecParser::revertBackToLastMode() { - m_mode = lastMode; - } - - bool TestSpecParser::separate() { - if( (m_mode==QuotedName) || (m_mode==Tag) ){ - //invalid argument, signal failure to previous scope. - m_mode = None; - m_pos = m_arg.size(); - m_substring.clear(); - m_patternName.clear(); - m_realPatternPos = 0; - return false; - } - endMode(); - addFilter(); - return true; //success - } - - std::string TestSpecParser::preprocessPattern() { - std::string token = m_patternName; - for (std::size_t i = 0; i < m_escapeChars.size(); ++i) - token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1); - m_escapeChars.clear(); - if (startsWith(token, "exclude:")) { - m_exclusion = true; - token = token.substr(8); - } - - m_patternName.clear(); - m_realPatternPos = 0; - - return token; - } - - void TestSpecParser::addNamePattern() { - auto token = preprocessPattern(); - - if (!token.empty()) { - if (m_exclusion) { - m_currentFilter.m_forbidden.emplace_back(Detail::make_unique(token, m_substring)); - } else { - m_currentFilter.m_required.emplace_back(Detail::make_unique(token, m_substring)); - } - } - m_substring.clear(); - m_exclusion = false; - m_mode = None; - } - - void TestSpecParser::addTagPattern() { - auto token = preprocessPattern(); - - if (!token.empty()) { - // If the tag pattern is the "hide and tag" shorthand (e.g. [.foo]) - // we have to create a separate hide tag and shorten the real one - if (token.size() > 1 && token[0] == '.') { - token.erase(token.begin()); - if (m_exclusion) { - m_currentFilter.m_forbidden.emplace_back(Detail::make_unique(".", m_substring)); - } else { - m_currentFilter.m_required.emplace_back(Detail::make_unique(".", m_substring)); - } - } - if (m_exclusion) { - m_currentFilter.m_forbidden.emplace_back(Detail::make_unique(token, m_substring)); - } else { - m_currentFilter.m_required.emplace_back(Detail::make_unique(token, m_substring)); - } - } - m_substring.clear(); - m_exclusion = false; - m_mode = None; - } - -} // namespace Catch - - - -#include -#include -#include - -namespace { - bool isWhitespace( char c ) { - return c == ' ' || c == '\t' || c == '\n' || c == '\r'; - } - - bool isBreakableBefore( char c ) { - static const char chars[] = "[({<|"; - return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr; - } - - bool isBreakableAfter( char c ) { - static const char chars[] = "])}>.,:;*+-=&/\\"; - return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr; - } - -} // namespace - -namespace Catch { - namespace TextFlow { - void AnsiSkippingString::preprocessString() { - for ( auto it = m_string.begin(); it != m_string.end(); ) { - // try to read through an ansi sequence - while ( it != m_string.end() && *it == '\033' && - it + 1 != m_string.end() && *( it + 1 ) == '[' ) { - auto cursor = it + 2; - while ( cursor != m_string.end() && - ( isdigit( *cursor ) || *cursor == ';' ) ) { - ++cursor; - } - if ( cursor == m_string.end() || *cursor != 'm' ) { - break; - } - // 'm' -> 0xff - *cursor = AnsiSkippingString::sentinel; - // if we've read an ansi sequence, set the iterator and - // return to the top of the loop - it = cursor + 1; - } - if ( it != m_string.end() ) { - ++m_size; - ++it; - } - } - } - - AnsiSkippingString::AnsiSkippingString( std::string const& text ): - m_string( text ) { - preprocessString(); - } - - AnsiSkippingString::AnsiSkippingString( std::string&& text ): - m_string( CATCH_MOVE( text ) ) { - preprocessString(); - } - - AnsiSkippingString::const_iterator AnsiSkippingString::begin() const { - return const_iterator( m_string ); - } - - AnsiSkippingString::const_iterator AnsiSkippingString::end() const { - return const_iterator( m_string, const_iterator::EndTag{} ); - } - - std::string AnsiSkippingString::substring( const_iterator begin, - const_iterator end ) const { - // There's one caveat here to an otherwise simple substring: when - // making a begin iterator we might have skipped ansi sequences at - // the start. If `begin` here is a begin iterator, skipped over - // initial ansi sequences, we'll use the true beginning of the - // string. Lastly: We need to transform any chars we replaced with - // 0xff back to 'm' - auto str = std::string( begin == this->begin() ? m_string.begin() - : begin.m_it, - end.m_it ); - std::transform( str.begin(), str.end(), str.begin(), []( char c ) { - return c == AnsiSkippingString::sentinel ? 'm' : c; - } ); - return str; - } - - void AnsiSkippingString::const_iterator::tryParseAnsiEscapes() { - // check if we've landed on an ansi sequence, and if so read through - // it - while ( m_it != m_string->end() && *m_it == '\033' && - m_it + 1 != m_string->end() && *( m_it + 1 ) == '[' ) { - auto cursor = m_it + 2; - while ( cursor != m_string->end() && - ( isdigit( *cursor ) || *cursor == ';' ) ) { - ++cursor; - } - if ( cursor == m_string->end() || - *cursor != AnsiSkippingString::sentinel ) { - break; - } - // if we've read an ansi sequence, set the iterator and - // return to the top of the loop - m_it = cursor + 1; - } - } - - void AnsiSkippingString::const_iterator::advance() { - assert( m_it != m_string->end() ); - m_it++; - tryParseAnsiEscapes(); - } - - void AnsiSkippingString::const_iterator::unadvance() { - assert( m_it != m_string->begin() ); - m_it--; - // if *m_it is 0xff, scan back to the \033 and then m_it-- once more - // (and repeat check) - while ( *m_it == AnsiSkippingString::sentinel ) { - while ( *m_it != '\033' ) { - assert( m_it != m_string->begin() ); - m_it--; - } - // if this happens, we must have been a begin iterator that had - // skipped over ansi sequences at the start of a string - assert( m_it != m_string->begin() ); - assert( *m_it == '\033' ); - m_it--; - } - } - - static bool isBoundary( AnsiSkippingString const& line, - AnsiSkippingString::const_iterator it ) { - return it == line.end() || - ( isWhitespace( *it ) && - !isWhitespace( *it.oneBefore() ) ) || - isBreakableBefore( *it ) || - isBreakableAfter( *it.oneBefore() ); - } - - void Column::const_iterator::calcLength() { - m_addHyphen = false; - m_parsedTo = m_lineStart; - AnsiSkippingString const& current_line = m_column.m_string; - - if ( m_parsedTo == current_line.end() ) { - m_lineEnd = m_parsedTo; - return; - } - - assert( m_lineStart != current_line.end() ); - if ( *m_lineStart == '\n' ) { ++m_parsedTo; } - - const auto maxLineLength = m_column.m_width - indentSize(); - std::size_t lineLength = 0; - while ( m_parsedTo != current_line.end() && - lineLength < maxLineLength && *m_parsedTo != '\n' ) { - ++m_parsedTo; - ++lineLength; - } - - // If we encountered a newline before the column is filled, - // then we linebreak at the newline and consider this line - // finished. - if ( lineLength < maxLineLength ) { - m_lineEnd = m_parsedTo; - } else { - // Look for a natural linebreak boundary in the column - // (We look from the end, so that the first found boundary is - // the right one) - m_lineEnd = m_parsedTo; - while ( lineLength > 0 && - !isBoundary( current_line, m_lineEnd ) ) { - --lineLength; - --m_lineEnd; - } - while ( lineLength > 0 && - isWhitespace( *m_lineEnd.oneBefore() ) ) { - --lineLength; - --m_lineEnd; - } - - // If we found one, then that is where we linebreak, otherwise - // we have to split text with a hyphen - if ( lineLength == 0 ) { - m_addHyphen = true; - m_lineEnd = m_parsedTo.oneBefore(); - } - } - } - - size_t Column::const_iterator::indentSize() const { - auto initial = m_lineStart == m_column.m_string.begin() - ? m_column.m_initialIndent - : std::string::npos; - return initial == std::string::npos ? m_column.m_indent : initial; - } - - std::string Column::const_iterator::addIndentAndSuffix( - AnsiSkippingString::const_iterator start, - AnsiSkippingString::const_iterator end ) const { - std::string ret; - const auto desired_indent = indentSize(); - // ret.reserve( desired_indent + (end - start) + m_addHyphen ); - ret.append( desired_indent, ' ' ); - // ret.append( start, end ); - ret += m_column.m_string.substring( start, end ); - if ( m_addHyphen ) { ret.push_back( '-' ); } - - return ret; - } - - Column::const_iterator::const_iterator( Column const& column ): - m_column( column ), - m_lineStart( column.m_string.begin() ), - m_lineEnd( column.m_string.begin() ), - m_parsedTo( column.m_string.begin() ) { - 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_lineStart == m_lineEnd ) { - m_lineStart = m_column.m_string.end(); - } - } - - std::string Column::const_iterator::operator*() const { - assert( m_lineStart <= m_parsedTo ); - return addIndentAndSuffix( m_lineStart, m_lineEnd ); - } - - Column::const_iterator& Column::const_iterator::operator++() { - m_lineStart = m_lineEnd; - AnsiSkippingString const& current_line = m_column.m_string; - if ( m_lineStart != current_line.end() && *m_lineStart == '\n' ) { - m_lineStart++; - } else { - while ( m_lineStart != current_line.end() && - isWhitespace( *m_lineStart ) ) { - ++m_lineStart; - } - } - - if ( m_lineStart != current_line.end() ) { calcLength(); } - return *this; - } - - Column::const_iterator Column::const_iterator::operator++( int ) { - const_iterator prev( *this ); - operator++(); - return prev; - } - - 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; - } - - Column Spacer( size_t spaceWidth ) { - Column ret{ "" }; - ret.width( spaceWidth ); - return ret; - } - - Columns::iterator::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() ); - } - } - - Columns::iterator::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() ); - } - } - - std::string Columns::iterator::operator*() const { - std::string row, padding; - - for ( size_t i = 0; i < m_columns.size(); ++i ) { - const auto width = m_columns[i].width(); - if ( m_iterators[i] != m_columns[i].end() ) { - std::string col = *m_iterators[i]; - row += padding; - row += col; - - padding.clear(); - if ( col.size() < width ) { - padding.append( width - col.size(), ' ' ); - } - } else { - padding.append( width, ' ' ); - } - } - return row; - } - - Columns::iterator& Columns::iterator::operator++() { - for ( size_t i = 0; i < m_columns.size(); ++i ) { - if ( m_iterators[i] != m_columns[i].end() ) { - ++m_iterators[i]; - } - } - return *this; - } - - Columns::iterator Columns::iterator::operator++( int ) { - iterator prev( *this ); - operator++(); - return prev; - } - - 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; - } - - Columns operator+( Column const& lhs, Column const& rhs ) { - Columns cols; - cols += lhs; - cols += rhs; - return cols; - } - Columns operator+( Column&& lhs, Column&& rhs ) { - Columns cols; - cols += CATCH_MOVE( lhs ); - cols += CATCH_MOVE( rhs ); - return cols; - } - - Columns& operator+=( Columns& lhs, Column const& rhs ) { - lhs.m_columns.push_back( rhs ); - return lhs; - } - Columns& operator+=( Columns& lhs, Column&& rhs ) { - lhs.m_columns.push_back( CATCH_MOVE( rhs ) ); - return lhs; - } - Columns operator+( Columns const& lhs, Column const& rhs ) { - auto combined( lhs ); - combined += rhs; - return combined; - } - Columns operator+( Columns&& lhs, Column&& rhs ) { - lhs += CATCH_MOVE( rhs ); - return CATCH_MOVE( lhs ); - } - - } // namespace TextFlow -} // namespace Catch - - - - -#include - -namespace Catch { - bool uncaught_exceptions() { -#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - return false; -#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) - return std::uncaught_exceptions() > 0; -#else - return std::uncaught_exception(); -#endif - } -} // end namespace Catch - - - -namespace Catch { - - WildcardPattern::WildcardPattern( std::string const& pattern, - CaseSensitive caseSensitivity ) - : m_caseSensitivity( caseSensitivity ), - m_pattern( normaliseString( 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 == normaliseString( str ); - case WildcardAtStart: - return endsWith( normaliseString( str ), m_pattern ); - case WildcardAtEnd: - return startsWith( normaliseString( str ), m_pattern ); - case WildcardAtBothEnds: - return contains( normaliseString( str ), m_pattern ); - default: - CATCH_INTERNAL_ERROR( "Unknown enum" ); - } - } - - std::string WildcardPattern::normaliseString( std::string const& str ) const { - return trim( m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str ); - } -} - - -// Note: swapping these two includes around causes MSVC to error out -// while in /permissive- mode. No, I don't know why. -// Tested on VS 2019, 18.{3, 4}.x - -#include -#include -#include - -namespace Catch { - -namespace { - - size_t trailingBytes(unsigned char c) { - if ((c & 0xE0) == 0xC0) { - return 2; - } - if ((c & 0xF0) == 0xE0) { - return 3; - } - if ((c & 0xF8) == 0xF0) { - return 4; - } - CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); - } - - uint32_t headerValue(unsigned char c) { - if ((c & 0xE0) == 0xC0) { - return c & 0x1F; - } - if ((c & 0xF0) == 0xE0) { - return c & 0x0F; - } - if ((c & 0xF8) == 0xF0) { - return c & 0x07; - } - CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); - } - - void hexEscapeChar(std::ostream& os, unsigned char c) { - std::ios_base::fmtflags f(os.flags()); - os << "\\x" - << std::uppercase << std::hex << std::setfill('0') << std::setw(2) - << static_cast(c); - os.flags(f); - } - - constexpr bool shouldNewline(XmlFormatting fmt) { - return !!(static_cast>(fmt & XmlFormatting::Newline)); - } - - constexpr bool shouldIndent(XmlFormatting fmt) { - return !!(static_cast>(fmt & XmlFormatting::Indent)); - } - -} // anonymous namespace - - void XmlEncode::encodeTo( std::ostream& os ) const { - // Apostrophe escaping not necessary if we always use " to write attributes - // (see: http://www.w3.org/TR/xml/#syntax) - - for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { - unsigned char c = static_cast(m_str[idx]); - switch (c) { - case '<': os << "<"; break; - case '&': os << "&"; break; - - case '>': - // See: http://www.w3.org/TR/xml/#syntax - if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') - os << ">"; - else - os << c; - break; - - case '\"': - if (m_forWhat == ForAttributes) - os << """; - else - os << c; - break; - - default: - // Check for control characters and invalid utf-8 - - // Escape control characters in standard ascii - // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 - if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { - hexEscapeChar(os, c); - break; - } - - // Plain ASCII: Write it to stream - if (c < 0x7F) { - os << c; - break; - } - - // UTF-8 territory - // Check if the encoding is valid and if it is not, hex escape bytes. - // Important: We do not check the exact decoded values for validity, only the encoding format - // First check that this bytes is a valid lead byte: - // This means that it is not encoded as 1111 1XXX - // Or as 10XX XXXX - if (c < 0xC0 || - c >= 0xF8) { - hexEscapeChar(os, c); - break; - } - - auto encBytes = trailingBytes(c); - // Are there enough bytes left to avoid accessing out-of-bounds memory? - if (idx + encBytes - 1 >= m_str.size()) { - hexEscapeChar(os, c); - break; - } - // The header is valid, check data - // The next encBytes bytes must together be a valid utf-8 - // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) - bool valid = true; - uint32_t value = headerValue(c); - for (std::size_t n = 1; n < encBytes; ++n) { - unsigned char nc = static_cast(m_str[idx + n]); - valid &= ((nc & 0xC0) == 0x80); - value = (value << 6) | (nc & 0x3F); - } - - if ( - // Wrong bit pattern of following bytes - (!valid) || - // Overlong encodings - (value < 0x80) || - (0x80 <= value && value < 0x800 && encBytes > 2) || - (0x800 < value && value < 0x10000 && encBytes > 3) || - // Encoded value out of range - (value >= 0x110000) - ) { - hexEscapeChar(os, c); - break; - } - - // If we got here, this is in fact a valid(ish) utf-8 sequence - for (std::size_t n = 0; n < encBytes; ++n) { - os << m_str[idx + n]; - } - idx += encBytes - 1; - break; - } - } - } - - std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { - xmlEncode.encodeTo( os ); - return os; - } - - XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer, XmlFormatting fmt ) - : m_writer( writer ), - m_fmt(fmt) - {} - - XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept - : m_writer( other.m_writer ), - m_fmt(other.m_fmt) - { - other.m_writer = nullptr; - other.m_fmt = XmlFormatting::None; - } - XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { - if ( m_writer ) { - m_writer->endElement(); - } - m_writer = other.m_writer; - other.m_writer = nullptr; - m_fmt = other.m_fmt; - other.m_fmt = XmlFormatting::None; - return *this; - } - - - XmlWriter::ScopedElement::~ScopedElement() { - if (m_writer) { - m_writer->endElement(m_fmt); - } - } - - XmlWriter::ScopedElement& - XmlWriter::ScopedElement::writeText( StringRef text, XmlFormatting fmt ) { - m_writer->writeText( text, fmt ); - return *this; - } - - XmlWriter::ScopedElement& - XmlWriter::ScopedElement::writeAttribute( StringRef name, - StringRef attribute ) { - m_writer->writeAttribute( name, attribute ); - return *this; - } - - - XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) - { - writeDeclaration(); - } - - XmlWriter::~XmlWriter() { - while (!m_tags.empty()) { - endElement(); - } - newlineIfNecessary(); - } - - XmlWriter& XmlWriter::startElement( std::string const& name, XmlFormatting fmt ) { - ensureTagClosed(); - newlineIfNecessary(); - if (shouldIndent(fmt)) { - m_os << m_indent; - m_indent += " "; - } - m_os << '<' << name; - m_tags.push_back( name ); - m_tagIsOpen = true; - applyFormatting(fmt); - return *this; - } - - XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name, XmlFormatting fmt ) { - ScopedElement scoped( this, fmt ); - startElement( name, fmt ); - return scoped; - } - - XmlWriter& XmlWriter::endElement(XmlFormatting fmt) { - m_indent = m_indent.substr(0, m_indent.size() - 2); - - if( m_tagIsOpen ) { - m_os << "/>"; - m_tagIsOpen = false; - } else { - newlineIfNecessary(); - if (shouldIndent(fmt)) { - m_os << m_indent; - } - m_os << "'; - } - m_os << std::flush; - applyFormatting(fmt); - m_tags.pop_back(); - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( StringRef name, - StringRef attribute ) { - if( !name.empty() && !attribute.empty() ) - m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( StringRef name, bool attribute ) { - writeAttribute(name, (attribute ? "true"_sr : "false"_sr)); - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( StringRef name, - char const* attribute ) { - writeAttribute( name, StringRef( attribute ) ); - return *this; - } - - XmlWriter& XmlWriter::writeText( StringRef text, XmlFormatting fmt ) { - CATCH_ENFORCE(!m_tags.empty(), "Cannot write text as top level element"); - if( !text.empty() ){ - bool tagWasOpen = m_tagIsOpen; - ensureTagClosed(); - if (tagWasOpen && shouldIndent(fmt)) { - m_os << m_indent; - } - m_os << XmlEncode( text, XmlEncode::ForTextNodes ); - applyFormatting(fmt); - } - return *this; - } - - XmlWriter& XmlWriter::writeComment( StringRef text, XmlFormatting fmt ) { - ensureTagClosed(); - if (shouldIndent(fmt)) { - m_os << m_indent; - } - m_os << ""; - applyFormatting(fmt); - return *this; - } - - void XmlWriter::writeStylesheetRef( StringRef url ) { - m_os << R"()" << '\n'; - } - - void XmlWriter::ensureTagClosed() { - if( m_tagIsOpen ) { - m_os << '>' << std::flush; - newlineIfNecessary(); - m_tagIsOpen = false; - } - } - - void XmlWriter::applyFormatting(XmlFormatting fmt) { - m_needsNewline = shouldNewline(fmt); - } - - void XmlWriter::writeDeclaration() { - m_os << R"()" << '\n'; - } - - void XmlWriter::newlineIfNecessary() { - if( m_needsNewline ) { - m_os << '\n' << std::flush; - m_needsNewline = false; - } - } -} - - - - - -namespace Catch { -namespace Matchers { - - std::string MatcherUntypedBase::toString() const { - if (m_cachedToString.empty()) { - m_cachedToString = describe(); - } - return m_cachedToString; - } - - MatcherUntypedBase::~MatcherUntypedBase() = default; - -} // namespace Matchers -} // namespace Catch - - - - -namespace Catch { -namespace Matchers { - - std::string IsEmptyMatcher::describe() const { - return "is empty"; - } - - std::string HasSizeMatcher::describe() const { - ReusableStringStream sstr; - sstr << "has size == " << m_target_size; - return sstr.str(); - } - - IsEmptyMatcher IsEmpty() { - return {}; - } - - HasSizeMatcher SizeIs(std::size_t sz) { - return HasSizeMatcher{ sz }; - } - -} // end namespace Matchers -} // end namespace Catch - - - -namespace Catch { -namespace Matchers { - -bool ExceptionMessageMatcher::match(std::exception const& ex) const { - return ex.what() == m_message; -} - -std::string ExceptionMessageMatcher::describe() const { - return "exception message matches \"" + m_message + '"'; -} - -ExceptionMessageMatcher Message(std::string const& message) { - return ExceptionMessageMatcher(message); -} - -} // namespace Matchers -} // namespace Catch - - - -#include -#include -#include -#include -#include -#include -#include - - -namespace Catch { -namespace { - - template - bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) { - // Comparison with NaN should always be false. - // This way we can rule it out before getting into the ugly details - if (Catch::isnan(lhs) || Catch::isnan(rhs)) { - return false; - } - - // This should also handle positive and negative zeros, infinities - const auto ulpDist = ulpDistance(lhs, rhs); - - return ulpDist <= maxUlpDiff; - } - - -template -FP step(FP start, FP direction, uint64_t steps) { - for (uint64_t i = 0; i < steps; ++i) { - start = Catch::nextafter(start, direction); - } - return start; -} - -// Performs equivalent check of std::fabs(lhs - rhs) <= margin -// But without the subtraction to allow for INFINITY in comparison -bool marginComparison(double lhs, double rhs, double margin) { - return (lhs + margin >= rhs) && (rhs + margin >= lhs); -} - -template -void write(std::ostream& out, FloatingPoint num) { - out << std::scientific - << std::setprecision(std::numeric_limits::max_digits10 - 1) - << num; -} - -} // end anonymous namespace - -namespace Matchers { -namespace Detail { - - enum class FloatingPointKind : uint8_t { - Float, - Double - }; - -} // end namespace Detail - - - WithinAbsMatcher::WithinAbsMatcher(double target, double margin) - :m_target{ target }, m_margin{ margin } { - CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' - << " Margin has to be non-negative."); - } - - // Performs equivalent check of std::fabs(lhs - rhs) <= margin - // But without the subtraction to allow for INFINITY in comparison - bool WithinAbsMatcher::match(double const& matchee) const { - return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); - } - - std::string WithinAbsMatcher::describe() const { - return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); - } - - - WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, Detail::FloatingPointKind baseType) - :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { - CATCH_ENFORCE(m_type == Detail::FloatingPointKind::Double - || m_ulps < (std::numeric_limits::max)(), - "Provided ULP is impossibly large for a float comparison."); - CATCH_ENFORCE( std::numeric_limits::is_iec559, - "WithinUlp matcher only supports platforms with " - "IEEE-754 compatible floating point representation" ); - } - -#if defined(__clang__) -#pragma clang diagnostic push -// Clang <3.5 reports on the default branch in the switch below -#pragma clang diagnostic ignored "-Wunreachable-code" -#endif - - bool WithinUlpsMatcher::match(double const& matchee) const { - switch (m_type) { - case Detail::FloatingPointKind::Float: - return almostEqualUlps(static_cast(matchee), static_cast(m_target), m_ulps); - case Detail::FloatingPointKind::Double: - return almostEqualUlps(matchee, m_target, m_ulps); - default: - CATCH_INTERNAL_ERROR( "Unknown Detail::FloatingPointKind value" ); - } - } - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - - std::string WithinUlpsMatcher::describe() const { - std::stringstream ret; - - ret << "is within " << m_ulps << " ULPs of "; - - if (m_type == Detail::FloatingPointKind::Float) { - write(ret, static_cast(m_target)); - ret << 'f'; - } else { - write(ret, m_target); - } - - ret << " (["; - if (m_type == Detail::FloatingPointKind::Double) { - write( ret, - step( m_target, - -std::numeric_limits::infinity(), - m_ulps ) ); - ret << ", "; - write( ret, - step( m_target, - std::numeric_limits::infinity(), - m_ulps ) ); - } else { - // We have to cast INFINITY to float because of MinGW, see #1782 - write( ret, - step( static_cast( m_target ), - -std::numeric_limits::infinity(), - m_ulps ) ); - ret << ", "; - write( ret, - step( static_cast( m_target ), - std::numeric_limits::infinity(), - m_ulps ) ); - } - ret << "])"; - - return ret.str(); - } - - WithinRelMatcher::WithinRelMatcher(double target, double epsilon): - m_target(target), - m_epsilon(epsilon){ - CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon < 0 does not make sense."); - CATCH_ENFORCE(m_epsilon < 1., "Relative comparison with epsilon >= 1 does not make sense."); - } - - bool WithinRelMatcher::match(double const& matchee) const { - const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target)); - return marginComparison(matchee, m_target, - std::isinf(relMargin)? 0 : relMargin); - } - - std::string WithinRelMatcher::describe() const { - Catch::ReusableStringStream sstr; - sstr << "and " << ::Catch::Detail::stringify(m_target) << " are within " << m_epsilon * 100. << "% of each other"; - return sstr.str(); - } - - -WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) { - return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Double); -} - -WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) { - return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Float); -} - -WithinAbsMatcher WithinAbs(double target, double margin) { - return WithinAbsMatcher(target, margin); -} - -WithinRelMatcher WithinRel(double target, double eps) { - return WithinRelMatcher(target, eps); -} - -WithinRelMatcher WithinRel(double target) { - return WithinRelMatcher(target, std::numeric_limits::epsilon() * 100); -} - -WithinRelMatcher WithinRel(float target, float eps) { - return WithinRelMatcher(target, eps); -} - -WithinRelMatcher WithinRel(float target) { - return WithinRelMatcher(target, std::numeric_limits::epsilon() * 100); -} - - - -bool IsNaNMatcher::match( double const& matchee ) const { - return std::isnan( matchee ); -} - -std::string IsNaNMatcher::describe() const { - using namespace std::string_literals; - return "is NaN"s; -} - -IsNaNMatcher IsNaN() { return IsNaNMatcher(); } - - } // namespace Matchers -} // namespace Catch - - - - -std::string Catch::Matchers::Detail::finalizeDescription(const std::string& desc) { - if (desc.empty()) { - return "matches undescribed predicate"; - } else { - return "matches predicate: \"" + desc + '"'; - } -} - - - -namespace Catch { - namespace Matchers { - std::string AllTrueMatcher::describe() const { return "contains only true"; } - - AllTrueMatcher AllTrue() { return AllTrueMatcher{}; } - - std::string NoneTrueMatcher::describe() const { return "contains no true"; } - - NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; } - - std::string AnyTrueMatcher::describe() const { return "contains at least one true"; } - - AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; } - } // namespace Matchers -} // namespace Catch - - - -#include - -namespace Catch { -namespace Matchers { - - CasedString::CasedString( std::string const& str, CaseSensitive 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; - } - StringRef CasedString::caseSensitivitySuffix() const { - return m_caseSensitivity == CaseSensitive::Yes - ? StringRef() - : " (case insensitive)"_sr; - } - - - StringMatcherBase::StringMatcherBase( StringRef 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; - } - - StringEqualsMatcher::StringEqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals"_sr, comparator ) {} - - bool StringEqualsMatcher::match( std::string const& source ) const { - return m_comparator.adjustString( source ) == m_comparator.m_str; - } - - - StringContainsMatcher::StringContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains"_sr, comparator ) {} - - bool StringContainsMatcher::match( std::string const& source ) const { - return contains( m_comparator.adjustString( source ), m_comparator.m_str ); - } - - - StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with"_sr, 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"_sr, comparator ) {} - - bool EndsWithMatcher::match( std::string const& source ) const { - return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); - } - - - - RegexMatcher::RegexMatcher(std::string regex, CaseSensitive caseSensitivity): m_regex(CATCH_MOVE(regex)), m_caseSensitivity(caseSensitivity) {} - - bool RegexMatcher::match(std::string const& matchee) const { - auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway - if (m_caseSensitivity == CaseSensitive::No) { - flags |= std::regex::icase; - } - auto reg = std::regex(m_regex, flags); - return std::regex_match(matchee, reg); - } - - std::string RegexMatcher::describe() const { - return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Yes)? " case sensitively" : " case insensitively"); - } - - - StringEqualsMatcher Equals( std::string const& str, CaseSensitive caseSensitivity ) { - return StringEqualsMatcher( CasedString( str, caseSensitivity) ); - } - StringContainsMatcher ContainsSubstring( std::string const& str, CaseSensitive caseSensitivity ) { - return StringContainsMatcher( CasedString( str, caseSensitivity) ); - } - EndsWithMatcher EndsWith( std::string const& str, CaseSensitive caseSensitivity ) { - return EndsWithMatcher( CasedString( str, caseSensitivity) ); - } - StartsWithMatcher StartsWith( std::string const& str, CaseSensitive caseSensitivity ) { - return StartsWithMatcher( CasedString( str, caseSensitivity) ); - } - - RegexMatcher Matches(std::string const& regex, CaseSensitive caseSensitivity) { - return RegexMatcher(regex, caseSensitivity); - } - -} // namespace Matchers -} // namespace Catch - - - -namespace Catch { -namespace Matchers { - MatcherGenericBase::~MatcherGenericBase() = default; - - namespace Detail { - - std::string describe_multi_matcher(StringRef combine, std::string const* descriptions_begin, std::string const* descriptions_end) { - std::string description; - std::size_t combined_size = 4; - for ( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) { - combined_size += desc->size(); - } - combined_size += static_cast(descriptions_end - descriptions_begin - 1) * combine.size(); - - description.reserve(combined_size); - - description += "( "; - bool first = true; - for( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) { - if( first ) - first = false; - else - description += combine; - description += *desc; - } - description += " )"; - return description; - } - - } // namespace Detail -} // namespace Matchers -} // namespace Catch - - - - -namespace Catch { - - // This is the general overload that takes a any string matcher - // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers - // the Equals matcher (so the header does not mention matchers) - void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher ) { - std::string exceptionMessage = Catch::translateActiveException(); - MatchExpr expr( CATCH_MOVE(exceptionMessage), matcher ); - handler.handleExpr( expr ); - } - -} // namespace Catch - - - -#include - -namespace Catch { - - AutomakeReporter::~AutomakeReporter() = default; - - void AutomakeReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { - // Possible values to emit are PASS, XFAIL, SKIP, FAIL, XPASS and ERROR. - m_stream << ":test-result: "; - if ( _testCaseStats.totals.testCases.skipped > 0 ) { - m_stream << "SKIP"; - } else if (_testCaseStats.totals.assertions.allPassed()) { - m_stream << "PASS"; - } else if (_testCaseStats.totals.assertions.allOk()) { - m_stream << "XFAIL"; - } else { - m_stream << "FAIL"; - } - m_stream << ' ' << _testCaseStats.testInfo->name << '\n'; - StreamingReporterBase::testCaseEnded(_testCaseStats); - } - - void AutomakeReporter::skipTest(TestCaseInfo const& testInfo) { - m_stream << ":test-result: SKIP " << testInfo.name << '\n'; - } - -} // end namespace Catch - - - - - - -namespace Catch { - ReporterBase::ReporterBase( ReporterConfig&& config ): - IEventListener( config.fullConfig() ), - m_wrapped_stream( CATCH_MOVE(config).takeStream() ), - m_stream( m_wrapped_stream->stream() ), - m_colour( makeColourImpl( config.colourMode(), m_wrapped_stream.get() ) ), - m_customOptions( config.customOptions() ) - {} - - ReporterBase::~ReporterBase() = default; - - void ReporterBase::listReporters( - std::vector const& descriptions ) { - defaultListReporters(m_stream, descriptions, m_config->verbosity()); - } - - void ReporterBase::listListeners( - std::vector const& descriptions ) { - defaultListListeners( m_stream, descriptions ); - } - - void ReporterBase::listTests(std::vector const& tests) { - defaultListTests(m_stream, - m_colour.get(), - tests, - m_config->hasTestFilters(), - m_config->verbosity()); - } - - void ReporterBase::listTags(std::vector const& tags) { - defaultListTags( m_stream, tags, m_config->hasTestFilters() ); - } - -} // namespace Catch - - - - -#include - -namespace Catch { -namespace { - - // Colour::LightGrey - static constexpr Colour::Code compactDimColour = Colour::FileName; - -#ifdef CATCH_PLATFORM_MAC - static constexpr Catch::StringRef compactFailedString = "FAILED"_sr; - static constexpr Catch::StringRef compactPassedString = "PASSED"_sr; -#else - static constexpr Catch::StringRef compactFailedString = "failed"_sr; - static constexpr Catch::StringRef compactPassedString = "passed"_sr; -#endif - -// Implementation of CompactReporter formatting -class AssertionPrinter { -public: - AssertionPrinter& operator= (AssertionPrinter const&) = delete; - AssertionPrinter(AssertionPrinter const&) = delete; - AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages, ColourImpl* colourImpl_) - : stream(_stream) - , result(_stats.assertionResult) - , messages(_stats.infoMessages) - , itMessage(_stats.infoMessages.begin()) - , printInfoMessages(_printInfoMessages) - , colourImpl(colourImpl_) - {} - - void print() { - printSourceInfo(); - - itMessage = messages.begin(); - - switch (result.getResultType()) { - case ResultWas::Ok: - printResultType(Colour::ResultSuccess, compactPassedString); - printOriginalExpression(); - printReconstructedExpression(); - if (!result.hasExpression()) - printRemainingMessages(Colour::None); - else - printRemainingMessages(); - break; - case ResultWas::ExpressionFailed: - if (result.isOk()) - printResultType(Colour::ResultSuccess, compactFailedString + " - but was ok"_sr); - else - printResultType(Colour::Error, compactFailedString); - printOriginalExpression(); - printReconstructedExpression(); - printRemainingMessages(); - break; - case ResultWas::ThrewException: - printResultType(Colour::Error, compactFailedString); - printIssue("unexpected exception with message:"); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::FatalErrorCondition: - printResultType(Colour::Error, compactFailedString); - printIssue("fatal error condition with message:"); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::DidntThrowException: - printResultType(Colour::Error, compactFailedString); - printIssue("expected exception, got none"); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::Info: - printResultType(Colour::None, "info"_sr); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::Warning: - printResultType(Colour::None, "warning"_sr); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::ExplicitFailure: - printResultType(Colour::Error, compactFailedString); - printIssue("explicitly"); - printRemainingMessages(Colour::None); - break; - case ResultWas::ExplicitSkip: - printResultType(Colour::Skip, "skipped"_sr); - printMessage(); - printRemainingMessages(); - 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 { - stream << colourImpl->guardColour( Colour::FileName ) - << result.getSourceInfo() << ':'; - } - - void printResultType(Colour::Code colour, StringRef passOrFail) const { - if (!passOrFail.empty()) { - stream << colourImpl->guardColour(colour) << ' ' << passOrFail; - stream << ':'; - } - } - - void printIssue(char const* issue) const { - stream << ' ' << issue; - } - - void printExpressionWas() { - if (result.hasExpression()) { - stream << ';'; - { - stream << colourImpl->guardColour(compactDimColour) << " expression was:"; - } - printOriginalExpression(); - } - } - - void printOriginalExpression() const { - if (result.hasExpression()) { - stream << ' ' << result.getExpression(); - } - } - - void printReconstructedExpression() const { - if (result.hasExpandedExpression()) { - stream << colourImpl->guardColour(compactDimColour) << " for: "; - stream << result.getExpandedExpression(); - } - } - - void printMessage() { - if (itMessage != messages.end()) { - stream << " '" << itMessage->message << '\''; - ++itMessage; - } - } - - void printRemainingMessages(Colour::Code colour = compactDimColour) { - if (itMessage == messages.end()) - return; - - const auto itEnd = messages.cend(); - const auto N = static_cast(itEnd - itMessage); - - stream << colourImpl->guardColour( colour ) << " with " - << pluralise( N, "message"_sr ) << ':'; - - while (itMessage != itEnd) { - // If this assertion is a warning ignore any INFO messages - if (printInfoMessages || itMessage->type != ResultWas::Info) { - printMessage(); - if (itMessage != itEnd) { - stream << colourImpl->guardColour(compactDimColour) << " and"; - } - continue; - } - ++itMessage; - } - } - -private: - std::ostream& stream; - AssertionResult const& result; - std::vector const& messages; - std::vector::const_iterator itMessage; - bool printInfoMessages; - ColourImpl* colourImpl; -}; - -} // anon namespace - - std::string CompactReporter::getDescription() { - return "Reports test results on a single line, suitable for IDEs"; - } - - void CompactReporter::noMatchingTestCases( StringRef unmatchedSpec ) { - m_stream << "No test cases matched '" << unmatchedSpec << "'\n"; - } - - void CompactReporter::testRunStarting( TestRunInfo const& ) { - if ( m_config->testSpec().hasFilters() ) { - m_stream << m_colour->guardColour( Colour::BrightYellow ) - << "Filters: " - << m_config->testSpec() - << '\n'; - } - m_stream << "RNG seed: " << getSeed() << '\n'; - } - - void CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { - 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 && result.getResultType() != ResultWas::ExplicitSkip ) - return; - printInfoMessages = false; - } - - AssertionPrinter printer( m_stream, _assertionStats, printInfoMessages, m_colour.get() ); - printer.print(); - - m_stream << '\n' << std::flush; - } - - void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { - double dur = _sectionStats.durationInSeconds; - if ( shouldShowDuration( *m_config, dur ) ) { - m_stream << getFormattedDuration( dur ) << " s: " << _sectionStats.sectionInfo.name << '\n' << std::flush; - } - } - - void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { - printTestRunTotals( m_stream, *m_colour, _testRunStats.totals ); - m_stream << "\n\n" << std::flush; - StreamingReporterBase::testRunEnded( _testRunStats ); - } - - CompactReporter::~CompactReporter() = default; - -} // end namespace Catch - - - - -#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 - -#if defined(__clang__) -# pragma clang diagnostic push -// For simplicity, benchmarking-only helpers are always enabled -# pragma clang diagnostic ignored "-Wunused-function" -#endif - - - -namespace Catch { - -namespace { - -// Formatter impl for ConsoleReporter -class ConsoleAssertionPrinter { -public: - ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; - ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; - ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, ColourImpl* colourImpl_, bool _printInfoMessages) - : stream(_stream), - stats(_stats), - result(_stats.assertionResult), - colour(Colour::None), - messages(_stats.infoMessages), - colourImpl(colourImpl_), - printInfoMessages(_printInfoMessages) { - switch (result.getResultType()) { - case ResultWas::Ok: - colour = Colour::Success; - passOrFail = "PASSED"_sr; - //if( result.hasMessage() ) - if (messages.size() == 1) - messageLabel = "with message"_sr; - if (messages.size() > 1) - messageLabel = "with messages"_sr; - break; - case ResultWas::ExpressionFailed: - if (result.isOk()) { - colour = Colour::Success; - passOrFail = "FAILED - but was ok"_sr; - } else { - colour = Colour::Error; - passOrFail = "FAILED"_sr; - } - if (messages.size() == 1) - messageLabel = "with message"_sr; - if (messages.size() > 1) - messageLabel = "with messages"_sr; - break; - case ResultWas::ThrewException: - colour = Colour::Error; - passOrFail = "FAILED"_sr; - // todo switch - switch (messages.size()) { case 0: - messageLabel = "due to unexpected exception with "_sr; - break; - case 1: - messageLabel = "due to unexpected exception with message"_sr; - break; - default: - messageLabel = "due to unexpected exception with messages"_sr; - break; - } - break; - case ResultWas::FatalErrorCondition: - colour = Colour::Error; - passOrFail = "FAILED"_sr; - messageLabel = "due to a fatal error condition"_sr; - break; - case ResultWas::DidntThrowException: - colour = Colour::Error; - passOrFail = "FAILED"_sr; - messageLabel = "because no exception was thrown where one was expected"_sr; - break; - case ResultWas::Info: - messageLabel = "info"_sr; - break; - case ResultWas::Warning: - messageLabel = "warning"_sr; - break; - case ResultWas::ExplicitFailure: - passOrFail = "FAILED"_sr; - colour = Colour::Error; - if (messages.size() == 1) - messageLabel = "explicitly with message"_sr; - if (messages.size() > 1) - messageLabel = "explicitly with messages"_sr; - break; - case ResultWas::ExplicitSkip: - colour = Colour::Skip; - passOrFail = "SKIPPED"_sr; - if (messages.size() == 1) - messageLabel = "explicitly with message"_sr; - if (messages.size() > 1) - messageLabel = "explicitly with messages"_sr; - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - passOrFail = "** internal error **"_sr; - colour = Colour::Error; - break; - } - } - - void print() const { - printSourceInfo(); - if (stats.totals.assertions.total() > 0) { - printResultType(); - printOriginalExpression(); - printReconstructedExpression(); - } else { - stream << '\n'; - } - printMessage(); - } - -private: - void printResultType() const { - if (!passOrFail.empty()) { - stream << colourImpl->guardColour(colour) << passOrFail << ":\n"; - } - } - void printOriginalExpression() const { - if (result.hasExpression()) { - stream << colourImpl->guardColour( Colour::OriginalExpression ) - << " " << result.getExpressionInMacro() << '\n'; - } - } - void printReconstructedExpression() const { - if (result.hasExpandedExpression()) { - stream << "with expansion:\n"; - stream << colourImpl->guardColour( Colour::ReconstructedExpression ) - << TextFlow::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 << TextFlow::Column(msg.message).indent(2) << '\n'; - } - } - void printSourceInfo() const { - stream << colourImpl->guardColour( Colour::FileName ) - << result.getSourceInfo() << ": "; - } - - std::ostream& stream; - AssertionStats const& stats; - AssertionResult const& result; - Colour::Code colour; - StringRef passOrFail; - StringRef messageLabel; - std::vector const& messages; - ColourImpl* colourImpl; - bool printInfoMessages; -}; - -std::size_t makeRatio( std::uint64_t number, std::uint64_t total ) { - const auto ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; - return (ratio == 0 && number > 0) ? 1 : static_cast(ratio); -} - -std::size_t& -findMax( std::size_t& i, std::size_t& j, std::size_t& k, std::size_t& l ) { - if (i > j && i > k && i > l) - return i; - else if (j > k && j > l) - return j; - else if (k > l) - return k; - else - return l; -} - -struct ColumnBreak {}; -struct RowBreak {}; -struct OutputFlush {}; - -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; - - double m_inNanoseconds; - Unit m_units; - -public: - explicit Duration(double inNanoseconds, Unit units = Unit::Auto) - : m_inNanoseconds(inNanoseconds), - m_units(units) { - if (m_units == Unit::Auto) { - if (m_inNanoseconds < s_nanosecondsInAMicrosecond) - m_units = Unit::Nanoseconds; - else if (m_inNanoseconds < s_nanosecondsInAMillisecond) - m_units = Unit::Microseconds; - else if (m_inNanoseconds < s_nanosecondsInASecond) - m_units = Unit::Milliseconds; - else if (m_inNanoseconds < s_nanosecondsInAMinute) - m_units = Unit::Seconds; - else - m_units = Unit::Minutes; - } - - } - - 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 m_inNanoseconds; - } - } - StringRef unitsAsString() const { - switch (m_units) { - case Unit::Nanoseconds: - return "ns"_sr; - case Unit::Microseconds: - return "us"_sr; - case Unit::Milliseconds: - return "ms"_sr; - case Unit::Seconds: - return "s"_sr; - case Unit::Minutes: - return "m"_sr; - default: - return "** internal error **"_sr; - } - - } - friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { - return os << duration.value() << ' ' << duration.unitsAsString(); - } -}; -} // end anon namespace - -enum class Justification { Left, Right }; - -struct ColumnInfo { - std::string name; - std::size_t width; - Justification justification; -}; - -class TablePrinter { - std::ostream& m_os; - std::vector m_columnInfos; - ReusableStringStream m_oss; - int m_currentColumn = -1; - bool m_isOpen = false; - -public: - TablePrinter( std::ostream& os, std::vector columnInfos ) - : m_os( os ), - m_columnInfos( CATCH_MOVE( columnInfos ) ) {} - - auto columnInfos() const -> std::vector const& { - return m_columnInfos; - } - - void open() { - if (!m_isOpen) { - m_isOpen = true; - *this << RowBreak(); - - TextFlow::Columns headerCols; - for (auto const& info : m_columnInfos) { - assert(info.width > 2); - headerCols += TextFlow::Column(info.name).width(info.width - 2); - headerCols += TextFlow::Spacer( 2 ); - } - m_os << headerCols << '\n'; - - m_os << lineOfChars('-') << '\n'; - } - } - void close() { - if (m_isOpen) { - *this << RowBreak(); - m_os << '\n' << std::flush; - 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(); - const auto strSize = colStr.size(); - 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 + 1 < colInfo.width) - ? std::string(colInfo.width - (strSize + 1), ' ') - : std::string(); - if (colInfo.justification == Justification::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; - } - - friend TablePrinter& operator<<(TablePrinter& tp, OutputFlush) { - tp.m_os << std::flush; - return tp; - } -}; - -ConsoleReporter::ConsoleReporter(ReporterConfig&& config): - StreamingReporterBase( CATCH_MOVE( config ) ), - m_tablePrinter(Detail::make_unique(m_stream, - [&config]() -> std::vector { - if (config.fullConfig()->benchmarkNoAnalysis()) - { - return{ - { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left }, - { " samples", 14, Justification::Right }, - { " iterations", 14, Justification::Right }, - { " mean", 14, Justification::Right } - }; - } - else - { - return{ - { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left }, - { "samples mean std dev", 14, Justification::Right }, - { "iterations low mean low std dev", 14, Justification::Right }, - { "est run time high mean high std dev", 14, Justification::Right } - }; - } - }())) {} -ConsoleReporter::~ConsoleReporter() = default; - -std::string ConsoleReporter::getDescription() { - return "Reports test results as plain lines of text"; -} - -void ConsoleReporter::noMatchingTestCases( StringRef unmatchedSpec ) { - m_stream << "No test cases matched '" << unmatchedSpec << "'\n"; -} - -void ConsoleReporter::reportInvalidTestSpec( StringRef arg ) { - m_stream << "Invalid Filter: " << arg << '\n'; -} - -void ConsoleReporter::assertionStarting(AssertionInfo const&) {} - -void ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { - AssertionResult const& result = _assertionStats.assertionResult; - - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - // Drop out if result was successful but we're not printing them. - // TODO: Make configurable whether skips should be printed - if (!includeResults && result.getResultType() != ResultWas::Warning && result.getResultType() != ResultWas::ExplicitSkip) - return; - - lazyPrint(); - - ConsoleAssertionPrinter printer(m_stream, _assertionStats, m_colour.get(), includeResults); - printer.print(); - m_stream << '\n' << std::flush; -} - -void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { - m_tablePrinter->close(); - m_headerPrinted = false; - StreamingReporterBase::sectionStarting(_sectionInfo); -} -void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { - m_tablePrinter->close(); - if (_sectionStats.missingAssertions) { - lazyPrint(); - auto guard = - m_colour->guardColour( Colour::ResultError ).engage( m_stream ); - if (m_sectionStack.size() > 1) - m_stream << "\nNo assertions in section"; - else - m_stream << "\nNo assertions in test case"; - m_stream << " '" << _sectionStats.sectionInfo.name << "'\n\n" << std::flush; - } - double dur = _sectionStats.durationInSeconds; - if (shouldShowDuration(*m_config, dur)) { - m_stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << '\n' << std::flush; - } - if (m_headerPrinted) { - m_headerPrinted = false; - } - StreamingReporterBase::sectionEnded(_sectionStats); -} - -void ConsoleReporter::benchmarkPreparing( StringRef name ) { - lazyPrintWithoutClosingBenchmarkTable(); - - auto nameCol = TextFlow::Column( static_cast( 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 ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { - (*m_tablePrinter) << info.samples << ColumnBreak() - << info.iterations << ColumnBreak(); - if ( !m_config->benchmarkNoAnalysis() ) { - ( *m_tablePrinter ) - << Duration( info.estimatedDuration ) << ColumnBreak(); - } - ( *m_tablePrinter ) << OutputFlush{}; -} -void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) { - if (m_config->benchmarkNoAnalysis()) - { - (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak(); - } - else - { - (*m_tablePrinter) << ColumnBreak() - << Duration(stats.mean.point.count()) << ColumnBreak() - << Duration(stats.mean.lower_bound.count()) << ColumnBreak() - << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak() - << Duration(stats.standardDeviation.point.count()) << ColumnBreak() - << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak() - << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak(); - } -} - -void ConsoleReporter::benchmarkFailed( StringRef error ) { - auto guard = m_colour->guardColour( Colour::Red ).engage( m_stream ); - (*m_tablePrinter) - << "Benchmark failed (" << error << ')' - << ColumnBreak() << RowBreak(); -} - -void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { - m_tablePrinter->close(); - StreamingReporterBase::testCaseEnded(_testCaseStats); - m_headerPrinted = false; -} -void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { - printTotalsDivider(_testRunStats.totals); - printTestRunTotals( m_stream, *m_colour, _testRunStats.totals ); - m_stream << '\n' << std::flush; - StreamingReporterBase::testRunEnded(_testRunStats); -} -void ConsoleReporter::testRunStarting(TestRunInfo const& _testRunInfo) { - StreamingReporterBase::testRunStarting(_testRunInfo); - if ( m_config->testSpec().hasFilters() ) { - m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: " - << m_config->testSpec() << '\n'; - } - m_stream << "Randomness seeded to: " << getSeed() << '\n'; -} - -void ConsoleReporter::lazyPrint() { - - m_tablePrinter->close(); - lazyPrintWithoutClosingBenchmarkTable(); -} - -void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { - - if ( !m_testRunInfoPrinted ) { - lazyPrintRunInfo(); - } - if (!m_headerPrinted) { - printTestCaseAndSectionHeader(); - m_headerPrinted = true; - } -} -void ConsoleReporter::lazyPrintRunInfo() { - m_stream << '\n' - << lineOfChars( '~' ) << '\n' - << m_colour->guardColour( Colour::SecondaryText ) - << currentTestRunInfo.name << " is a Catch2 v" << libraryVersion() - << " host application.\n" - << "Run with -? for options\n\n"; - - m_testRunInfoPrinted = true; -} -void ConsoleReporter::printTestCaseAndSectionHeader() { - assert(!m_sectionStack.empty()); - printOpenHeader(currentTestCaseInfo->name); - - if (m_sectionStack.size() > 1) { - auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream ); - - 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; - - - m_stream << lineOfChars( '-' ) << '\n' - << m_colour->guardColour( Colour::FileName ) << lineInfo << '\n' - << lineOfChars( '.' ) << "\n\n" - << std::flush; -} - -void ConsoleReporter::printClosedHeader(std::string const& _name) { - printOpenHeader(_name); - m_stream << lineOfChars('.') << '\n'; -} -void ConsoleReporter::printOpenHeader(std::string const& _name) { - m_stream << lineOfChars('-') << '\n'; - { - auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream ); - printHeaderString(_name); - } -} - -void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { - // We want to get a bit fancy with line breaking here, so that subsequent - // lines start after ":" if one is present, e.g. - // ``` - // blablabla: Fancy - // linebreaking - // ``` - // but we also want to avoid problems with overly long indentation causing - // the text to take up too many lines, e.g. - // ``` - // blablabla: F - // a - // n - // c - // y - // . - // . - // . - // ``` - // So we limit the prefix indentation check to first quarter of the possible - // width - std::size_t idx = _string.find( ": " ); - if ( idx != std::string::npos && idx < CATCH_CONFIG_CONSOLE_WIDTH / 4 ) { - idx += 2; - } else { - idx = 0; - } - m_stream << TextFlow::Column( _string ) - .indent( indent + idx ) - .initialIndent( indent ) - << '\n'; -} - -void ConsoleReporter::printTotalsDivider(Totals const& totals) { - if (totals.testCases.total() > 0) { - std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); - std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); - std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); - std::size_t skippedRatio = makeRatio(totals.testCases.skipped, totals.testCases.total()); - while (failedRatio + failedButOkRatio + passedRatio + skippedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) - findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)++; - while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) - findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)--; - - m_stream << m_colour->guardColour( Colour::Error ) - << std::string( failedRatio, '=' ) - << m_colour->guardColour( Colour::ResultExpectedFailure ) - << std::string( failedButOkRatio, '=' ); - if ( totals.testCases.allPassed() ) { - m_stream << m_colour->guardColour( Colour::ResultSuccess ) - << std::string( passedRatio, '=' ); - } else { - m_stream << m_colour->guardColour( Colour::Success ) - << std::string( passedRatio, '=' ); - } - m_stream << m_colour->guardColour( Colour::Skip ) - << std::string( skippedRatio, '=' ); - } else { - m_stream << m_colour->guardColour( Colour::Warning ) - << std::string( CATCH_CONFIG_CONSOLE_WIDTH - 1, '=' ); - } - m_stream << '\n'; -} - -} // end namespace Catch - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - - - - -#include -#include - -namespace Catch { - namespace { - struct BySectionInfo { - BySectionInfo( SectionInfo const& other ): m_other( other ) {} - BySectionInfo( BySectionInfo const& other ) = default; - bool operator()( - Detail::unique_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; - }; - - } // namespace - - namespace Detail { - AssertionOrBenchmarkResult::AssertionOrBenchmarkResult( - AssertionStats const& assertion ): - m_assertion( assertion ) {} - - AssertionOrBenchmarkResult::AssertionOrBenchmarkResult( - BenchmarkStats<> const& benchmark ): - m_benchmark( benchmark ) {} - - bool AssertionOrBenchmarkResult::isAssertion() const { - return m_assertion.some(); - } - bool AssertionOrBenchmarkResult::isBenchmark() const { - return m_benchmark.some(); - } - - AssertionStats const& AssertionOrBenchmarkResult::asAssertion() const { - assert(m_assertion.some()); - - return *m_assertion; - } - BenchmarkStats<> const& AssertionOrBenchmarkResult::asBenchmark() const { - assert(m_benchmark.some()); - - return *m_benchmark; - } - - } - - CumulativeReporterBase::~CumulativeReporterBase() = default; - - void CumulativeReporterBase::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) { - m_sectionStack.back()->assertionsAndBenchmarks.emplace_back(benchmarkStats); - } - - void - CumulativeReporterBase::sectionStarting( SectionInfo const& sectionInfo ) { - // We need a copy, because SectionStats expect to take ownership - SectionStats incompleteStats( SectionInfo(sectionInfo), Counts(), 0, false ); - SectionNode* node; - if ( m_sectionStack.empty() ) { - if ( !m_rootSection ) { - m_rootSection = - Detail::make_unique( incompleteStats ); - } - node = m_rootSection.get(); - } else { - SectionNode& parentNode = *m_sectionStack.back(); - auto it = std::find_if( parentNode.childSections.begin(), - parentNode.childSections.end(), - BySectionInfo( sectionInfo ) ); - if ( it == parentNode.childSections.end() ) { - auto newNode = - Detail::make_unique( incompleteStats ); - node = newNode.get(); - parentNode.childSections.push_back( CATCH_MOVE( newNode ) ); - } else { - node = it->get(); - } - } - - m_deepestSection = node; - m_sectionStack.push_back( node ); - } - - void CumulativeReporterBase::assertionEnded( - AssertionStats const& assertionStats ) { - 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. - if ( m_shouldStoreFailedAssertions && - !assertionStats.assertionResult.isOk() ) { - static_cast( - assertionStats.assertionResult.getExpandedExpression() ); - } - if ( m_shouldStoreSuccesfulAssertions && - assertionStats.assertionResult.isOk() ) { - static_cast( - assertionStats.assertionResult.getExpandedExpression() ); - } - SectionNode& sectionNode = *m_sectionStack.back(); - sectionNode.assertionsAndBenchmarks.emplace_back( assertionStats ); - } - - void CumulativeReporterBase::sectionEnded( SectionStats const& sectionStats ) { - assert( !m_sectionStack.empty() ); - SectionNode& node = *m_sectionStack.back(); - node.stats = sectionStats; - m_sectionStack.pop_back(); - } - - void CumulativeReporterBase::testCaseEnded( - TestCaseStats const& testCaseStats ) { - auto node = Detail::make_unique( testCaseStats ); - assert( m_sectionStack.size() == 0 ); - node->children.push_back( CATCH_MOVE(m_rootSection) ); - m_testCases.push_back( CATCH_MOVE(node) ); - - assert( m_deepestSection ); - m_deepestSection->stdOut = testCaseStats.stdOut; - m_deepestSection->stdErr = testCaseStats.stdErr; - } - - - void CumulativeReporterBase::testRunEnded( TestRunStats const& testRunStats ) { - assert(!m_testRun && "CumulativeReporterBase assumes there can only be one test run"); - m_testRun = Detail::make_unique( testRunStats ); - m_testRun->children.swap( m_testCases ); - testRunEndedCumulative(); - } - - bool CumulativeReporterBase::SectionNode::hasAnyAssertions() const { - return std::any_of( - assertionsAndBenchmarks.begin(), - assertionsAndBenchmarks.end(), - []( Detail::AssertionOrBenchmarkResult const& res ) { - return res.isAssertion(); - } ); - } - -} // end namespace Catch - - - - -namespace Catch { - - void EventListenerBase::fatalErrorEncountered( StringRef ) {} - - void EventListenerBase::benchmarkPreparing( StringRef ) {} - void EventListenerBase::benchmarkStarting( BenchmarkInfo const& ) {} - void EventListenerBase::benchmarkEnded( BenchmarkStats<> const& ) {} - void EventListenerBase::benchmarkFailed( StringRef ) {} - - void EventListenerBase::assertionStarting( AssertionInfo const& ) {} - - void EventListenerBase::assertionEnded( AssertionStats const& ) {} - void EventListenerBase::listReporters( - std::vector const& ) {} - void EventListenerBase::listListeners( - std::vector const& ) {} - void EventListenerBase::listTests( std::vector const& ) {} - void EventListenerBase::listTags( std::vector const& ) {} - void EventListenerBase::noMatchingTestCases( StringRef ) {} - void EventListenerBase::reportInvalidTestSpec( StringRef ) {} - void EventListenerBase::testRunStarting( TestRunInfo const& ) {} - void EventListenerBase::testCaseStarting( TestCaseInfo const& ) {} - void EventListenerBase::testCasePartialStarting(TestCaseInfo const&, uint64_t) {} - void EventListenerBase::sectionStarting( SectionInfo const& ) {} - void EventListenerBase::sectionEnded( SectionStats const& ) {} - void EventListenerBase::testCasePartialEnded(TestCaseStats const&, uint64_t) {} - void EventListenerBase::testCaseEnded( TestCaseStats const& ) {} - void EventListenerBase::testRunEnded( TestRunStats const& ) {} - void EventListenerBase::skipTest( TestCaseInfo const& ) {} -} // namespace Catch - - - - -#include -#include -#include -#include -#include - -namespace Catch { - - namespace { - void listTestNamesOnly(std::ostream& out, - std::vector const& tests) { - for (auto const& test : tests) { - auto const& testCaseInfo = test.getTestCaseInfo(); - - if (startsWith(testCaseInfo.name, '#')) { - out << '"' << testCaseInfo.name << '"'; - } else { - out << testCaseInfo.name; - } - - out << '\n'; - } - out << std::flush; - } - } // end unnamed namespace - - - // Because formatting using c++ streams is stateful, drop down to C is - // required Alternatively we could use stringstream, but its performance - // is... not good. - std::string getFormattedDuration( double duration ) { - // Max exponent + 1 is required to represent the whole part - // + 1 for decimal point - // + 3 for the 3 decimal places - // + 1 for null terminator - const 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 - size_t printedLength = static_cast( - sprintf_s( buffer, "%.3f", duration ) ); -#else - size_t printedLength = static_cast( - std::snprintf( buffer, maxDoubleSize, "%.3f", duration ) ); -#endif - return std::string( buffer, printedLength ); - } - - bool shouldShowDuration( IConfig const& config, double duration ) { - if ( config.showDurations() == ShowDurations::Always ) { - return true; - } - if ( config.showDurations() == ShowDurations::Never ) { - return false; - } - const double min = config.minDuration(); - return min >= 0 && duration >= min; - } - - std::string serializeFilters( std::vector const& filters ) { - // We add a ' ' separator between each filter - size_t serialized_size = filters.size() - 1; - for (auto const& filter : filters) { - serialized_size += filter.size(); - } - - std::string serialized; - serialized.reserve(serialized_size); - bool first = true; - - for (auto const& filter : filters) { - if (!first) { - serialized.push_back(' '); - } - first = false; - serialized.append(filter); - } - - return serialized; - } - - std::ostream& operator<<( std::ostream& out, lineOfChars value ) { - for ( size_t idx = 0; idx < CATCH_CONFIG_CONSOLE_WIDTH - 1; ++idx ) { - out.put( value.c ); - } - return out; - } - - void - defaultListReporters( std::ostream& out, - std::vector const& descriptions, - Verbosity verbosity ) { - out << "Available reporters:\n"; - const auto maxNameLen = - std::max_element( descriptions.begin(), - descriptions.end(), - []( ReporterDescription const& lhs, - ReporterDescription const& rhs ) { - return lhs.name.size() < rhs.name.size(); - } ) - ->name.size(); - - for ( auto const& desc : descriptions ) { - if ( verbosity == Verbosity::Quiet ) { - out << TextFlow::Column( desc.name ) - .indent( 2 ) - .width( 5 + maxNameLen ) - << '\n'; - } else { - out << TextFlow::Column( desc.name + ':' ) - .indent( 2 ) - .width( 5 + maxNameLen ) + - TextFlow::Column( desc.description ) - .initialIndent( 0 ) - .indent( 2 ) - .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 ) - << '\n'; - } - } - out << '\n' << std::flush; - } - - void defaultListListeners( std::ostream& out, - std::vector const& descriptions ) { - out << "Registered listeners:\n"; - - if(descriptions.empty()) { - return; - } - - const auto maxNameLen = - std::max_element( descriptions.begin(), - descriptions.end(), - []( ListenerDescription const& lhs, - ListenerDescription const& rhs ) { - return lhs.name.size() < rhs.name.size(); - } ) - ->name.size(); - - for ( auto const& desc : descriptions ) { - out << TextFlow::Column( static_cast( desc.name ) + - ':' ) - .indent( 2 ) - .width( maxNameLen + 5 ) + - TextFlow::Column( desc.description ) - .initialIndent( 0 ) - .indent( 2 ) - .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 ) - << '\n'; - } - - out << '\n' << std::flush; - } - - void defaultListTags( std::ostream& out, - std::vector const& tags, - bool isFiltered ) { - if ( isFiltered ) { - out << "Tags for matching test cases:\n"; - } else { - out << "All available tags:\n"; - } - - for ( auto const& tagCount : tags ) { - ReusableStringStream rss; - rss << " " << std::setw( 2 ) << tagCount.count << " "; - auto str = rss.str(); - auto wrapper = TextFlow::Column( tagCount.all() ) - .initialIndent( 0 ) - .indent( str.size() ) - .width( CATCH_CONFIG_CONSOLE_WIDTH - 10 ); - out << str << wrapper << '\n'; - } - out << pluralise(tags.size(), "tag"_sr) << "\n\n" << std::flush; - } - - void defaultListTests(std::ostream& out, ColourImpl* streamColour, std::vector const& tests, bool isFiltered, Verbosity verbosity) { - // We special case this to provide the equivalent of old - // `--list-test-names-only`, which could then be used by the - // `--input-file` option. - if (verbosity == Verbosity::Quiet) { - listTestNamesOnly(out, tests); - return; - } - - if (isFiltered) { - out << "Matching test cases:\n"; - } else { - out << "All available test cases:\n"; - } - - for (auto const& test : tests) { - auto const& testCaseInfo = test.getTestCaseInfo(); - Colour::Code colour = testCaseInfo.isHidden() - ? Colour::SecondaryText - : Colour::None; - auto colourGuard = streamColour->guardColour( colour ).engage( out ); - - out << TextFlow::Column(testCaseInfo.name).indent(2) << '\n'; - if (verbosity >= Verbosity::High) { - out << TextFlow::Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << '\n'; - } - if (!testCaseInfo.tags.empty() && - verbosity > Verbosity::Quiet) { - out << TextFlow::Column(testCaseInfo.tagsAsString()).indent(6) << '\n'; - } - } - - if (isFiltered) { - out << pluralise(tests.size(), "matching test case"_sr); - } else { - out << pluralise(tests.size(), "test case"_sr); - } - out << "\n\n" << std::flush; - } - - namespace { - class SummaryColumn { - public: - SummaryColumn( std::string suffix, Colour::Code colour ): - m_suffix( CATCH_MOVE( suffix ) ), m_colour( colour ) {} - - SummaryColumn&& addRow( std::uint64_t count ) && { - std::string row = std::to_string(count); - auto const new_width = std::max( m_width, row.size() ); - if ( new_width > m_width ) { - for ( auto& oldRow : m_rows ) { - oldRow.insert( 0, new_width - m_width, ' ' ); - } - } else { - row.insert( 0, m_width - row.size(), ' ' ); - } - m_width = new_width; - m_rows.push_back( row ); - return std::move( *this ); - } - - std::string const& getSuffix() const { return m_suffix; } - Colour::Code getColour() const { return m_colour; } - std::string const& getRow( std::size_t index ) const { - return m_rows[index]; - } - - private: - std::string m_suffix; - Colour::Code m_colour; - std::size_t m_width = 0; - std::vector m_rows; - }; - - void printSummaryRow( std::ostream& stream, - ColourImpl& colour, - StringRef label, - std::vector const& cols, - std::size_t row ) { - for ( auto const& col : cols ) { - auto const& value = col.getRow( row ); - auto const& suffix = col.getSuffix(); - if ( suffix.empty() ) { - stream << label << ": "; - if ( value != "0" ) { - stream << value; - } else { - stream << colour.guardColour( Colour::Warning ) - << "- none -"; - } - } else if ( value != "0" ) { - stream << colour.guardColour( Colour::LightGrey ) << " | " - << colour.guardColour( col.getColour() ) << value - << ' ' << suffix; - } - } - stream << '\n'; - } - } // namespace - - void printTestRunTotals( std::ostream& stream, - ColourImpl& streamColour, - Totals const& totals ) { - if ( totals.testCases.total() == 0 ) { - stream << streamColour.guardColour( Colour::Warning ) - << "No tests ran\n"; - return; - } - - if ( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { - stream << streamColour.guardColour( Colour::ResultSuccess ) - << "All tests passed"; - stream << " (" - << pluralise( totals.assertions.passed, "assertion"_sr ) - << " in " - << pluralise( totals.testCases.passed, "test case"_sr ) - << ')' << '\n'; - return; - } - - std::vector columns; - // Don't include "skipped assertions" in total count - const auto totalAssertionCount = - totals.assertions.total() - totals.assertions.skipped; - columns.push_back( SummaryColumn( "", Colour::None ) - .addRow( totals.testCases.total() ) - .addRow( totalAssertionCount ) ); - 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( "skipped", Colour::Skip ) - .addRow( totals.testCases.skipped ) - // Don't print "skipped assertions" - .addRow( 0 ) ); - columns.push_back( - SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) - .addRow( totals.testCases.failedButOk ) - .addRow( totals.assertions.failedButOk ) ); - printSummaryRow( stream, streamColour, "test cases"_sr, columns, 0 ); - printSummaryRow( stream, streamColour, "assertions"_sr, columns, 1 ); - } - -} // namespace Catch - - -// - -namespace Catch { - namespace { - void writeSourceInfo( JsonObjectWriter& writer, - SourceLineInfo const& sourceInfo ) { - auto source_location_writer = - writer.write( "source-location"_sr ).writeObject(); - source_location_writer.write( "filename"_sr ) - .write( sourceInfo.file ); - source_location_writer.write( "line"_sr ).write( sourceInfo.line ); - } - - void writeTags( JsonArrayWriter writer, std::vector const& tags ) { - for ( auto const& tag : tags ) { - writer.write( tag.original ); - } - } - - void writeProperties( JsonArrayWriter writer, - TestCaseInfo const& info ) { - if ( info.isHidden() ) { writer.write( "is-hidden"_sr ); } - if ( info.okToFail() ) { writer.write( "ok-to-fail"_sr ); } - if ( info.expectedToFail() ) { - writer.write( "expected-to-fail"_sr ); - } - if ( info.throws() ) { writer.write( "throws"_sr ); } - } - - } // namespace - - JsonReporter::JsonReporter( ReporterConfig&& config ): - StreamingReporterBase{ CATCH_MOVE( config ) } { - - m_preferences.shouldRedirectStdOut = true; - // TBD: Do we want to report all assertions? XML reporter does - // not, but for machine-parseable reporters I think the answer - // should be yes. - m_preferences.shouldReportAllAssertions = true; - - m_objectWriters.emplace( m_stream ); - m_writers.emplace( Writer::Object ); - auto& writer = m_objectWriters.top(); - - writer.write( "version"_sr ).write( 1 ); - - { - auto metadata_writer = writer.write( "metadata"_sr ).writeObject(); - metadata_writer.write( "name"_sr ).write( m_config->name() ); - metadata_writer.write( "rng-seed"_sr ).write( m_config->rngSeed() ); - metadata_writer.write( "catch2-version"_sr ) - .write( libraryVersion() ); - if ( m_config->testSpec().hasFilters() ) { - metadata_writer.write( "filters"_sr ) - .write( m_config->testSpec() ); - } - } - } - - JsonReporter::~JsonReporter() { - endListing(); - // TODO: Ensure this closes the top level object, add asserts - assert( m_writers.size() == 1 && "Only the top level object should be open" ); - assert( m_writers.top() == Writer::Object ); - endObject(); - m_stream << '\n' << std::flush; - assert( m_writers.empty() ); - } - - JsonArrayWriter& JsonReporter::startArray() { - m_arrayWriters.emplace( m_arrayWriters.top().writeArray() ); - m_writers.emplace( Writer::Array ); - return m_arrayWriters.top(); - } - JsonArrayWriter& JsonReporter::startArray( StringRef key ) { - m_arrayWriters.emplace( - m_objectWriters.top().write( key ).writeArray() ); - m_writers.emplace( Writer::Array ); - return m_arrayWriters.top(); - } - - JsonObjectWriter& JsonReporter::startObject() { - m_objectWriters.emplace( m_arrayWriters.top().writeObject() ); - m_writers.emplace( Writer::Object ); - return m_objectWriters.top(); - } - JsonObjectWriter& JsonReporter::startObject( StringRef key ) { - m_objectWriters.emplace( - m_objectWriters.top().write( key ).writeObject() ); - m_writers.emplace( Writer::Object ); - return m_objectWriters.top(); - } - - void JsonReporter::endObject() { - assert( isInside( Writer::Object ) ); - m_objectWriters.pop(); - m_writers.pop(); - } - void JsonReporter::endArray() { - assert( isInside( Writer::Array ) ); - m_arrayWriters.pop(); - m_writers.pop(); - } - - bool JsonReporter::isInside( Writer writer ) { - return !m_writers.empty() && m_writers.top() == writer; - } - - void JsonReporter::startListing() { - if ( !m_startedListing ) { startObject( "listings"_sr ); } - m_startedListing = true; - } - void JsonReporter::endListing() { - if ( m_startedListing ) { endObject(); } - m_startedListing = false; - } - - std::string JsonReporter::getDescription() { - return "Outputs listings as JSON. Test listing is Work-in-Progress!"; - } - - void JsonReporter::testRunStarting( TestRunInfo const& runInfo ) { - StreamingReporterBase::testRunStarting( runInfo ); - endListing(); - - assert( isInside( Writer::Object ) ); - startObject( "test-run"_sr ); - startArray( "test-cases"_sr ); - } - - static void writeCounts( JsonObjectWriter&& writer, Counts const& counts ) { - writer.write( "passed"_sr ).write( counts.passed ); - writer.write( "failed"_sr ).write( counts.failed ); - writer.write( "fail-but-ok"_sr ).write( counts.failedButOk ); - writer.write( "skipped"_sr ).write( counts.skipped ); - } - - void JsonReporter::testRunEnded(TestRunStats const& runStats) { - assert( isInside( Writer::Array ) ); - // End "test-cases" - endArray(); - - { - auto totals = - m_objectWriters.top().write( "totals"_sr ).writeObject(); - writeCounts( totals.write( "assertions"_sr ).writeObject(), - runStats.totals.assertions ); - writeCounts( totals.write( "test-cases"_sr ).writeObject(), - runStats.totals.testCases ); - } - - // End the "test-run" object - endObject(); - } - - void JsonReporter::testCaseStarting( TestCaseInfo const& tcInfo ) { - StreamingReporterBase::testCaseStarting( tcInfo ); - - assert( isInside( Writer::Array ) && - "We should be in the 'test-cases' array" ); - startObject(); - // "test-info" prelude - { - auto testInfo = - m_objectWriters.top().write( "test-info"_sr ).writeObject(); - // TODO: handle testName vs className!! - testInfo.write( "name"_sr ).write( tcInfo.name ); - writeSourceInfo(testInfo, tcInfo.lineInfo); - writeTags( testInfo.write( "tags"_sr ).writeArray(), tcInfo.tags ); - writeProperties( testInfo.write( "properties"_sr ).writeArray(), - tcInfo ); - } - - - // Start the array for individual test runs (testCasePartial pairs) - startArray( "runs"_sr ); - } - - void JsonReporter::testCaseEnded( TestCaseStats const& tcStats ) { - StreamingReporterBase::testCaseEnded( tcStats ); - - // We need to close the 'runs' array before finishing the test case - assert( isInside( Writer::Array ) ); - endArray(); - - { - auto totals = - m_objectWriters.top().write( "totals"_sr ).writeObject(); - writeCounts( totals.write( "assertions"_sr ).writeObject(), - tcStats.totals.assertions ); - // We do not write the test case totals, because there will always be just one test case here. - // TODO: overall "result" -> success, skip, fail here? Or in partial result? - } - // We do not write out stderr/stdout, because we instead wrote those out in partial runs - - // TODO: aborting? - - // And we also close this test case's object - assert( isInside( Writer::Object ) ); - endObject(); - } - - void JsonReporter::testCasePartialStarting( TestCaseInfo const& /*tcInfo*/, - uint64_t index ) { - startObject(); - m_objectWriters.top().write( "run-idx"_sr ).write( index ); - startArray( "path"_sr ); - // TODO: we want to delay most of the printing to the 'root' section - // TODO: childSection key name? - } - - void JsonReporter::testCasePartialEnded( TestCaseStats const& tcStats, - uint64_t /*index*/ ) { - // Fixme: the top level section handles this. - //// path object - endArray(); - if ( !tcStats.stdOut.empty() ) { - m_objectWriters.top() - .write( "captured-stdout"_sr ) - .write( tcStats.stdOut ); - } - if ( !tcStats.stdErr.empty() ) { - m_objectWriters.top() - .write( "captured-stderr"_sr ) - .write( tcStats.stdErr ); - } - { - auto totals = - m_objectWriters.top().write( "totals"_sr ).writeObject(); - writeCounts( totals.write( "assertions"_sr ).writeObject(), - tcStats.totals.assertions ); - // We do not write the test case totals, because there will - // always be just one test case here. - // TODO: overall "result" -> success, skip, fail here? Or in - // partial result? - } - // TODO: aborting? - // run object - endObject(); - } - - void JsonReporter::sectionStarting( SectionInfo const& sectionInfo ) { - assert( isInside( Writer::Array ) && - "Section should always start inside an object" ); - // We want to nest top level sections, even though it shares name - // and source loc with the TEST_CASE - auto& sectionObject = startObject(); - sectionObject.write( "kind"_sr ).write( "section"_sr ); - sectionObject.write( "name"_sr ).write( sectionInfo.name ); - writeSourceInfo( m_objectWriters.top(), sectionInfo.lineInfo ); - - - // TBD: Do we want to create this event lazily? It would become - // rather complex, but we could do it, and it would look - // better for empty sections. OTOH, empty sections should - // be rare. - startArray( "path"_sr ); - } - void JsonReporter::sectionEnded( SectionStats const& /*sectionStats */) { - // End the subpath array - endArray(); - // TODO: metadata - // TODO: what info do we have here? - - // End the section object - endObject(); - } - - void JsonReporter::assertionStarting( AssertionInfo const& /*assertionInfo*/ ) {} - void JsonReporter::assertionEnded( AssertionStats const& assertionStats ) { - // TODO: There is lot of different things to handle here, but - // we can fill it in later, after we show that the basic - // outline and streaming reporter impl works well enough. - //if ( !m_config->includeSuccessfulResults() - // && assertionStats.assertionResult.isOk() ) { - // return; - //} - assert( isInside( Writer::Array ) ); - auto assertionObject = m_arrayWriters.top().writeObject(); - - assertionObject.write( "kind"_sr ).write( "assertion"_sr ); - writeSourceInfo( assertionObject, - assertionStats.assertionResult.getSourceInfo() ); - assertionObject.write( "status"_sr ) - .write( assertionStats.assertionResult.isOk() ); - // TODO: handling of result. - // TODO: messages - // TODO: totals? - } - - - void JsonReporter::benchmarkPreparing( StringRef name ) { (void)name; } - void JsonReporter::benchmarkStarting( BenchmarkInfo const& ) {} - void JsonReporter::benchmarkEnded( BenchmarkStats<> const& ) {} - void JsonReporter::benchmarkFailed( StringRef error ) { (void)error; } - - void JsonReporter::listReporters( - std::vector const& descriptions ) { - startListing(); - - auto writer = - m_objectWriters.top().write( "reporters"_sr ).writeArray(); - for ( auto const& desc : descriptions ) { - auto desc_writer = writer.writeObject(); - desc_writer.write( "name"_sr ).write( desc.name ); - desc_writer.write( "description"_sr ).write( desc.description ); - } - } - void JsonReporter::listListeners( - std::vector const& descriptions ) { - startListing(); - - auto writer = - m_objectWriters.top().write( "listeners"_sr ).writeArray(); - - for ( auto const& desc : descriptions ) { - auto desc_writer = writer.writeObject(); - desc_writer.write( "name"_sr ).write( desc.name ); - desc_writer.write( "description"_sr ).write( desc.description ); - } - } - void JsonReporter::listTests( std::vector const& tests ) { - startListing(); - - auto writer = m_objectWriters.top().write( "tests"_sr ).writeArray(); - - for ( auto const& test : tests ) { - auto desc_writer = writer.writeObject(); - auto const& info = test.getTestCaseInfo(); - - desc_writer.write( "name"_sr ).write( info.name ); - desc_writer.write( "class-name"_sr ).write( info.className ); - { - auto tag_writer = desc_writer.write( "tags"_sr ).writeArray(); - for ( auto const& tag : info.tags ) { - tag_writer.write( tag.original ); - } - } - writeSourceInfo( desc_writer, info.lineInfo ); - } - } - void JsonReporter::listTags( std::vector const& tags ) { - startListing(); - - auto writer = m_objectWriters.top().write( "tags"_sr ).writeArray(); - for ( auto const& tag : tags ) { - auto tag_writer = writer.writeObject(); - { - auto aliases_writer = - tag_writer.write( "aliases"_sr ).writeArray(); - for ( auto alias : tag.spellings ) { - aliases_writer.write( alias ); - } - } - tag_writer.write( "count"_sr ).write( tag.count ); - } - } -} // namespace Catch - - - - -#include -#include -#include -#include - -namespace Catch { - - namespace { - std::string getCurrentTimestamp() { - time_t rawtime; - std::time(&rawtime); - - std::tm timeInfo = {}; -#if defined (_MSC_VER) || defined (__MINGW32__) - gmtime_s(&timeInfo, &rawtime); -#elif defined (CATCH_PLATFORM_PLAYSTATION) - gmtime_s(&rawtime, &timeInfo); -#elif defined (__IAR_SYSTEMS_ICC__) - timeInfo = *std::gmtime(&rawtime); -#else - gmtime_r(&rawtime, &timeInfo); -#endif - - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); - char timeStamp[timeStampSize]; - const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; - - std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); - - return std::string(timeStamp, timeStampSize - 1); - } - - std::string fileNameTag(std::vector const& tags) { - auto it = std::find_if(begin(tags), - end(tags), - [] (Tag const& tag) { - return tag.original.size() > 0 - && tag.original[0] == '#'; }); - if (it != tags.end()) { - return static_cast( - it->original.substr(1, it->original.size() - 1) - ); - } - return std::string(); - } - - // Formats the duration in seconds to 3 decimal places. - // This is done because some genius defined Maven Surefire schema - // in a way that only accepts 3 decimal places, and tools like - // Jenkins use that schema for validation JUnit reporter output. - std::string formatDuration( double seconds ) { - ReusableStringStream rss; - rss << std::fixed << std::setprecision( 3 ) << seconds; - return rss.str(); - } - - static void normalizeNamespaceMarkers(std::string& str) { - std::size_t pos = str.find( "::" ); - while ( pos != std::string::npos ) { - str.replace( pos, 2, "." ); - pos += 1; - pos = str.find( "::", pos ); - } - } - - } // anonymous namespace - - JunitReporter::JunitReporter( ReporterConfig&& _config ) - : CumulativeReporterBase( CATCH_MOVE(_config) ), - xml( m_stream ) - { - m_preferences.shouldRedirectStdOut = true; - m_preferences.shouldReportAllAssertions = false; - m_shouldStoreSuccesfulAssertions = false; - } - - std::string JunitReporter::getDescription() { - return "Reports test results in an XML format that looks like Ant's junitreport target"; - } - - void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { - CumulativeReporterBase::testRunStarting( runInfo ); - xml.startElement( "testsuites" ); - suiteTimer.start(); - stdOutForSuite.clear(); - stdErrForSuite.clear(); - unexpectedExceptions = 0; - } - - void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { - m_okToFail = testCaseInfo.okToFail(); - } - - void JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { - if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) - unexpectedExceptions++; - CumulativeReporterBase::assertionEnded( assertionStats ); - } - - void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { - stdOutForSuite += testCaseStats.stdOut; - stdErrForSuite += testCaseStats.stdErr; - CumulativeReporterBase::testCaseEnded( testCaseStats ); - } - - void JunitReporter::testRunEndedCumulative() { - const auto suiteTime = suiteTimer.getElapsedSeconds(); - writeRun( *m_testRun, suiteTime ); - xml.endElement(); - } - - void JunitReporter::writeRun( TestRunNode const& testRunNode, double suiteTime ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); - - TestRunStats const& stats = testRunNode.value; - xml.writeAttribute( "name"_sr, stats.runInfo.name ); - xml.writeAttribute( "errors"_sr, unexpectedExceptions ); - xml.writeAttribute( "failures"_sr, stats.totals.assertions.failed-unexpectedExceptions ); - xml.writeAttribute( "skipped"_sr, stats.totals.assertions.skipped ); - xml.writeAttribute( "tests"_sr, stats.totals.assertions.total() ); - xml.writeAttribute( "hostname"_sr, "tbd"_sr ); // !TBD - if( m_config->showDurations() == ShowDurations::Never ) - xml.writeAttribute( "time"_sr, ""_sr ); - else - xml.writeAttribute( "time"_sr, formatDuration( suiteTime ) ); - xml.writeAttribute( "timestamp"_sr, getCurrentTimestamp() ); - - // Write properties - { - auto properties = xml.scopedElement("properties"); - xml.scopedElement("property") - .writeAttribute("name"_sr, "random-seed"_sr) - .writeAttribute("value"_sr, m_config->rngSeed()); - if (m_config->testSpec().hasFilters()) { - xml.scopedElement("property") - .writeAttribute("name"_sr, "filters"_sr) - .writeAttribute("value"_sr, m_config->testSpec()); - } - } - - // Write test cases - for( auto const& child : testRunNode.children ) - writeTestCase( *child ); - - xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), XmlFormatting::Newline ); - xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), XmlFormatting::Newline ); - } - - void JunitReporter::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 = - static_cast( stats.testInfo->className ); - - if( className.empty() ) { - className = fileNameTag(stats.testInfo->tags); - if ( className.empty() ) { - className = "global"; - } - } - - if ( !m_config->name().empty() ) - className = static_cast(m_config->name()) + '.' + className; - - normalizeNamespaceMarkers(className); - - writeSection( className, "", rootSection, stats.testInfo->okToFail() ); - } - - void JunitReporter::writeSection( std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode, - bool testOkToFail) { - std::string name = trim( sectionNode.stats.sectionInfo.name ); - if( !rootName.empty() ) - name = rootName + '/' + name; - - if ( sectionNode.stats.assertions.total() > 0 - || !sectionNode.stdOut.empty() - || !sectionNode.stdErr.empty() ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); - if( className.empty() ) { - xml.writeAttribute( "classname"_sr, name ); - xml.writeAttribute( "name"_sr, "root"_sr ); - } - else { - xml.writeAttribute( "classname"_sr, className ); - xml.writeAttribute( "name"_sr, name ); - } - xml.writeAttribute( "time"_sr, formatDuration( sectionNode.stats.durationInSeconds ) ); - // This is not ideal, but it should be enough to mimic gtest's - // junit output. - // Ideally the JUnit reporter would also handle `skipTest` - // events and write those out appropriately. - xml.writeAttribute( "status"_sr, "run"_sr ); - - if (sectionNode.stats.assertions.failedButOk) { - xml.scopedElement("skipped") - .writeAttribute("message", "TEST_CASE tagged with !mayfail"); - } - - writeAssertions( sectionNode ); - - - if( !sectionNode.stdOut.empty() ) - xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), XmlFormatting::Newline ); - if( !sectionNode.stdErr.empty() ) - xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), XmlFormatting::Newline ); - } - for( auto const& childNode : sectionNode.childSections ) - if( className.empty() ) - writeSection( name, "", *childNode, testOkToFail ); - else - writeSection( className, name, *childNode, testOkToFail ); - } - - void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { - for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) { - if (assertionOrBenchmark.isAssertion()) { - writeAssertion(assertionOrBenchmark.asAssertion()); - } - } - } - - void JunitReporter::writeAssertion( AssertionStats const& stats ) { - AssertionResult const& result = stats.assertionResult; - if ( !result.isOk() || - result.getResultType() == ResultWas::ExplicitSkip ) { - std::string elementName; - switch( result.getResultType() ) { - case ResultWas::ThrewException: - case ResultWas::FatalErrorCondition: - elementName = "error"; - break; - case ResultWas::ExplicitFailure: - case ResultWas::ExpressionFailed: - case ResultWas::DidntThrowException: - elementName = "failure"; - break; - case ResultWas::ExplicitSkip: - elementName = "skipped"; - break; - // We should never see these here: - case ResultWas::Info: - case ResultWas::Warning: - case ResultWas::Ok: - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - elementName = "internalError"; - break; - } - - XmlWriter::ScopedElement e = xml.scopedElement( elementName ); - - xml.writeAttribute( "message"_sr, result.getExpression() ); - xml.writeAttribute( "type"_sr, result.getTestMacroName() ); - - ReusableStringStream rss; - if ( result.getResultType() == ResultWas::ExplicitSkip ) { - rss << "SKIPPED\n"; - } else { - rss << "FAILED" << ":\n"; - if (result.hasExpression()) { - rss << " "; - rss << result.getExpressionInMacro(); - rss << '\n'; - } - if (result.hasExpandedExpression()) { - rss << "with expansion:\n"; - rss << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n'; - } - } - - if( result.hasMessage() ) - rss << result.getMessage() << '\n'; - for( auto const& msg : stats.infoMessages ) - if( msg.type == ResultWas::Info ) - rss << msg.message << '\n'; - - rss << "at " << result.getSourceInfo(); - xml.writeText( rss.str(), XmlFormatting::Newline ); - } - } - -} // end namespace Catch - - - - -#include - -namespace Catch { - void MultiReporter::updatePreferences(IEventListener const& reporterish) { - m_preferences.shouldRedirectStdOut |= - reporterish.getPreferences().shouldRedirectStdOut; - m_preferences.shouldReportAllAssertions |= - reporterish.getPreferences().shouldReportAllAssertions; - } - - void MultiReporter::addListener( IEventListenerPtr&& listener ) { - updatePreferences(*listener); - m_reporterLikes.insert(m_reporterLikes.begin() + m_insertedListeners, CATCH_MOVE(listener) ); - ++m_insertedListeners; - } - - void MultiReporter::addReporter( IEventListenerPtr&& reporter ) { - updatePreferences(*reporter); - - // We will need to output the captured stdout if there are reporters - // that do not want it captured. - // We do not consider listeners, because it is generally assumed that - // listeners are output-transparent, even though they can ask for stdout - // capture to do something with it. - m_haveNoncapturingReporters |= !reporter->getPreferences().shouldRedirectStdOut; - - // Reporters can always be placed to the back without breaking the - // reporting order - m_reporterLikes.push_back( CATCH_MOVE( reporter ) ); - } - - void MultiReporter::noMatchingTestCases( StringRef unmatchedSpec ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->noMatchingTestCases( unmatchedSpec ); - } - } - - void MultiReporter::fatalErrorEncountered( StringRef error ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->fatalErrorEncountered( error ); - } - } - - void MultiReporter::reportInvalidTestSpec( StringRef arg ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->reportInvalidTestSpec( arg ); - } - } - - void MultiReporter::benchmarkPreparing( StringRef name ) { - for (auto& reporterish : m_reporterLikes) { - reporterish->benchmarkPreparing(name); - } - } - void MultiReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->benchmarkStarting( benchmarkInfo ); - } - } - void MultiReporter::benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->benchmarkEnded( benchmarkStats ); - } - } - - void MultiReporter::benchmarkFailed( StringRef error ) { - for (auto& reporterish : m_reporterLikes) { - reporterish->benchmarkFailed(error); - } - } - - void MultiReporter::testRunStarting( TestRunInfo const& testRunInfo ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->testRunStarting( testRunInfo ); - } - } - - void MultiReporter::testCaseStarting( TestCaseInfo const& testInfo ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->testCaseStarting( testInfo ); - } - } - - void - MultiReporter::testCasePartialStarting( TestCaseInfo const& testInfo, - uint64_t partNumber ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->testCasePartialStarting( testInfo, partNumber ); - } - } - - void MultiReporter::sectionStarting( SectionInfo const& sectionInfo ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->sectionStarting( sectionInfo ); - } - } - - void MultiReporter::assertionStarting( AssertionInfo const& assertionInfo ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->assertionStarting( assertionInfo ); - } - } - - void MultiReporter::assertionEnded( AssertionStats const& assertionStats ) { - const bool reportByDefault = - assertionStats.assertionResult.getResultType() != ResultWas::Ok || - m_config->includeSuccessfulResults(); - - for ( auto & reporterish : m_reporterLikes ) { - if ( reportByDefault || - reporterish->getPreferences().shouldReportAllAssertions ) { - reporterish->assertionEnded( assertionStats ); - } - } - } - - void MultiReporter::sectionEnded( SectionStats const& sectionStats ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->sectionEnded( sectionStats ); - } - } - - void MultiReporter::testCasePartialEnded( TestCaseStats const& testStats, - uint64_t partNumber ) { - if ( m_preferences.shouldRedirectStdOut && - m_haveNoncapturingReporters ) { - if ( !testStats.stdOut.empty() ) { - Catch::cout() << testStats.stdOut << std::flush; - } - if ( !testStats.stdErr.empty() ) { - Catch::cerr() << testStats.stdErr << std::flush; - } - } - - for ( auto& reporterish : m_reporterLikes ) { - reporterish->testCasePartialEnded( testStats, partNumber ); - } - } - - void MultiReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->testCaseEnded( testCaseStats ); - } - } - - void MultiReporter::testRunEnded( TestRunStats const& testRunStats ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->testRunEnded( testRunStats ); - } - } - - - void MultiReporter::skipTest( TestCaseInfo const& testInfo ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->skipTest( testInfo ); - } - } - - void MultiReporter::listReporters(std::vector const& descriptions) { - for (auto& reporterish : m_reporterLikes) { - reporterish->listReporters(descriptions); - } - } - - void MultiReporter::listListeners( - std::vector const& descriptions ) { - for ( auto& reporterish : m_reporterLikes ) { - reporterish->listListeners( descriptions ); - } - } - - void MultiReporter::listTests(std::vector const& tests) { - for (auto& reporterish : m_reporterLikes) { - reporterish->listTests(tests); - } - } - - void MultiReporter::listTags(std::vector const& tags) { - for (auto& reporterish : m_reporterLikes) { - reporterish->listTags(tags); - } - } - -} // end namespace Catch - - - - - -namespace Catch { - namespace Detail { - - void registerReporterImpl( std::string const& name, - IReporterFactoryPtr reporterPtr ) { - CATCH_TRY { - getMutableRegistryHub().registerReporter( - name, CATCH_MOVE( reporterPtr ) ); - } - CATCH_CATCH_ALL { - // Do not throw when constructing global objects, instead - // register the exception to be processed later - getMutableRegistryHub().registerStartupException(); - } - } - - void registerListenerImpl( Detail::unique_ptr listenerFactory ) { - getMutableRegistryHub().registerListener( CATCH_MOVE(listenerFactory) ); - } - - - } // namespace Detail -} // namespace Catch - - - - -#include - -namespace Catch { - - namespace { - std::string createMetadataString(IConfig const& config) { - ReusableStringStream sstr; - if ( config.testSpec().hasFilters() ) { - sstr << "filters='" - << config.testSpec() - << "' "; - } - sstr << "rng-seed=" << config.rngSeed(); - return sstr.str(); - } - } - - void SonarQubeReporter::testRunStarting(TestRunInfo const& testRunInfo) { - CumulativeReporterBase::testRunStarting(testRunInfo); - - xml.writeComment( createMetadataString( *m_config ) ); - xml.startElement("testExecutions"); - xml.writeAttribute("version"_sr, '1'); - } - - void SonarQubeReporter::writeRun( TestRunNode const& runNode ) { - std::map> testsPerFile; - - for ( auto const& child : runNode.children ) { - testsPerFile[child->value.testInfo->lineInfo.file].push_back( - child.get() ); - } - - for ( auto const& kv : testsPerFile ) { - writeTestFile( kv.first, kv.second ); - } - } - - void SonarQubeReporter::writeTestFile(StringRef filename, std::vector const& testCaseNodes) { - XmlWriter::ScopedElement e = xml.scopedElement("file"); - xml.writeAttribute("path"_sr, filename); - - for (auto const& child : testCaseNodes) - writeTestCase(*child); - } - - void SonarQubeReporter::writeTestCase(TestCaseNode const& testCaseNode) { - // 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(); - writeSection("", rootSection, testCaseNode.value.testInfo->okToFail()); - } - - void SonarQubeReporter::writeSection(std::string const& rootName, SectionNode const& sectionNode, bool okToFail) { - std::string name = trim(sectionNode.stats.sectionInfo.name); - if (!rootName.empty()) - name = rootName + '/' + name; - - if ( sectionNode.stats.assertions.total() > 0 - || !sectionNode.stdOut.empty() - || !sectionNode.stdErr.empty() ) { - XmlWriter::ScopedElement e = xml.scopedElement("testCase"); - xml.writeAttribute("name"_sr, name); - xml.writeAttribute("duration"_sr, static_cast(sectionNode.stats.durationInSeconds * 1000)); - - writeAssertions(sectionNode, okToFail); - } - - for (auto const& childNode : sectionNode.childSections) - writeSection(name, *childNode, okToFail); - } - - void SonarQubeReporter::writeAssertions(SectionNode const& sectionNode, bool okToFail) { - for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) { - if (assertionOrBenchmark.isAssertion()) { - writeAssertion(assertionOrBenchmark.asAssertion(), okToFail); - } - } - } - - void SonarQubeReporter::writeAssertion(AssertionStats const& stats, bool okToFail) { - AssertionResult const& result = stats.assertionResult; - if ( !result.isOk() || - result.getResultType() == ResultWas::ExplicitSkip ) { - std::string elementName; - if (okToFail) { - elementName = "skipped"; - } else { - switch (result.getResultType()) { - case ResultWas::ThrewException: - case ResultWas::FatalErrorCondition: - elementName = "error"; - break; - case ResultWas::ExplicitFailure: - case ResultWas::ExpressionFailed: - case ResultWas::DidntThrowException: - elementName = "failure"; - break; - case ResultWas::ExplicitSkip: - elementName = "skipped"; - break; - // We should never see these here: - case ResultWas::Info: - case ResultWas::Warning: - case ResultWas::Ok: - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - elementName = "internalError"; - break; - } - } - - XmlWriter::ScopedElement e = xml.scopedElement(elementName); - - ReusableStringStream messageRss; - messageRss << result.getTestMacroName() << '(' << result.getExpression() << ')'; - xml.writeAttribute("message"_sr, messageRss.str()); - - ReusableStringStream textRss; - if ( result.getResultType() == ResultWas::ExplicitSkip ) { - textRss << "SKIPPED\n"; - } else { - textRss << "FAILED:\n"; - if (result.hasExpression()) { - textRss << '\t' << result.getExpressionInMacro() << '\n'; - } - if (result.hasExpandedExpression()) { - textRss << "with expansion:\n\t" << result.getExpandedExpression() << '\n'; - } - } - - if (result.hasMessage()) - textRss << result.getMessage() << '\n'; - - for (auto const& msg : stats.infoMessages) - if (msg.type == ResultWas::Info) - textRss << msg.message << '\n'; - - textRss << "at " << result.getSourceInfo(); - xml.writeText(textRss.str(), XmlFormatting::Newline); - } - } - -} // end namespace Catch - - - -namespace Catch { - - StreamingReporterBase::~StreamingReporterBase() = default; - - void - StreamingReporterBase::testRunStarting( TestRunInfo const& _testRunInfo ) { - currentTestRunInfo = _testRunInfo; - } - - void StreamingReporterBase::testRunEnded( TestRunStats const& ) { - currentTestCaseInfo = nullptr; - } - -} // end namespace Catch - - - -#include -#include - -namespace Catch { - - namespace { - // Yes, this has to be outside the class and namespaced by naming. - // Making older compiler happy is hard. - static constexpr StringRef tapFailedString = "not ok"_sr; - static constexpr StringRef tapPassedString = "ok"_sr; - static constexpr Colour::Code tapDimColour = Colour::FileName; - - class TapAssertionPrinter { - public: - TapAssertionPrinter& operator= (TapAssertionPrinter const&) = delete; - TapAssertionPrinter(TapAssertionPrinter const&) = delete; - TapAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter, ColourImpl* colour_) - : stream(_stream) - , result(_stats.assertionResult) - , messages(_stats.infoMessages) - , itMessage(_stats.infoMessages.begin()) - , printInfoMessages(true) - , counter(_counter) - , colourImpl( colour_ ) {} - - void print() { - itMessage = messages.begin(); - - switch (result.getResultType()) { - case ResultWas::Ok: - printResultType(tapPassedString); - printOriginalExpression(); - printReconstructedExpression(); - if (!result.hasExpression()) - printRemainingMessages(Colour::None); - else - printRemainingMessages(); - break; - case ResultWas::ExpressionFailed: - if (result.isOk()) { - printResultType(tapPassedString); - } else { - printResultType(tapFailedString); - } - printOriginalExpression(); - printReconstructedExpression(); - if (result.isOk()) { - printIssue(" # TODO"); - } - printRemainingMessages(); - break; - case ResultWas::ThrewException: - printResultType(tapFailedString); - printIssue("unexpected exception with message:"_sr); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::FatalErrorCondition: - printResultType(tapFailedString); - printIssue("fatal error condition with message:"_sr); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::DidntThrowException: - printResultType(tapFailedString); - printIssue("expected exception, got none"_sr); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::Info: - printResultType("info"_sr); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::Warning: - printResultType("warning"_sr); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::ExplicitFailure: - printResultType(tapFailedString); - printIssue("explicitly"_sr); - printRemainingMessages(Colour::None); - break; - case ResultWas::ExplicitSkip: - printResultType(tapPassedString); - printIssue(" # SKIP"_sr); - printMessage(); - printRemainingMessages(); - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - printResultType("** internal error **"_sr); - break; - } - } - - private: - void printResultType(StringRef passOrFail) const { - if (!passOrFail.empty()) { - stream << passOrFail << ' ' << counter << " -"; - } - } - - void printIssue(StringRef issue) const { - stream << ' ' << issue; - } - - void printExpressionWas() { - if (result.hasExpression()) { - stream << ';'; - stream << colourImpl->guardColour( tapDimColour ) - << " expression was:"; - printOriginalExpression(); - } - } - - void printOriginalExpression() const { - if (result.hasExpression()) { - stream << ' ' << result.getExpression(); - } - } - - void printReconstructedExpression() const { - if (result.hasExpandedExpression()) { - stream << colourImpl->guardColour( tapDimColour ) << " for: "; - - std::string expr = result.getExpandedExpression(); - std::replace(expr.begin(), expr.end(), '\n', ' '); - stream << expr; - } - } - - void printMessage() { - if (itMessage != messages.end()) { - stream << " '" << itMessage->message << '\''; - ++itMessage; - } - } - - void printRemainingMessages(Colour::Code colour = tapDimColour) { - if (itMessage == messages.end()) { - return; - } - - // using messages.end() directly (or auto) yields compilation error: - std::vector::const_iterator itEnd = messages.end(); - const std::size_t N = static_cast(itEnd - itMessage); - - stream << colourImpl->guardColour( colour ) << " with " - << pluralise( N, "message"_sr ) << ':'; - - 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) { - stream << colourImpl->guardColour(tapDimColour) << " and"; - } - } - } - } - - private: - std::ostream& stream; - AssertionResult const& result; - std::vector const& messages; - std::vector::const_iterator itMessage; - bool printInfoMessages; - std::size_t counter; - ColourImpl* colourImpl; - }; - - } // End anonymous namespace - - void TAPReporter::testRunStarting( TestRunInfo const& ) { - if ( m_config->testSpec().hasFilters() ) { - m_stream << "# filters: " << m_config->testSpec() << '\n'; - } - m_stream << "# rng-seed: " << m_config->rngSeed() << '\n'; - } - - void TAPReporter::noMatchingTestCases( StringRef unmatchedSpec ) { - m_stream << "# No test cases matched '" << unmatchedSpec << "'\n"; - } - - void TAPReporter::assertionEnded(AssertionStats const& _assertionStats) { - ++counter; - - m_stream << "# " << currentTestCaseInfo->name << '\n'; - TapAssertionPrinter printer(m_stream, _assertionStats, counter, m_colour.get()); - printer.print(); - - m_stream << '\n' << std::flush; - } - - void TAPReporter::testRunEnded(TestRunStats const& _testRunStats) { - m_stream << "1.." << _testRunStats.totals.assertions.total(); - if (_testRunStats.totals.testCases.total() == 0) { - m_stream << " # Skipped: No tests ran."; - } - m_stream << "\n\n" << std::flush; - StreamingReporterBase::testRunEnded(_testRunStats); - } - - - - -} // end namespace Catch - - - - -#include -#include - -namespace Catch { - - namespace { - // if string has a : in first line will set indent to follow it on - // subsequent lines - void printHeaderString(std::ostream& os, 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; - os << TextFlow::Column(_string) - .indent(indent + i) - .initialIndent(indent) << '\n'; - } - - std::string escape(StringRef str) { - std::string escaped = static_cast(str); - replaceInPlace(escaped, "|", "||"); - replaceInPlace(escaped, "'", "|'"); - replaceInPlace(escaped, "\n", "|n"); - replaceInPlace(escaped, "\r", "|r"); - replaceInPlace(escaped, "[", "|["); - replaceInPlace(escaped, "]", "|]"); - return escaped; - } - } // end anonymous namespace - - - TeamCityReporter::~TeamCityReporter() = default; - - void TeamCityReporter::testRunStarting( TestRunInfo const& runInfo ) { - m_stream << "##teamcity[testSuiteStarted name='" << escape( runInfo.name ) - << "']\n"; - } - - void TeamCityReporter::testRunEnded( TestRunStats const& runStats ) { - m_stream << "##teamcity[testSuiteFinished name='" - << escape( runStats.runInfo.name ) << "']\n"; - } - - void TeamCityReporter::assertionEnded(AssertionStats const& assertionStats) { - AssertionResult const& result = assertionStats.assertionResult; - if ( !result.isOk() || - result.getResultType() == ResultWas::ExplicitSkip ) { - - ReusableStringStream msg; - if (!m_headerPrintedForThisSection) - printSectionHeader(msg.get()); - m_headerPrintedForThisSection = true; - - msg << result.getSourceInfo() << '\n'; - - switch (result.getResultType()) { - case ResultWas::ExpressionFailed: - msg << "expression failed"; - break; - case ResultWas::ThrewException: - msg << "unexpected exception"; - break; - case ResultWas::FatalErrorCondition: - msg << "fatal error condition"; - break; - case ResultWas::DidntThrowException: - msg << "no exception was thrown where one was expected"; - break; - case ResultWas::ExplicitFailure: - msg << "explicit failure"; - break; - case ResultWas::ExplicitSkip: - msg << "explicit skip"; - break; - - // We shouldn't get here because of the isOk() test - case ResultWas::Ok: - case ResultWas::Info: - case ResultWas::Warning: - CATCH_ERROR("Internal error in TeamCity reporter"); - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - CATCH_ERROR("Not implemented"); - } - if (assertionStats.infoMessages.size() == 1) - msg << " with message:"; - if (assertionStats.infoMessages.size() > 1) - msg << " with messages:"; - for (auto const& messageInfo : assertionStats.infoMessages) - msg << "\n \"" << messageInfo.message << '"'; - - - if (result.hasExpression()) { - msg << - "\n " << result.getExpressionInMacro() << "\n" - "with expansion:\n" - " " << result.getExpandedExpression() << '\n'; - } - - if ( result.getResultType() == ResultWas::ExplicitSkip ) { - m_stream << "##teamcity[testIgnored"; - } else if ( currentTestCaseInfo->okToFail() ) { - msg << "- failure ignore as test marked as 'ok to fail'\n"; - m_stream << "##teamcity[testIgnored"; - } else { - m_stream << "##teamcity[testFailed"; - } - m_stream << " name='" << escape( currentTestCaseInfo->name ) << '\'' - << " message='" << escape( msg.str() ) << '\'' << "]\n"; - } - m_stream.flush(); - } - - void TeamCityReporter::testCaseStarting(TestCaseInfo const& testInfo) { - m_testTimer.start(); - StreamingReporterBase::testCaseStarting(testInfo); - m_stream << "##teamcity[testStarted name='" - << escape(testInfo.name) << "']\n"; - m_stream.flush(); - } - - void TeamCityReporter::testCaseEnded(TestCaseStats const& testCaseStats) { - StreamingReporterBase::testCaseEnded(testCaseStats); - auto const& testCaseInfo = *testCaseStats.testInfo; - if (!testCaseStats.stdOut.empty()) - m_stream << "##teamcity[testStdOut name='" - << escape(testCaseInfo.name) - << "' out='" << escape(testCaseStats.stdOut) << "']\n"; - if (!testCaseStats.stdErr.empty()) - m_stream << "##teamcity[testStdErr name='" - << escape(testCaseInfo.name) - << "' out='" << escape(testCaseStats.stdErr) << "']\n"; - m_stream << "##teamcity[testFinished name='" - << escape(testCaseInfo.name) << "' duration='" - << m_testTimer.getElapsedMilliseconds() << "']\n"; - m_stream.flush(); - } - - void TeamCityReporter::printSectionHeader(std::ostream& os) { - assert(!m_sectionStack.empty()); - - if (m_sectionStack.size() > 1) { - os << lineOfChars('-') << '\n'; - - std::vector::const_iterator - it = m_sectionStack.begin() + 1, // Skip first section (test case) - itEnd = m_sectionStack.end(); - for (; it != itEnd; ++it) - printHeaderString(os, it->name); - os << lineOfChars('-') << '\n'; - } - - SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; - - os << lineInfo << '\n'; - os << lineOfChars('.') << "\n\n"; - } - -} // end namespace Catch - - - - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch - // Note that 4062 (not all labels are handled - // and default is missing) is enabled -#endif - -namespace Catch { - XmlReporter::XmlReporter( ReporterConfig&& _config ) - : StreamingReporterBase( CATCH_MOVE(_config) ), - m_xml(m_stream) - { - m_preferences.shouldRedirectStdOut = true; - m_preferences.shouldReportAllAssertions = true; - } - - XmlReporter::~XmlReporter() = default; - - std::string XmlReporter::getDescription() { - return "Reports test results as an XML document"; - } - - std::string XmlReporter::getStylesheetRef() const { - return std::string(); - } - - void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { - m_xml - .writeAttribute( "filename"_sr, sourceInfo.file ) - .writeAttribute( "line"_sr, sourceInfo.line ); - } - - void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { - StreamingReporterBase::testRunStarting( testInfo ); - std::string stylesheetRef = getStylesheetRef(); - if( !stylesheetRef.empty() ) - m_xml.writeStylesheetRef( stylesheetRef ); - m_xml.startElement("Catch2TestRun") - .writeAttribute("name"_sr, m_config->name()) - .writeAttribute("rng-seed"_sr, m_config->rngSeed()) - .writeAttribute("xml-format-version"_sr, 3) - .writeAttribute("catch2-version"_sr, libraryVersion()); - if ( m_config->testSpec().hasFilters() ) { - m_xml.writeAttribute( "filters"_sr, m_config->testSpec() ); - } - } - - void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { - StreamingReporterBase::testCaseStarting(testInfo); - m_xml.startElement( "TestCase" ) - .writeAttribute( "name"_sr, trim( StringRef(testInfo.name) ) ) - .writeAttribute( "tags"_sr, testInfo.tagsAsString() ); - - writeSourceInfo( testInfo.lineInfo ); - - if ( m_config->showDurations() == ShowDurations::Always ) - m_testCaseTimer.start(); - m_xml.ensureTagClosed(); - } - - void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { - StreamingReporterBase::sectionStarting( sectionInfo ); - if( m_sectionDepth++ > 0 ) { - m_xml.startElement( "Section" ) - .writeAttribute( "name"_sr, trim( StringRef(sectionInfo.name) ) ); - writeSourceInfo( sectionInfo.lineInfo ); - m_xml.ensureTagClosed(); - } - } - - void XmlReporter::assertionStarting( AssertionInfo const& ) { } - - void XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { - - AssertionResult const& result = assertionStats.assertionResult; - - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - if( includeResults || result.getResultType() == ResultWas::Warning ) { - // Print any info messages in tags. - for( auto const& msg : assertionStats.infoMessages ) { - if( msg.type == ResultWas::Info && includeResults ) { - auto t = m_xml.scopedElement( "Info" ); - writeSourceInfo( msg.lineInfo ); - t.writeText( msg.message ); - } else if ( msg.type == ResultWas::Warning ) { - auto t = m_xml.scopedElement( "Warning" ); - writeSourceInfo( msg.lineInfo ); - t.writeText( msg.message ); - } - } - } - - // Drop out if result was successful but we're not printing them. - if ( !includeResults && result.getResultType() != ResultWas::Warning && - result.getResultType() != ResultWas::ExplicitSkip ) { - return; - } - - // Print the expression if there is one. - if( result.hasExpression() ) { - m_xml.startElement( "Expression" ) - .writeAttribute( "success"_sr, result.succeeded() ) - .writeAttribute( "type"_sr, 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; - case ResultWas::ExplicitSkip: - m_xml.startElement( "Skip" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - default: - break; - } - - if( result.hasExpression() ) - m_xml.endElement(); - } - - void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { - StreamingReporterBase::sectionEnded( sectionStats ); - if ( --m_sectionDepth > 0 ) { - { - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); - e.writeAttribute( "successes"_sr, sectionStats.assertions.passed ); - e.writeAttribute( "failures"_sr, sectionStats.assertions.failed ); - e.writeAttribute( "expectedFailures"_sr, sectionStats.assertions.failedButOk ); - e.writeAttribute( "skipped"_sr, sectionStats.assertions.skipped > 0 ); - - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds"_sr, sectionStats.durationInSeconds ); - } - // Ends assertion tag - m_xml.endElement(); - } - } - - void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { - StreamingReporterBase::testCaseEnded( testCaseStats ); - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); - e.writeAttribute( "success"_sr, testCaseStats.totals.assertions.allOk() ); - e.writeAttribute( "skips"_sr, testCaseStats.totals.assertions.skipped ); - - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds"_sr, m_testCaseTimer.getElapsedSeconds() ); - if( !testCaseStats.stdOut.empty() ) - m_xml.scopedElement( "StdOut" ).writeText( trim( StringRef(testCaseStats.stdOut) ), XmlFormatting::Newline ); - if( !testCaseStats.stdErr.empty() ) - m_xml.scopedElement( "StdErr" ).writeText( trim( StringRef(testCaseStats.stdErr) ), XmlFormatting::Newline ); - - m_xml.endElement(); - } - - void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { - StreamingReporterBase::testRunEnded( testRunStats ); - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes"_sr, testRunStats.totals.assertions.passed ) - .writeAttribute( "failures"_sr, testRunStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures"_sr, testRunStats.totals.assertions.failedButOk ) - .writeAttribute( "skips"_sr, testRunStats.totals.assertions.skipped ); - m_xml.scopedElement( "OverallResultsCases") - .writeAttribute( "successes"_sr, testRunStats.totals.testCases.passed ) - .writeAttribute( "failures"_sr, testRunStats.totals.testCases.failed ) - .writeAttribute( "expectedFailures"_sr, testRunStats.totals.testCases.failedButOk ) - .writeAttribute( "skips"_sr, testRunStats.totals.testCases.skipped ); - m_xml.endElement(); - } - - void XmlReporter::benchmarkPreparing( StringRef name ) { - m_xml.startElement("BenchmarkResults") - .writeAttribute("name"_sr, name); - } - - void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) { - m_xml.writeAttribute("samples"_sr, info.samples) - .writeAttribute("resamples"_sr, info.resamples) - .writeAttribute("iterations"_sr, info.iterations) - .writeAttribute("clockResolution"_sr, info.clockResolution) - .writeAttribute("estimatedDuration"_sr, info.estimatedDuration) - .writeComment("All values in nano seconds"_sr); - } - - void XmlReporter::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) { - m_xml.scopedElement("mean") - .writeAttribute("value"_sr, benchmarkStats.mean.point.count()) - .writeAttribute("lowerBound"_sr, benchmarkStats.mean.lower_bound.count()) - .writeAttribute("upperBound"_sr, benchmarkStats.mean.upper_bound.count()) - .writeAttribute("ci"_sr, benchmarkStats.mean.confidence_interval); - m_xml.scopedElement("standardDeviation") - .writeAttribute("value"_sr, benchmarkStats.standardDeviation.point.count()) - .writeAttribute("lowerBound"_sr, benchmarkStats.standardDeviation.lower_bound.count()) - .writeAttribute("upperBound"_sr, benchmarkStats.standardDeviation.upper_bound.count()) - .writeAttribute("ci"_sr, benchmarkStats.standardDeviation.confidence_interval); - m_xml.scopedElement("outliers") - .writeAttribute("variance"_sr, benchmarkStats.outlierVariance) - .writeAttribute("lowMild"_sr, benchmarkStats.outliers.low_mild) - .writeAttribute("lowSevere"_sr, benchmarkStats.outliers.low_severe) - .writeAttribute("highMild"_sr, benchmarkStats.outliers.high_mild) - .writeAttribute("highSevere"_sr, benchmarkStats.outliers.high_severe); - m_xml.endElement(); - } - - void XmlReporter::benchmarkFailed(StringRef error) { - m_xml.scopedElement("failed"). - writeAttribute("message"_sr, error); - m_xml.endElement(); - } - - void XmlReporter::listReporters(std::vector const& descriptions) { - auto outerTag = m_xml.scopedElement("AvailableReporters"); - for (auto const& reporter : descriptions) { - auto inner = m_xml.scopedElement("Reporter"); - m_xml.startElement("Name", XmlFormatting::Indent) - .writeText(reporter.name, XmlFormatting::None) - .endElement(XmlFormatting::Newline); - m_xml.startElement("Description", XmlFormatting::Indent) - .writeText(reporter.description, XmlFormatting::None) - .endElement(XmlFormatting::Newline); - } - } - - void XmlReporter::listListeners(std::vector const& descriptions) { - auto outerTag = m_xml.scopedElement( "RegisteredListeners" ); - for ( auto const& listener : descriptions ) { - auto inner = m_xml.scopedElement( "Listener" ); - m_xml.startElement( "Name", XmlFormatting::Indent ) - .writeText( listener.name, XmlFormatting::None ) - .endElement( XmlFormatting::Newline ); - m_xml.startElement( "Description", XmlFormatting::Indent ) - .writeText( listener.description, XmlFormatting::None ) - .endElement( XmlFormatting::Newline ); - } - } - - void XmlReporter::listTests(std::vector const& tests) { - auto outerTag = m_xml.scopedElement("MatchingTests"); - for (auto const& test : tests) { - auto innerTag = m_xml.scopedElement("TestCase"); - auto const& testInfo = test.getTestCaseInfo(); - m_xml.startElement("Name", XmlFormatting::Indent) - .writeText(testInfo.name, XmlFormatting::None) - .endElement(XmlFormatting::Newline); - m_xml.startElement("ClassName", XmlFormatting::Indent) - .writeText(testInfo.className, XmlFormatting::None) - .endElement(XmlFormatting::Newline); - m_xml.startElement("Tags", XmlFormatting::Indent) - .writeText(testInfo.tagsAsString(), XmlFormatting::None) - .endElement(XmlFormatting::Newline); - - auto sourceTag = m_xml.scopedElement("SourceInfo"); - m_xml.startElement("File", XmlFormatting::Indent) - .writeText(testInfo.lineInfo.file, XmlFormatting::None) - .endElement(XmlFormatting::Newline); - m_xml.startElement("Line", XmlFormatting::Indent) - .writeText(std::to_string(testInfo.lineInfo.line), XmlFormatting::None) - .endElement(XmlFormatting::Newline); - } - } - - void XmlReporter::listTags(std::vector const& tags) { - auto outerTag = m_xml.scopedElement("TagsFromMatchingTests"); - for (auto const& tag : tags) { - auto innerTag = m_xml.scopedElement("Tag"); - m_xml.startElement("Count", XmlFormatting::Indent) - .writeText(std::to_string(tag.count), XmlFormatting::None) - .endElement(XmlFormatting::Newline); - auto aliasTag = m_xml.scopedElement("Aliases"); - for (auto const& alias : tag.spellings) { - m_xml.startElement("Alias", XmlFormatting::Indent) - .writeText(alias, XmlFormatting::None) - .endElement(XmlFormatting::Newline); - } - } - } - -} // end namespace Catch - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif diff --git a/extern/catch2/catch_amalgamated.hpp b/extern/catch2/catch_amalgamated.hpp deleted file mode 100644 index b7c768b8..00000000 --- a/extern/catch2/catch_amalgamated.hpp +++ /dev/null @@ -1,14106 +0,0 @@ - -// Copyright Catch2 Authors -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE.txt or copy at -// https://www.boost.org/LICENSE_1_0.txt) - -// SPDX-License-Identifier: BSL-1.0 - -// Catch v3.7.1 -// Generated: 2024-09-17 10:36:40.974985 -// ---------------------------------------------------------- -// This file is an amalgamation of multiple different files. -// You probably shouldn't edit it directly. -// ---------------------------------------------------------- -#ifndef CATCH_AMALGAMATED_HPP_INCLUDED -#define CATCH_AMALGAMATED_HPP_INCLUDED - - -/** \file - * This is a convenience header for Catch2. It includes **all** of Catch2 headers. - * - * Generally the Catch2 users should use specific includes they need, - * but this header can be used instead for ease-of-experimentation, or - * just plain convenience, at the cost of (significantly) increased - * compilation times. - * - * When a new header is added to either the top level folder, or to the - * corresponding internal subfolder, it should be added here. Headers - * added to the various subparts (e.g. matchers, generators, etc...), - * should go their respective catch-all headers. - */ - -#ifndef CATCH_ALL_HPP_INCLUDED -#define CATCH_ALL_HPP_INCLUDED - - - -/** \file - * This is a convenience header for Catch2's benchmarking. It includes - * **all** of Catch2 headers related to benchmarking. - * - * Generally the Catch2 users should use specific includes they need, - * but this header can be used instead for ease-of-experimentation, or - * just plain convenience, at the cost of (significantly) increased - * compilation times. - * - * When a new header is added to either the `benchmark` folder, or to - * the corresponding internal (detail) subfolder, it should be added here. - */ - -#ifndef CATCH_BENCHMARK_ALL_HPP_INCLUDED -#define CATCH_BENCHMARK_ALL_HPP_INCLUDED - - - -// Adapted from donated nonius code. - -#ifndef CATCH_BENCHMARK_HPP_INCLUDED -#define CATCH_BENCHMARK_HPP_INCLUDED - - - -#ifndef CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED -#define CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED - -// Detect a number of compiler features - by compiler -// The following features are defined: -// -// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? -// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? -// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? -// **************** -// Note to maintainers: if new toggles are added please document them -// in configuration.md, too -// **************** - -// In general each macro has a _NO_ 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. - - - -#ifndef CATCH_PLATFORM_HPP_INCLUDED -#define CATCH_PLATFORM_HPP_INCLUDED - -// See e.g.: -// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html -#ifdef __APPLE__ -# ifndef __has_extension -# define __has_extension(x) 0 -# endif -# include -# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ - (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) -# define CATCH_PLATFORM_MAC -# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) -# define CATCH_PLATFORM_IPHONE -# endif - -#elif defined(linux) || defined(__linux) || defined(__linux__) -# define CATCH_PLATFORM_LINUX - -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) -# define CATCH_PLATFORM_WINDOWS - -# if defined( WINAPI_FAMILY ) && ( WINAPI_FAMILY == WINAPI_FAMILY_APP ) -# define CATCH_PLATFORM_WINDOWS_UWP -# endif - -#elif defined(__ORBIS__) || defined(__PROSPERO__) -# define CATCH_PLATFORM_PLAYSTATION - -#endif - -#endif // CATCH_PLATFORM_HPP_INCLUDED - -#ifdef __cplusplus - -# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define CATCH_CPP17_OR_GREATER -# endif - -# if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) -# define CATCH_CPP20_OR_GREATER -# endif - -#endif - -// Only GCC compiler should be used in this block, so other compilers trying to -// mask themselves as GCC should be ignored. -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) && !defined(__NVCOMPILER) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) - -// This only works on GCC 9+. so we have to also add a global suppression of Wparentheses -// for older versions of GCC. -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ - _Pragma( "GCC diagnostic ignored \"-Wunused-result\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - _Pragma( "GCC diagnostic ignored \"-Wunused-variable\"" ) - -# define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ - _Pragma( "GCC diagnostic ignored \"-Wuseless-cast\"" ) - -# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ - _Pragma( "GCC diagnostic ignored \"-Wshadow\"" ) - -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) - -#endif - -#if defined(__NVCOMPILER) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "diag push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "diag pop" ) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "diag_suppress declared_but_not_referenced" ) -#endif - -#if defined(__CUDACC__) && !defined(__clang__) -# ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ -// New pragmas introduced in CUDA 11.5+ -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "nv_diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "nv_diagnostic pop" ) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "nv_diag_suppress 177" ) -# else -# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "diag_suppress 177" ) -# endif -#endif - -// clang-cl defines _MSC_VER as well as __clang__, which could cause the -// start/stop internal suppression macros to be double defined. -#if defined(__clang__) && !defined(_MSC_VER) - -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) - -#endif // __clang__ && !_MSC_VER - -#if defined(__clang__) - -// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug -// which results in calls to destructors being emitted for each temporary, -// without a matching initialization. In practice, this can result in something -// like `std::string::~string` being called on an uninitialized value. -// -// For example, this code will likely segfault under IBM XL: -// ``` -// REQUIRE(std::string("12") + "34" == "1234") -// ``` -// -// Similarly, NVHPC's implementation of `__builtin_constant_p` has a bug which -// results in calls to the immediately evaluated lambda expressions to be -// reported as unevaluated lambdas. -// https://developer.nvidia.com/nvidia_bug/3321845. -// -// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. -# if !defined(__ibmxl__) && !defined(__CUDACC__) && !defined( __NVCOMPILER ) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ -# endif - - -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") - -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) - -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) - -# define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wcomma\"" ) - -# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wshadow\"" ) - -#endif // __clang__ - - -//////////////////////////////////////////////////////////////////////////////// -// We know some environments not to support full POSIX signals -#if defined( CATCH_PLATFORM_WINDOWS ) || \ - defined( CATCH_PLATFORM_PLAYSTATION ) || \ - defined( __CYGWIN__ ) || \ - defined( __QNX__ ) || \ - defined( __EMSCRIPTEN__ ) || \ - defined( __DJGPP__ ) || \ - defined( __OS400__ ) -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -#else -# define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Assume that some platforms do not support getenv. -#if defined( CATCH_PLATFORM_WINDOWS_UWP ) || \ - defined( CATCH_PLATFORM_PLAYSTATION ) || \ - defined( _GAMING_XBOX ) -# define CATCH_INTERNAL_CONFIG_NO_GETENV -#else -# define CATCH_INTERNAL_CONFIG_GETENV -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Android somehow still does not support std::to_string -#if defined(__ANDROID__) -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Not all Windows environments support SEH properly -#if defined(__MINGW32__) -# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH -#endif - -//////////////////////////////////////////////////////////////////////////////// -// PS4 -#if defined(__ORBIS__) -# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE -#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 -// some versions of cygwin (most) do not support std::to_string. Use the libstd check. -// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 -# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ - && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) - -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING - -# endif -#endif // __CYGWIN__ - -//////////////////////////////////////////////////////////////////////////////// -// Visual C++ -#if defined(_MSC_VER) - -// We want to defer to nvcc-specific warning suppression if we are compiled -// with nvcc masquerading for MSVC. -# if !defined( __CUDACC__ ) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - __pragma( warning( push ) ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - __pragma( warning( pop ) ) -# endif - -// Universal Windows platform does not support SEH -// Or console colours (or console at all...) -# if defined(CATCH_PLATFORM_WINDOWS_UWP) -# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 -# else -# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH -# endif - -// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ -// _MSVC_TRADITIONAL == 0 means new conformant preprocessor -// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor -# if !defined(__clang__) // Handle Clang masquerading for msvc -# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) -# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -# endif // MSVC_TRADITIONAL -# endif // __clang__ - -#endif // _MSC_VER - -#if defined(_REENTRANT) || defined(_MSC_VER) -// Enable async processing, as -pthread is specified or no additional linking is required -# define CATCH_INTERNAL_CONFIG_USE_ASYNC -#endif // _MSC_VER - -//////////////////////////////////////////////////////////////////////////////// -// Check if we are compiled with -fno-exceptions or equivalent -#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) -# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED -#endif - - -//////////////////////////////////////////////////////////////////////////////// -// Embarcadero C++Build -#if defined(__BORLANDC__) - #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// RTX is a special version of Windows that is real time. -// This means that it is detected as Windows, but does not provide -// the same set of capabilities as real Windows does. -#if defined(UNDER_RTSS) || defined(RTX64_BUILD) - #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH - #define CATCH_INTERNAL_CONFIG_NO_ASYNC - #define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 -#endif - -#if !defined(_GLIBCXX_USE_C99_MATH_TR1) -#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER -#endif - -// Various stdlib support checks that require __has_include -#if defined(__has_include) - // Check if string_view is available and usable - #if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW - #endif - - // Check if optional is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if byte is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # include - # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) - # define CATCH_INTERNAL_CONFIG_CPP17_BYTE - # endif - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if variant is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # if defined(__clang__) && (__clang_major__ < 8) - // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 - // fix should be in clang 8, workaround in libstdc++ 8.2 - # include - # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # define CATCH_CONFIG_NO_CPP17_VARIANT - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__clang__) && (__clang_major__ < 8) - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) -#endif // defined(__has_include) - - -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) -# define CATCH_CONFIG_WINDOWS_SEH -#endif -// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. -#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_CONFIG_POSIX_SIGNALS -#endif - -#if defined(CATCH_INTERNAL_CONFIG_GETENV) && !defined(CATCH_INTERNAL_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_GETENV) -# define CATCH_CONFIG_GETENV -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) -# define CATCH_CONFIG_CPP11_TO_STRING -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) -# define CATCH_CONFIG_CPP17_OPTIONAL -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) -# define CATCH_CONFIG_CPP17_STRING_VIEW -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) -# define CATCH_CONFIG_CPP17_VARIANT -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) -# define CATCH_CONFIG_CPP17_BYTE -#endif - - -#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) -# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE -#endif - -#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) -# define CATCH_CONFIG_NEW_CAPTURE -#endif - -#if !defined( CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED ) && \ - !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) && \ - !defined( CATCH_CONFIG_NO_DISABLE_EXCEPTIONS ) -# define CATCH_CONFIG_DISABLE_EXCEPTIONS -#endif - -#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) -# define CATCH_CONFIG_POLYFILL_ISNAN -#endif - -#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) -# define CATCH_CONFIG_USE_ASYNC -#endif - -#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) -# define CATCH_CONFIG_GLOBAL_NEXTAFTER -#endif - - -// Even if we do not think the compiler has that warning, we still have -// to provide a macro that can be used by the code. -#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS -#endif -#if !defined( CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS ) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif -#if !defined( CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS ) -# define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS -#endif -#if !defined( CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS ) -# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS -#endif - - -// The goal of this macro is to avoid evaluation of the arguments, but -// still have the compiler warn on problems inside... -#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) -#endif - -#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#elif defined(__clang__) && (__clang_major__ < 5) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif - - -#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -#define CATCH_TRY if ((true)) -#define CATCH_CATCH_ALL if ((false)) -#define CATCH_CATCH_ANON(type) if ((false)) -#else -#define CATCH_TRY try -#define CATCH_CATCH_ALL catch (...) -#define CATCH_CATCH_ANON(type) catch (type) -#endif - -#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) -#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#endif - -#if defined( CATCH_PLATFORM_WINDOWS ) && \ - !defined( CATCH_CONFIG_COLOUR_WIN32 ) && \ - !defined( CATCH_CONFIG_NO_COLOUR_WIN32 ) && \ - !defined( CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 ) -# define CATCH_CONFIG_COLOUR_WIN32 -#endif - -#if defined( CATCH_CONFIG_SHARED_LIBRARY ) && defined( _MSC_VER ) && \ - !defined( CATCH_CONFIG_STATIC ) -# ifdef Catch2_EXPORTS -# define CATCH_EXPORT //__declspec( dllexport ) // not needed -# else -# define CATCH_EXPORT __declspec( dllimport ) -# endif -#else -# define CATCH_EXPORT -#endif - -#endif // CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED - - -#ifndef CATCH_CONTEXT_HPP_INCLUDED -#define CATCH_CONTEXT_HPP_INCLUDED - - -namespace Catch { - - class IResultCapture; - class IConfig; - - class Context { - IConfig const* m_config = nullptr; - IResultCapture* m_resultCapture = nullptr; - - CATCH_EXPORT static Context* currentContext; - friend Context& getCurrentMutableContext(); - friend Context const& getCurrentContext(); - static void createContext(); - friend void cleanUpContext(); - - public: - constexpr IResultCapture* getResultCapture() const { - return m_resultCapture; - } - constexpr IConfig const* getConfig() const { return m_config; } - constexpr void setResultCapture( IResultCapture* resultCapture ) { - m_resultCapture = resultCapture; - } - constexpr void setConfig( IConfig const* config ) { m_config = config; } - - }; - - Context& getCurrentMutableContext(); - - inline Context const& getCurrentContext() { - // We duplicate the logic from `getCurrentMutableContext` here, - // to avoid paying the call overhead in debug mode. - if ( !Context::currentContext ) { Context::createContext(); } - // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) - return *Context::currentContext; - } - - void cleanUpContext(); - - class SimplePcg32; - SimplePcg32& sharedRng(); -} - -#endif // CATCH_CONTEXT_HPP_INCLUDED - - -#ifndef CATCH_MOVE_AND_FORWARD_HPP_INCLUDED -#define CATCH_MOVE_AND_FORWARD_HPP_INCLUDED - -#include - -//! Replacement for std::move with better compile time performance -#define CATCH_MOVE(...) static_cast&&>(__VA_ARGS__) - -//! Replacement for std::forward with better compile time performance -#define CATCH_FORWARD(...) static_cast(__VA_ARGS__) - -#endif // CATCH_MOVE_AND_FORWARD_HPP_INCLUDED - - -#ifndef CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED -#define CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED - -namespace Catch { - - //! Used to signal that an assertion macro failed - struct TestFailureException{}; - //! Used to signal that the remainder of a test should be skipped - struct TestSkipException {}; - - /** - * Outlines throwing of `TestFailureException` into a single TU - * - * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers. - */ - [[noreturn]] void throw_test_failure_exception(); - - /** - * Outlines throwing of `TestSkipException` into a single TU - * - * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers. - */ - [[noreturn]] void throw_test_skip_exception(); - -} // namespace Catch - -#endif // CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED - - -#ifndef CATCH_UNIQUE_NAME_HPP_INCLUDED -#define CATCH_UNIQUE_NAME_HPP_INCLUDED - - - - -/** \file - * Wrapper for the CONFIG configuration option - * - * When generating internal unique names, there are two options. Either - * we mix in the current line number, or mix in an incrementing number. - * We prefer the latter, using `__COUNTER__`, but users might want to - * use the former. - */ - -#ifndef CATCH_CONFIG_COUNTER_HPP_INCLUDED -#define CATCH_CONFIG_COUNTER_HPP_INCLUDED - - -#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 - - -#endif // CATCH_CONFIG_COUNTER_HPP_INCLUDED -#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 - -#endif // CATCH_UNIQUE_NAME_HPP_INCLUDED - - -#ifndef CATCH_INTERFACES_CAPTURE_HPP_INCLUDED -#define CATCH_INTERFACES_CAPTURE_HPP_INCLUDED - -#include - - - -#ifndef CATCH_STRINGREF_HPP_INCLUDED -#define CATCH_STRINGREF_HPP_INCLUDED - -#include -#include -#include -#include - -#include - -namespace Catch { - - /// A non-owning string class (similar to the forthcoming std::string_view) - /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. - class StringRef { - public: - using size_type = std::size_t; - using const_iterator = const char*; - - static constexpr size_type npos{ static_cast( -1 ) }; - - private: - static constexpr char const* const s_empty = ""; - - char const* m_start = s_empty; - size_type m_size = 0; - - public: // construction - constexpr StringRef() noexcept = default; - - StringRef( char const* rawChars ) noexcept; - - constexpr StringRef( char const* rawChars, size_type size ) noexcept - : m_start( rawChars ), - m_size( size ) - {} - - StringRef( std::string const& stdString ) noexcept - : m_start( stdString.c_str() ), - m_size( stdString.size() ) - {} - - explicit operator std::string() const { - return std::string(m_start, m_size); - } - - public: // operators - auto operator == ( StringRef other ) const noexcept -> bool { - return m_size == other.m_size - && (std::memcmp( m_start, other.m_start, m_size ) == 0); - } - auto operator != (StringRef other) const noexcept -> bool { - return !(*this == other); - } - - constexpr auto operator[] ( size_type index ) const noexcept -> char { - assert(index < m_size); - return m_start[index]; - } - - bool operator<(StringRef rhs) const noexcept; - - public: // named queries - constexpr auto empty() const noexcept -> bool { - return m_size == 0; - } - constexpr auto size() const noexcept -> size_type { - return m_size; - } - - // Returns a substring of [start, start + length). - // If start + length > size(), then the substring is [start, size()). - // If start > size(), then the substring is empty. - constexpr StringRef substr(size_type start, size_type length) const noexcept { - if (start < m_size) { - const auto shortened_size = m_size - start; - return StringRef(m_start + start, (shortened_size < length) ? shortened_size : length); - } else { - return StringRef(); - } - } - - // Returns the current start pointer. May not be null-terminated. - constexpr char const* data() const noexcept { - return m_start; - } - - constexpr const_iterator begin() const { return m_start; } - constexpr const_iterator end() const { return m_start + m_size; } - - - friend std::string& operator += (std::string& lhs, StringRef rhs); - friend std::ostream& operator << (std::ostream& os, StringRef str); - friend std::string operator+(StringRef lhs, StringRef rhs); - - /** - * Provides a three-way comparison with rhs - * - * Returns negative number if lhs < rhs, 0 if lhs == rhs, and a positive - * number if lhs > rhs - */ - int compare( StringRef rhs ) const; - }; - - - constexpr auto operator ""_sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { - return StringRef( rawChars, size ); - } -} // namespace Catch - -constexpr auto operator ""_catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { - return Catch::StringRef( rawChars, size ); -} - -#endif // CATCH_STRINGREF_HPP_INCLUDED - - -#ifndef CATCH_RESULT_TYPE_HPP_INCLUDED -#define CATCH_RESULT_TYPE_HPP_INCLUDED - -namespace Catch { - - // ResultWas::OfType enum - struct ResultWas { enum OfType { - Unknown = -1, - Ok = 0, - Info = 1, - Warning = 2, - // TODO: Should explicit skip be considered "not OK" (cf. isOk)? I.e., should it have the failure bit? - ExplicitSkip = 4, - - FailureBit = 0x10, - - ExpressionFailed = FailureBit | 1, - ExplicitFailure = FailureBit | 2, - - Exception = 0x100 | FailureBit, - - ThrewException = Exception | 1, - DidntThrowException = Exception | 2, - - FatalErrorCondition = 0x200 | FailureBit - - }; }; - - constexpr bool isOk( ResultWas::OfType resultType ) { - return ( resultType & ResultWas::FailureBit ) == 0; - } - constexpr bool isJustInfo( int flags ) { return flags == ResultWas::Info; } - - - // 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 - }; }; - - constexpr ResultDisposition::Flags operator|( ResultDisposition::Flags lhs, - ResultDisposition::Flags rhs ) { - return static_cast( static_cast( lhs ) | - static_cast( rhs ) ); - } - - constexpr bool isFalseTest( int flags ) { - return ( flags & ResultDisposition::FalseTest ) != 0; - } - constexpr bool shouldSuppressFailure( int flags ) { - return ( flags & ResultDisposition::SuppressFail ) != 0; - } - -} // end namespace Catch - -#endif // CATCH_RESULT_TYPE_HPP_INCLUDED - - -#ifndef CATCH_UNIQUE_PTR_HPP_INCLUDED -#define CATCH_UNIQUE_PTR_HPP_INCLUDED - -#include -#include - - -namespace Catch { -namespace Detail { - /** - * A reimplementation of `std::unique_ptr` for improved compilation performance - * - * Does not support arrays nor custom deleters. - */ - template - class unique_ptr { - T* m_ptr; - public: - constexpr unique_ptr(std::nullptr_t = nullptr): - m_ptr{} - {} - explicit constexpr unique_ptr(T* ptr): - m_ptr(ptr) - {} - - template ::value>> - unique_ptr(unique_ptr&& from): - m_ptr(from.release()) - {} - - template ::value>> - unique_ptr& operator=(unique_ptr&& from) { - reset(from.release()); - - return *this; - } - - unique_ptr(unique_ptr const&) = delete; - unique_ptr& operator=(unique_ptr const&) = delete; - - unique_ptr(unique_ptr&& rhs) noexcept: - m_ptr(rhs.m_ptr) { - rhs.m_ptr = nullptr; - } - unique_ptr& operator=(unique_ptr&& rhs) noexcept { - reset(rhs.release()); - - return *this; - } - - ~unique_ptr() { - delete m_ptr; - } - - T& operator*() { - assert(m_ptr); - return *m_ptr; - } - T const& operator*() const { - assert(m_ptr); - return *m_ptr; - } - T* operator->() noexcept { - assert(m_ptr); - return m_ptr; - } - T const* operator->() const noexcept { - assert(m_ptr); - return m_ptr; - } - - T* get() { return m_ptr; } - T const* get() const { return m_ptr; } - - void reset(T* ptr = nullptr) { - delete m_ptr; - m_ptr = ptr; - } - - T* release() { - auto temp = m_ptr; - m_ptr = nullptr; - return temp; - } - - explicit operator bool() const { - return m_ptr; - } - - friend void swap(unique_ptr& lhs, unique_ptr& rhs) { - auto temp = lhs.m_ptr; - lhs.m_ptr = rhs.m_ptr; - rhs.m_ptr = temp; - } - }; - - //! Specialization to cause compile-time error for arrays - template - class unique_ptr; - - template - unique_ptr make_unique(Args&&... args) { - return unique_ptr(new T(CATCH_FORWARD(args)...)); - } - - -} // end namespace Detail -} // end namespace Catch - -#endif // CATCH_UNIQUE_PTR_HPP_INCLUDED - - -#ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED -#define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED - - - -// Adapted from donated nonius code. - -#ifndef CATCH_CLOCK_HPP_INCLUDED -#define CATCH_CLOCK_HPP_INCLUDED - -#include - -namespace Catch { - namespace Benchmark { - using IDuration = std::chrono::nanoseconds; - using FDuration = std::chrono::duration; - - template - using TimePoint = typename Clock::time_point; - - using default_clock = std::chrono::steady_clock; - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_CLOCK_HPP_INCLUDED - -namespace Catch { - - // We cannot forward declare the type with default template argument - // multiple times, so it is split out into a separate header so that - // we can prevent multiple declarations in dependees - template - struct BenchmarkStats; - -} // end namespace Catch - -#endif // CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED - -namespace Catch { - - class AssertionResult; - struct AssertionInfo; - struct SectionInfo; - struct SectionEndInfo; - struct MessageInfo; - struct MessageBuilder; - struct Counts; - struct AssertionReaction; - struct SourceLineInfo; - - class ITransientExpression; - class IGeneratorTracker; - - struct BenchmarkInfo; - - namespace Generators { - class GeneratorUntypedBase; - using GeneratorBasePtr = Catch::Detail::unique_ptr; - } - - - class IResultCapture { - public: - virtual ~IResultCapture(); - - virtual void notifyAssertionStarted( AssertionInfo const& info ) = 0; - virtual bool sectionStarted( StringRef sectionName, - SourceLineInfo const& sectionLineInfo, - Counts& assertions ) = 0; - virtual void sectionEnded( SectionEndInfo&& endInfo ) = 0; - virtual void sectionEndedEarly( SectionEndInfo&& endInfo ) = 0; - - virtual IGeneratorTracker* - acquireGeneratorTracker( StringRef generatorName, - SourceLineInfo const& lineInfo ) = 0; - virtual IGeneratorTracker* - createGeneratorTracker( StringRef generatorName, - SourceLineInfo lineInfo, - Generators::GeneratorBasePtr&& generator ) = 0; - - virtual void benchmarkPreparing( StringRef name ) = 0; - virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; - virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; - virtual void benchmarkFailed( StringRef error ) = 0; - - virtual void pushScopedMessage( MessageInfo const& message ) = 0; - virtual void popScopedMessage( MessageInfo const& message ) = 0; - - virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0; - - virtual void handleFatalErrorCondition( StringRef message ) = 0; - - virtual void handleExpr - ( AssertionInfo const& info, - ITransientExpression const& expr, - AssertionReaction& reaction ) = 0; - virtual void handleMessage - ( AssertionInfo const& info, - ResultWas::OfType resultType, - std::string&& message, - AssertionReaction& reaction ) = 0; - virtual void handleUnexpectedExceptionNotThrown - ( AssertionInfo const& info, - AssertionReaction& reaction ) = 0; - virtual void handleUnexpectedInflightException - ( AssertionInfo const& info, - std::string&& message, - AssertionReaction& reaction ) = 0; - virtual void handleIncomplete - ( AssertionInfo const& info ) = 0; - virtual void handleNonExpr - ( AssertionInfo const &info, - ResultWas::OfType resultType, - AssertionReaction &reaction ) = 0; - - - - virtual bool lastAssertionPassed() = 0; - virtual void assertionPassed() = 0; - - // Deprecated, do not use: - virtual std::string getCurrentTestName() const = 0; - virtual const AssertionResult* getLastResult() const = 0; - virtual void exceptionEarlyReported() = 0; - }; - - IResultCapture& getResultCapture(); -} - -#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED - - -#ifndef CATCH_INTERFACES_CONFIG_HPP_INCLUDED -#define CATCH_INTERFACES_CONFIG_HPP_INCLUDED - - - -#ifndef CATCH_NONCOPYABLE_HPP_INCLUDED -#define CATCH_NONCOPYABLE_HPP_INCLUDED - -namespace Catch { - namespace Detail { - - //! Deriving classes become noncopyable and nonmovable - class NonCopyable { - NonCopyable( NonCopyable const& ) = delete; - NonCopyable( NonCopyable&& ) = delete; - NonCopyable& operator=( NonCopyable const& ) = delete; - NonCopyable& operator=( NonCopyable&& ) = delete; - - protected: - NonCopyable() noexcept = default; - }; - - } // namespace Detail -} // namespace Catch - -#endif // CATCH_NONCOPYABLE_HPP_INCLUDED - -#include -#include -#include -#include - -namespace Catch { - - enum class Verbosity { - Quiet = 0, - Normal, - High - }; - - struct WarnAbout { enum What { - Nothing = 0x00, - //! A test case or leaf section did not run any assertions - NoAssertions = 0x01, - //! A command line test spec matched no test cases - UnmatchedTestSpec = 0x02, - }; }; - - enum class ShowDurations { - DefaultForReporter, - Always, - Never - }; - enum class TestRunOrder { - Declared, - LexicographicallySorted, - Randomized - }; - enum class ColourMode : std::uint8_t { - //! Let Catch2 pick implementation based on platform detection - PlatformDefault, - //! Use ANSI colour code escapes - ANSI, - //! Use Win32 console colour API - Win32, - //! Don't use any colour - None - }; - struct WaitForKeypress { enum When { - Never, - BeforeStart = 1, - BeforeExit = 2, - BeforeStartAndExit = BeforeStart | BeforeExit - }; }; - - class TestSpec; - class IStream; - - class IConfig : public Detail::NonCopyable { - public: - virtual ~IConfig(); - - virtual bool allowThrows() const = 0; - virtual StringRef name() const = 0; - virtual bool includeSuccessfulResults() const = 0; - virtual bool shouldDebugBreak() const = 0; - virtual bool warnAboutMissingAssertions() const = 0; - virtual bool warnAboutUnmatchedTestSpecs() const = 0; - virtual bool zeroTestsCountAsSuccess() const = 0; - virtual int abortAfter() const = 0; - virtual bool showInvisibles() const = 0; - virtual ShowDurations showDurations() const = 0; - virtual double minDuration() const = 0; - virtual TestSpec const& testSpec() const = 0; - virtual bool hasTestFilters() const = 0; - virtual std::vector const& getTestsOrTags() const = 0; - virtual TestRunOrder runOrder() const = 0; - virtual uint32_t rngSeed() const = 0; - virtual unsigned int shardCount() const = 0; - virtual unsigned int shardIndex() const = 0; - virtual ColourMode defaultColourMode() const = 0; - virtual std::vector const& getSectionsToRun() const = 0; - virtual Verbosity verbosity() const = 0; - - virtual bool skipBenchmarks() const = 0; - virtual bool benchmarkNoAnalysis() const = 0; - virtual unsigned int benchmarkSamples() const = 0; - virtual double benchmarkConfidenceInterval() const = 0; - virtual unsigned int benchmarkResamples() const = 0; - virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0; - }; -} - -#endif // CATCH_INTERFACES_CONFIG_HPP_INCLUDED - - -#ifndef CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED -#define CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED - - -#include - -namespace Catch { - - class TestCaseHandle; - struct TestCaseInfo; - class ITestCaseRegistry; - class IExceptionTranslatorRegistry; - class IExceptionTranslator; - class ReporterRegistry; - class IReporterFactory; - class ITagAliasRegistry; - class ITestInvoker; - class IMutableEnumValuesRegistry; - struct SourceLineInfo; - - class StartupExceptionRegistry; - class EventListenerFactory; - - using IReporterFactoryPtr = Detail::unique_ptr; - - class IRegistryHub { - public: - virtual ~IRegistryHub(); // = default - - virtual ReporterRegistry const& getReporterRegistry() const = 0; - virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; - virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; - virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; - - - virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; - }; - - class IMutableRegistryHub { - public: - virtual ~IMutableRegistryHub(); // = default - virtual void registerReporter( std::string const& name, IReporterFactoryPtr factory ) = 0; - virtual void registerListener( Detail::unique_ptr factory ) = 0; - virtual void registerTest(Detail::unique_ptr&& testInfo, Detail::unique_ptr&& invoker) = 0; - virtual void registerTranslator( Detail::unique_ptr&& translator ) = 0; - virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; - virtual void registerStartupException() noexcept = 0; - virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0; - }; - - IRegistryHub const& getRegistryHub(); - IMutableRegistryHub& getMutableRegistryHub(); - void cleanUp(); - std::string translateActiveException(); - -} - -#endif // CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED - - -#ifndef CATCH_BENCHMARK_STATS_HPP_INCLUDED -#define CATCH_BENCHMARK_STATS_HPP_INCLUDED - - - -// Adapted from donated nonius code. - -#ifndef CATCH_ESTIMATE_HPP_INCLUDED -#define CATCH_ESTIMATE_HPP_INCLUDED - -namespace Catch { - namespace Benchmark { - template - struct Estimate { - Type point; - Type lower_bound; - Type upper_bound; - double confidence_interval; - }; - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_ESTIMATE_HPP_INCLUDED - - -// Adapted from donated nonius code. - -#ifndef CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED -#define CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED - -namespace Catch { - namespace Benchmark { - struct OutlierClassification { - int samples_seen = 0; - int low_severe = 0; // more than 3 times IQR below Q1 - int low_mild = 0; // 1.5 to 3 times IQR below Q1 - int high_mild = 0; // 1.5 to 3 times IQR above Q3 - int high_severe = 0; // more than 3 times IQR above Q3 - - constexpr int total() const { - return low_severe + low_mild + high_mild + high_severe; - } - }; - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_OUTLIERS_CLASSIFICATION_HPP_INCLUDED -// The fwd decl & default specialization needs to be seen by VS2017 before -// BenchmarkStats itself, or VS2017 will report compilation error. - -#include -#include - -namespace Catch { - - struct BenchmarkInfo { - std::string name; - double estimatedDuration; - int iterations; - unsigned int samples; - unsigned int resamples; - double clockResolution; - double clockCost; - }; - - // We need to keep template parameter for backwards compatibility, - // but we also do not want to use the template paraneter. - template - struct BenchmarkStats { - BenchmarkInfo info; - - std::vector samples; - Benchmark::Estimate mean; - Benchmark::Estimate standardDeviation; - Benchmark::OutlierClassification outliers; - double outlierVariance; - }; - - -} // end namespace Catch - -#endif // CATCH_BENCHMARK_STATS_HPP_INCLUDED - - -// Adapted from donated nonius code. - -#ifndef CATCH_ENVIRONMENT_HPP_INCLUDED -#define CATCH_ENVIRONMENT_HPP_INCLUDED - - -namespace Catch { - namespace Benchmark { - struct EnvironmentEstimate { - FDuration mean; - OutlierClassification outliers; - }; - struct Environment { - EnvironmentEstimate clock_resolution; - EnvironmentEstimate clock_cost; - }; - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_ENVIRONMENT_HPP_INCLUDED - - -// Adapted from donated nonius code. - -#ifndef CATCH_EXECUTION_PLAN_HPP_INCLUDED -#define CATCH_EXECUTION_PLAN_HPP_INCLUDED - - - -// Adapted from donated nonius code. - -#ifndef CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED -#define CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED - - - -// Adapted from donated nonius code. - -#ifndef CATCH_CHRONOMETER_HPP_INCLUDED -#define CATCH_CHRONOMETER_HPP_INCLUDED - - - -// Adapted from donated nonius code. - -#ifndef CATCH_OPTIMIZER_HPP_INCLUDED -#define CATCH_OPTIMIZER_HPP_INCLUDED - -#if defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__) -# include // atomic_thread_fence -#endif - - -#include - -namespace Catch { - namespace Benchmark { -#if defined(__GNUC__) || defined(__clang__) - template - inline void keep_memory(T* p) { - asm volatile("" : : "g"(p) : "memory"); - } - inline void keep_memory() { - asm volatile("" : : : "memory"); - } - - namespace Detail { - inline void optimizer_barrier() { keep_memory(); } - } // namespace Detail -#elif defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__) - -#if defined(_MSVC_VER) -#pragma optimize("", off) -#elif defined(__IAR_SYSTEMS_ICC__) -// For IAR the pragma only affects the following function -#pragma optimize=disable -#endif - template - inline void keep_memory(T* p) { - // thanks @milleniumbug - *reinterpret_cast(p) = *reinterpret_cast(p); - } - // TODO equivalent keep_memory() -#if defined(_MSVC_VER) -#pragma optimize("", on) -#endif - - namespace Detail { - inline void optimizer_barrier() { - std::atomic_thread_fence(std::memory_order_seq_cst); - } - } // namespace Detail - -#endif - - template - inline void deoptimize_value(T&& x) { - keep_memory(&x); - } - - template - inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t::value> { - deoptimize_value(CATCH_FORWARD(fn) (CATCH_FORWARD(args)...)); - } - - template - inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t::value> { - CATCH_FORWARD((fn)) (CATCH_FORWARD(args)...); - } - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_OPTIMIZER_HPP_INCLUDED - - -#ifndef CATCH_META_HPP_INCLUDED -#define CATCH_META_HPP_INCLUDED - -#include - -namespace Catch { - template - struct true_given : std::true_type {}; - - struct is_callable_tester { - template - static true_given()(std::declval()...))> test(int); - template - static std::false_type test(...); - }; - - template - struct is_callable; - - template - struct is_callable : decltype(is_callable_tester::test(0)) {}; - - -#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 - // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is - // replaced with std::invoke_result here. - template - using FunctionReturnType = std::remove_reference_t>>; -#else - template - using FunctionReturnType = std::remove_reference_t>>; -#endif - -} // namespace Catch - -namespace mpl_{ - struct na; -} - -#endif // CATCH_META_HPP_INCLUDED - -namespace Catch { - namespace Benchmark { - namespace Detail { - struct ChronometerConcept { - virtual void start() = 0; - virtual void finish() = 0; - virtual ~ChronometerConcept(); // = default; - - ChronometerConcept() = default; - ChronometerConcept(ChronometerConcept const&) = default; - ChronometerConcept& operator=(ChronometerConcept const&) = default; - }; - template - struct ChronometerModel final : public ChronometerConcept { - void start() override { started = Clock::now(); } - void finish() override { finished = Clock::now(); } - - IDuration elapsed() const { - return std::chrono::duration_cast( - finished - started ); - } - - TimePoint started; - TimePoint finished; - }; - } // namespace Detail - - struct Chronometer { - public: - template - void measure(Fun&& fun) { measure(CATCH_FORWARD(fun), is_callable()); } - - int runs() const { return repeats; } - - Chronometer(Detail::ChronometerConcept& meter, int repeats_) - : impl(&meter) - , repeats(repeats_) {} - - private: - template - void measure(Fun&& fun, std::false_type) { - measure([&fun](int) { return fun(); }, std::true_type()); - } - - template - void measure(Fun&& fun, std::true_type) { - Detail::optimizer_barrier(); - impl->start(); - for (int i = 0; i < repeats; ++i) invoke_deoptimized(fun, i); - impl->finish(); - Detail::optimizer_barrier(); - } - - Detail::ChronometerConcept* impl; - int repeats; - }; - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_CHRONOMETER_HPP_INCLUDED - -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - struct is_related - : std::is_same, std::decay_t> {}; - - /// We need to reinvent std::function because every piece of code that might add overhead - /// in a measurement context needs to have consistent performance characteristics so that we - /// can account for it in the measurement. - /// Implementations of std::function with optimizations that aren't always applicable, like - /// small buffer optimizations, are not uncommon. - /// This is effectively an implementation of std::function without any such optimizations; - /// it may be slow, but it is consistently slow. - struct BenchmarkFunction { - private: - struct callable { - virtual void call(Chronometer meter) const = 0; - virtual ~callable(); // = default; - - callable() = default; - callable(callable&&) = default; - callable& operator=(callable&&) = default; - }; - template - struct model : public callable { - model(Fun&& fun_) : fun(CATCH_MOVE(fun_)) {} - model(Fun const& fun_) : fun(fun_) {} - - void call(Chronometer meter) const override { - call(meter, is_callable()); - } - void call(Chronometer meter, std::true_type) const { - fun(meter); - } - void call(Chronometer meter, std::false_type) const { - meter.measure(fun); - } - - Fun fun; - }; - - public: - BenchmarkFunction(); - - template ::value, int> = 0> - BenchmarkFunction(Fun&& fun) - : f(new model>(CATCH_FORWARD(fun))) {} - - BenchmarkFunction( BenchmarkFunction&& that ) noexcept: - f( CATCH_MOVE( that.f ) ) {} - - BenchmarkFunction& - operator=( BenchmarkFunction&& that ) noexcept { - f = CATCH_MOVE( that.f ); - return *this; - } - - void operator()(Chronometer meter) const { f->call(meter); } - - private: - Catch::Detail::unique_ptr f; - }; - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED - - -// Adapted from donated nonius code. - -#ifndef CATCH_REPEAT_HPP_INCLUDED -#define CATCH_REPEAT_HPP_INCLUDED - -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - struct repeater { - void operator()(int k) const { - for (int i = 0; i < k; ++i) { - fun(); - } - } - Fun fun; - }; - template - repeater> repeat(Fun&& fun) { - return { CATCH_FORWARD(fun) }; - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_REPEAT_HPP_INCLUDED - - -// Adapted from donated nonius code. - -#ifndef CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED -#define CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED - - - -// Adapted from donated nonius code. - -#ifndef CATCH_MEASURE_HPP_INCLUDED -#define CATCH_MEASURE_HPP_INCLUDED - - - -// Adapted from donated nonius code. - -#ifndef CATCH_COMPLETE_INVOKE_HPP_INCLUDED -#define CATCH_COMPLETE_INVOKE_HPP_INCLUDED - - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - struct CompleteType { using type = T; }; - template <> - struct CompleteType { struct type {}; }; - - template - using CompleteType_t = typename CompleteType::type; - - template - struct CompleteInvoker { - template - static Result invoke(Fun&& fun, Args&&... args) { - return CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); - } - }; - template <> - struct CompleteInvoker { - template - static CompleteType_t invoke(Fun&& fun, Args&&... args) { - CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); - return {}; - } - }; - - // invoke and not return void :( - template - CompleteType_t> complete_invoke(Fun&& fun, Args&&... args) { - return CompleteInvoker>::invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...); - } - - } // namespace Detail - - template - Detail::CompleteType_t> user_code(Fun&& fun) { - return Detail::complete_invoke(CATCH_FORWARD(fun)); - } - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_COMPLETE_INVOKE_HPP_INCLUDED - - -// Adapted from donated nonius code. - -#ifndef CATCH_TIMING_HPP_INCLUDED -#define CATCH_TIMING_HPP_INCLUDED - - -#include - -namespace Catch { - namespace Benchmark { - template - struct Timing { - IDuration elapsed; - Result result; - int iterations; - }; - template - using TimingOf = Timing>>; - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_TIMING_HPP_INCLUDED - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - TimingOf measure(Fun&& fun, Args&&... args) { - auto start = Clock::now(); - auto&& r = Detail::complete_invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...); - auto end = Clock::now(); - auto delta = end - start; - return { delta, CATCH_FORWARD(r), 1 }; - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_MEASURE_HPP_INCLUDED - -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - TimingOf measure_one(Fun&& fun, int iters, std::false_type) { - return Detail::measure(fun, iters); - } - template - TimingOf measure_one(Fun&& fun, int iters, std::true_type) { - Detail::ChronometerModel meter; - auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters)); - - return { meter.elapsed(), CATCH_MOVE(result), iters }; - } - - template - using run_for_at_least_argument_t = std::conditional_t::value, Chronometer, int>; - - - [[noreturn]] - void throw_optimized_away_error(); - - template - TimingOf> - run_for_at_least(IDuration how_long, - const int initial_iterations, - Fun&& fun) { - auto iters = initial_iterations; - while (iters < (1 << 30)) { - auto&& Timing = measure_one(fun, iters, is_callable()); - - if (Timing.elapsed >= how_long) { - return { Timing.elapsed, CATCH_MOVE(Timing.result), iters }; - } - iters *= 2; - } - throw_optimized_away_error(); - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED - -#include - -namespace Catch { - namespace Benchmark { - struct ExecutionPlan { - int iterations_per_sample; - FDuration estimated_duration; - Detail::BenchmarkFunction benchmark; - FDuration warmup_time; - int warmup_iterations; - - template - std::vector run(const IConfig &cfg, Environment env) const { - // warmup a bit - Detail::run_for_at_least( - std::chrono::duration_cast( warmup_time ), - warmup_iterations, - Detail::repeat( []() { return Clock::now(); } ) - ); - - std::vector times; - const auto num_samples = cfg.benchmarkSamples(); - times.reserve( num_samples ); - for ( size_t i = 0; i < num_samples; ++i ) { - Detail::ChronometerModel model; - this->benchmark( Chronometer( model, iterations_per_sample ) ); - auto sample_time = model.elapsed() - env.clock_cost.mean; - if ( sample_time < FDuration::zero() ) { - sample_time = FDuration::zero(); - } - times.push_back(sample_time / iterations_per_sample); - } - return times; - } - }; - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_EXECUTION_PLAN_HPP_INCLUDED - - -// Adapted from donated nonius code. - -#ifndef CATCH_ESTIMATE_CLOCK_HPP_INCLUDED -#define CATCH_ESTIMATE_CLOCK_HPP_INCLUDED - - - -// Adapted from donated nonius code. - -#ifndef CATCH_STATS_HPP_INCLUDED -#define CATCH_STATS_HPP_INCLUDED - - -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - using sample = std::vector; - - double weighted_average_quantile( int k, - int q, - double* first, - double* last ); - - OutlierClassification - classify_outliers( double const* first, double const* last ); - - double mean( double const* first, double const* last ); - - double normal_cdf( double x ); - - double erfc_inv(double x); - - double normal_quantile(double p); - - Estimate - bootstrap( double confidence_level, - double* first, - double* last, - sample const& resample, - double ( *estimator )( double const*, double const* ) ); - - struct bootstrap_analysis { - Estimate mean; - Estimate standard_deviation; - double outlier_variance; - }; - - bootstrap_analysis analyse_samples(double confidence_level, - unsigned int n_resamples, - double* first, - double* last); - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_STATS_HPP_INCLUDED - -#include -#include -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - std::vector resolution(int k) { - const size_t points = static_cast( k + 1 ); - // To avoid overhead from the branch inside vector::push_back, - // we allocate them all and then overwrite. - std::vector> times(points); - for ( auto& time : times ) { - time = Clock::now(); - } - - std::vector deltas; - deltas.reserve(static_cast(k)); - for ( size_t idx = 1; idx < points; ++idx ) { - deltas.push_back( static_cast( - ( times[idx] - times[idx - 1] ).count() ) ); - } - - return deltas; - } - - constexpr auto warmup_iterations = 10000; - constexpr auto warmup_time = std::chrono::milliseconds(100); - constexpr auto minimum_ticks = 1000; - constexpr auto warmup_seed = 10000; - constexpr auto clock_resolution_estimation_time = std::chrono::milliseconds(500); - constexpr auto clock_cost_estimation_time_limit = std::chrono::seconds(1); - constexpr auto clock_cost_estimation_tick_limit = 100000; - constexpr auto clock_cost_estimation_time = std::chrono::milliseconds(10); - constexpr auto clock_cost_estimation_iterations = 10000; - - template - int warmup() { - return run_for_at_least(warmup_time, warmup_seed, &resolution) - .iterations; - } - template - EnvironmentEstimate estimate_clock_resolution(int iterations) { - auto r = run_for_at_least(clock_resolution_estimation_time, iterations, &resolution) - .result; - return { - FDuration(mean(r.data(), r.data() + r.size())), - classify_outliers(r.data(), r.data() + r.size()), - }; - } - template - EnvironmentEstimate estimate_clock_cost(FDuration resolution) { - auto time_limit = (std::min)( - resolution * clock_cost_estimation_tick_limit, - FDuration(clock_cost_estimation_time_limit)); - auto time_clock = [](int k) { - return Detail::measure([k] { - for (int i = 0; i < k; ++i) { - volatile auto ignored = Clock::now(); - (void)ignored; - } - }).elapsed; - }; - time_clock(1); - int iters = clock_cost_estimation_iterations; - auto&& r = run_for_at_least(clock_cost_estimation_time, iters, time_clock); - std::vector times; - int nsamples = static_cast(std::ceil(time_limit / r.elapsed)); - times.reserve(static_cast(nsamples)); - for ( int s = 0; s < nsamples; ++s ) { - times.push_back( static_cast( - ( time_clock( r.iterations ) / r.iterations ) - .count() ) ); - } - return { - FDuration(mean(times.data(), times.data() + times.size())), - classify_outliers(times.data(), times.data() + times.size()), - }; - } - - template - Environment measure_environment() { -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - static Catch::Detail::unique_ptr env; -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - if (env) { - return *env; - } - - auto iters = Detail::warmup(); - auto resolution = Detail::estimate_clock_resolution(iters); - auto cost = Detail::estimate_clock_cost(resolution.mean); - - env = Catch::Detail::make_unique( Environment{resolution, cost} ); - return *env; - } - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_ESTIMATE_CLOCK_HPP_INCLUDED - - -// Adapted from donated nonius code. - -#ifndef CATCH_ANALYSE_HPP_INCLUDED -#define CATCH_ANALYSE_HPP_INCLUDED - - - -// Adapted from donated nonius code. - -#ifndef CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED -#define CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED - - -#include - -namespace Catch { - namespace Benchmark { - struct SampleAnalysis { - std::vector samples; - Estimate mean; - Estimate standard_deviation; - OutlierClassification outliers; - double outlier_variance; - }; - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED - - -namespace Catch { - class IConfig; - - namespace Benchmark { - namespace Detail { - SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last); - } // namespace Detail - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_ANALYSE_HPP_INCLUDED - -#include -#include -#include -#include -#include - -namespace Catch { - namespace Benchmark { - struct Benchmark { - Benchmark(std::string&& benchmarkName) - : name(CATCH_MOVE(benchmarkName)) {} - - template - Benchmark(std::string&& benchmarkName , FUN &&func) - : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} - - template - ExecutionPlan prepare(const IConfig &cfg, Environment env) { - auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; - auto run_time = std::max(min_time, std::chrono::duration_cast(cfg.benchmarkWarmupTime())); - auto&& test = Detail::run_for_at_least(std::chrono::duration_cast(run_time), 1, fun); - int new_iters = static_cast(std::ceil(min_time * test.iterations / test.elapsed)); - return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), CATCH_MOVE(fun), std::chrono::duration_cast(cfg.benchmarkWarmupTime()), Detail::warmup_iterations }; - } - - template - void run() { - static_assert( Clock::is_steady, - "Benchmarking clock should be steady" ); - auto const* cfg = getCurrentContext().getConfig(); - - auto env = Detail::measure_environment(); - - getResultCapture().benchmarkPreparing(name); - CATCH_TRY{ - auto plan = user_code([&] { - return prepare(*cfg, env); - }); - - BenchmarkInfo info { - CATCH_MOVE(name), - plan.estimated_duration.count(), - plan.iterations_per_sample, - cfg->benchmarkSamples(), - cfg->benchmarkResamples(), - env.clock_resolution.mean.count(), - env.clock_cost.mean.count() - }; - - getResultCapture().benchmarkStarting(info); - - auto samples = user_code([&] { - return plan.template run(*cfg, env); - }); - - auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size()); - BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; - getResultCapture().benchmarkEnded(stats); - } CATCH_CATCH_ANON (TestFailureException const&) { - getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr); - } CATCH_CATCH_ALL{ - getResultCapture().benchmarkFailed(translateActiveException()); - // We let the exception go further up so that the - // test case is marked as failed. - std::rethrow_exception(std::current_exception()); - } - } - - // sets lambda to be used in fun *and* executes benchmark! - template ::value, int> = 0> - Benchmark & operator=(Fun func) { - auto const* cfg = getCurrentContext().getConfig(); - if (!cfg->skipBenchmarks()) { - fun = Detail::BenchmarkFunction(func); - run(); - } - return *this; - } - - explicit operator bool() { - return true; - } - - private: - Detail::BenchmarkFunction fun; - std::string name; - }; - } -} // namespace Catch - -#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1 -#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2 - -#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\ - if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ - BenchmarkName = [&](int benchmarkIndex) - -#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\ - if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ - BenchmarkName = [&] - -#if defined(CATCH_CONFIG_PREFIX_ALL) - -#define CATCH_BENCHMARK(...) \ - INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) -#define CATCH_BENCHMARK_ADVANCED(name) \ - INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) - -#else - -#define BENCHMARK(...) \ - INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) -#define BENCHMARK_ADVANCED(name) \ - INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) - -#endif - -#endif // CATCH_BENCHMARK_HPP_INCLUDED - - -// Adapted from donated nonius code. - -#ifndef CATCH_CONSTRUCTOR_HPP_INCLUDED -#define CATCH_CONSTRUCTOR_HPP_INCLUDED - - -#include - -namespace Catch { - namespace Benchmark { - namespace Detail { - template - struct ObjectStorage - { - ObjectStorage() = default; - - ObjectStorage(const ObjectStorage& other) - { - new(&data) T(other.stored_object()); - } - - ObjectStorage(ObjectStorage&& other) - { - new(data) T(CATCH_MOVE(other.stored_object())); - } - - ~ObjectStorage() { destruct_on_exit(); } - - template - void construct(Args&&... args) - { - new (data) T(CATCH_FORWARD(args)...); - } - - template - std::enable_if_t destruct() - { - stored_object().~T(); - } - - private: - // If this is a constructor benchmark, destruct the underlying object - template - void destruct_on_exit(std::enable_if_t* = nullptr) { destruct(); } - // Otherwise, don't - template - void destruct_on_exit(std::enable_if_t* = nullptr) { } - -#if defined( __GNUC__ ) && __GNUC__ <= 6 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wstrict-aliasing" -#endif - T& stored_object() { return *reinterpret_cast( data ); } - - T const& stored_object() const { - return *reinterpret_cast( data ); - } -#if defined( __GNUC__ ) && __GNUC__ <= 6 -# pragma GCC diagnostic pop -#endif - - alignas( T ) unsigned char data[sizeof( T )]{}; - }; - } // namespace Detail - - template - using storage_for = Detail::ObjectStorage; - - template - using destructable_object = Detail::ObjectStorage; - } // namespace Benchmark -} // namespace Catch - -#endif // CATCH_CONSTRUCTOR_HPP_INCLUDED - -#endif // CATCH_BENCHMARK_ALL_HPP_INCLUDED - - -#ifndef CATCH_APPROX_HPP_INCLUDED -#define CATCH_APPROX_HPP_INCLUDED - - - -#ifndef CATCH_TOSTRING_HPP_INCLUDED -#define CATCH_TOSTRING_HPP_INCLUDED - - -#include -#include -#include -#include - - - - -/** \file - * Wrapper for the WCHAR configuration option - * - * We want to support platforms that do not provide `wchar_t`, so we - * sometimes have to disable providing wchar_t overloads through Catch2, - * e.g. the StringMaker specialization for `std::wstring`. - */ - -#ifndef CATCH_CONFIG_WCHAR_HPP_INCLUDED -#define CATCH_CONFIG_WCHAR_HPP_INCLUDED - - -// We assume that WCHAR should be enabled by default, and only disabled -// for a shortlist (so far only DJGPP) of compilers. - -#if defined(__DJGPP__) -# define CATCH_INTERNAL_CONFIG_NO_WCHAR -#endif // __DJGPP__ - -#if !defined( CATCH_INTERNAL_CONFIG_NO_WCHAR ) && \ - !defined( CATCH_CONFIG_NO_WCHAR ) && \ - !defined( CATCH_CONFIG_WCHAR ) -# define CATCH_CONFIG_WCHAR -#endif - -#endif // CATCH_CONFIG_WCHAR_HPP_INCLUDED - - -#ifndef CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED -#define CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED - - -#include -#include -#include -#include - -namespace Catch { - - class ReusableStringStream : Detail::NonCopyable { - std::size_t m_index; - std::ostream* m_oss; - public: - ReusableStringStream(); - ~ReusableStringStream(); - - //! Returns the serialized state - std::string str() const; - //! Sets internal state to `str` - void str(std::string const& str); - -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -// Old versions of GCC do not understand -Wnonnull-compare -#pragma GCC diagnostic ignored "-Wpragmas" -// Streaming a function pointer triggers Waddress and Wnonnull-compare -// on GCC, because it implicitly converts it to bool and then decides -// that the check it uses (a? true : false) is tautological and cannot -// be null... -#pragma GCC diagnostic ignored "-Waddress" -#pragma GCC diagnostic ignored "-Wnonnull-compare" -#endif - - template - auto operator << ( T const& value ) -> ReusableStringStream& { - *m_oss << value; - return *this; - } - -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif - auto get() -> std::ostream& { return *m_oss; } - }; -} - -#endif // CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED - - -#ifndef CATCH_VOID_TYPE_HPP_INCLUDED -#define CATCH_VOID_TYPE_HPP_INCLUDED - - -namespace Catch { - namespace Detail { - - template - struct make_void { using type = void; }; - - template - using void_t = typename make_void::type; - - } // namespace Detail -} // namespace Catch - - -#endif // CATCH_VOID_TYPE_HPP_INCLUDED - - -#ifndef CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED -#define CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED - - -#include - -namespace Catch { - - namespace Detail { - struct EnumInfo { - StringRef m_name; - std::vector> m_values; - - ~EnumInfo(); - - StringRef lookup( int value ) const; - }; - } // namespace Detail - - class IMutableEnumValuesRegistry { - public: - virtual ~IMutableEnumValuesRegistry(); // = default; - - virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector const& values ) = 0; - - template - Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list values ) { - static_assert(sizeof(int) >= sizeof(E), "Cannot serialize enum to int"); - std::vector intValues; - intValues.reserve( values.size() ); - for( auto enumValue : values ) - intValues.push_back( static_cast( enumValue ) ); - return registerEnum( enumName, allEnums, intValues ); - } - }; - -} // Catch - -#endif // CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED - -#ifdef CATCH_CONFIG_CPP17_STRING_VIEW -#include -#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 global namespace operator<< for ADL lookup in - // `IsStreamInsertable` below. - using ::operator<<; - - namespace Detail { - - inline std::size_t catch_strnlen(const char *str, std::size_t n) { - auto ret = std::char_traits::find(str, n, '\0'); - if (ret != nullptr) { - return static_cast(ret - str); - } - return n; - } - - constexpr StringRef unprintableString = "{?}"_sr; - - //! Encases `string in quotes, and optionally escapes invisibles - std::string convertIntoString( StringRef string, bool escapeInvisibles ); - - //! Encases `string` in quotes, and escapes invisibles if user requested - //! it via CLI - std::string convertIntoString( StringRef string ); - - 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; - }; - - template - std::string convertUnknownEnumToString( E e ); - - template - std::enable_if_t< - !std::is_enum::value && !std::is_base_of::value, - std::string> convertUnstreamable( T const& ) { - return std::string(Detail::unprintableString); - } - template - std::enable_if_t< - !std::is_enum::value && std::is_base_of::value, - std::string> convertUnstreamable(T const& ex) { - return ex.what(); - } - - - template - std::enable_if_t< - std::is_enum::value, - std::string> convertUnstreamable( T const& value ) { - return convertUnknownEnumToString( value ); - } - -#if defined(_MANAGED) - //! Convert a CLR string to a utf8 std::string - template - std::string clrReferenceToString( T^ ref ) { - if (ref == nullptr) - return std::string("null"); - auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); - cli::pin_ptr p = &bytes[0]; - return std::string(reinterpret_cast(p), bytes->Length); - } -#endif - - } // namespace Detail - - - template - struct StringMaker { - template - static - std::enable_if_t<::Catch::Detail::IsStreamInsertable::value, std::string> - convert(const Fake& value) { - ReusableStringStream rss; - // NB: call using the function-like syntax to avoid ambiguity with - // user-defined templated operator<< under clang. - rss.operator<<(value); - return rss.str(); - } - - template - static - std::enable_if_t::value, std::string> - convert( const Fake& value ) { -#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) - return Detail::convertUnstreamable(value); -#else - return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); -#endif - } - }; - - 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>>::convert(e); - } - - template - std::string convertUnknownEnumToString( E e ) { - return ::Catch::Detail::stringify(static_cast>(e)); - } - -#if defined(_MANAGED) - template - std::string stringify( T^ e ) { - return ::Catch::StringMaker::convert(e); - } -#endif - - } // namespace Detail - - // Some predefined specializations - - template<> - struct StringMaker { - static std::string convert(const std::string& str); - }; - -#ifdef CATCH_CONFIG_CPP17_STRING_VIEW - template<> - struct StringMaker { - static std::string convert(std::string_view str); - }; -#endif - - template<> - struct StringMaker { - static std::string convert(char const * str); - }; - template<> - struct StringMaker { - static std::string convert(char * str); - }; - -#if defined(CATCH_CONFIG_WCHAR) - template<> - struct StringMaker { - static std::string convert(const std::wstring& wstr); - }; - -# ifdef CATCH_CONFIG_CPP17_STRING_VIEW - template<> - struct StringMaker { - static std::string convert(std::wstring_view str); - }; -# endif - - template<> - struct StringMaker { - static std::string convert(wchar_t const * str); - }; - template<> - struct StringMaker { - static std::string convert(wchar_t * str); - }; -#endif // CATCH_CONFIG_WCHAR - - template - struct StringMaker { - static std::string convert(char const* str) { - return Detail::convertIntoString( - StringRef( str, Detail::catch_strnlen( str, SZ ) ) ); - } - }; - template - struct StringMaker { - static std::string convert(signed char const* str) { - auto reinterpreted = reinterpret_cast(str); - return Detail::convertIntoString( - StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); - } - }; - template - struct StringMaker { - static std::string convert(unsigned char const* str) { - auto reinterpreted = reinterpret_cast(str); - return Detail::convertIntoString( - StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); - } - }; - -#if defined(CATCH_CONFIG_CPP17_BYTE) - template<> - struct StringMaker { - static std::string convert(std::byte value); - }; -#endif // defined(CATCH_CONFIG_CPP17_BYTE) - 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) { - using namespace std::string_literals; - return b ? "true"s : "false"s; - } - }; - - template<> - struct StringMaker { - static std::string convert(char c); - }; - template<> - struct StringMaker { - static std::string convert(signed char value); - }; - template<> - struct StringMaker { - static std::string convert(unsigned char value); - }; - - template<> - struct StringMaker { - static std::string convert(std::nullptr_t) { - using namespace std::string_literals; - return "nullptr"s; - } - }; - - template<> - struct StringMaker { - static std::string convert(float value); - CATCH_EXPORT static int precision; - }; - - template<> - struct StringMaker { - static std::string convert(double value); - CATCH_EXPORT static int precision; - }; - - 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"; - } - } - }; - -#if defined(_MANAGED) - template - struct StringMaker { - static std::string convert( T^ ref ) { - return ::Catch::Detail::clrReferenceToString(ref); - } - }; -#endif - - namespace Detail { - template - std::string rangeToString(InputIterator first, Sentinel last) { - ReusableStringStream rss; - rss << "{ "; - if (first != last) { - rss << ::Catch::Detail::stringify(*first); - for (++first; first != last; ++first) - rss << ", " << ::Catch::Detail::stringify(*first); - } - rss << " }"; - return rss.str(); - } - } - -} // namespace Catch - -////////////////////////////////////////////////////// -// Separate std-lib types stringification, so it can be selectively enabled -// This means that we do not bring in their headers - -#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) -# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER -# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER -# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER -# define CATCH_CONFIG_ENABLE_OPTIONAL_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) { - ReusableStringStream rss; - rss << "{ " - << ::Catch::Detail::stringify(pair.first) - << ", " - << ::Catch::Detail::stringify(pair.second) - << " }"; - return rss.str(); - } - }; -} -#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER - -#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) -#include -namespace Catch { - template - struct StringMaker > { - static std::string convert(const std::optional& optional) { - if (optional.has_value()) { - return ::Catch::Detail::stringify(*optional); - } else { - return "{ }"; - } - } - }; - template <> - struct StringMaker { - static std::string convert(const std::nullopt_t&) { - return "{ }"; - } - }; -} -#endif // CATCH_CONFIG_ENABLE_OPTIONAL_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) { - ReusableStringStream rss; - rss << '{'; - Detail::TupleElementPrinter>::print(tuple, rss.get()); - rss << " }"; - return rss.str(); - } - }; -} -#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER - -#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) -#include -namespace Catch { - template<> - struct StringMaker { - static std::string convert(const std::monostate&) { - return "{ }"; - } - }; - - template - struct StringMaker> { - static std::string convert(const std::variant& variant) { - if (variant.valueless_by_exception()) { - return "{valueless variant}"; - } else { - return std::visit( - [](const auto& value) { - return ::Catch::Detail::stringify(value); - }, - variant - ); - } - } - }; -} -#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER - -namespace Catch { - // Import begin/ end from std here - using std::begin; - using std::end; - - namespace Detail { - template - struct is_range_impl : std::false_type {}; - - template - struct is_range_impl()))>> : std::true_type {}; - } // namespace Detail - - template - struct is_range : Detail::is_range_impl {}; - -#if defined(_MANAGED) // Managed types are never ranges - template - struct is_range { - static const bool value = false; - }; -#endif - - template - std::string rangeToString( Range const& range ) { - return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); - } - - // Handle vector specially - template - std::string rangeToString( std::vector const& v ) { - ReusableStringStream rss; - rss << "{ "; - bool first = true; - for( bool b : v ) { - if( first ) - first = false; - else - rss << ", "; - rss << ::Catch::Detail::stringify( b ); - } - rss << " }"; - return rss.str(); - } - - template - struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>> { - static std::string convert( R const& range ) { - return rangeToString( range ); - } - }; - - template - struct StringMaker { - static std::string convert(T const(&arr)[SZ]) { - return rangeToString(arr); - } - }; - - -} // namespace Catch - -// Separate std::chrono::duration specialization -#include -#include -#include - - -namespace Catch { - -template -struct ratio_string { - static std::string symbol() { - Catch::ReusableStringStream rss; - rss << '[' << Ratio::num << '/' - << Ratio::den << ']'; - return rss.str(); - } -}; - -template <> -struct ratio_string { - static char symbol() { return 'a'; } -}; -template <> -struct ratio_string { - static char symbol() { return 'f'; } -}; -template <> -struct ratio_string { - static char symbol() { return 'p'; } -}; -template <> -struct ratio_string { - static char symbol() { return 'n'; } -}; -template <> -struct ratio_string { - static char symbol() { return 'u'; } -}; -template <> -struct ratio_string { - static char symbol() { return 'm'; } -}; - - //////////// - // std::chrono::duration specializations - template - struct StringMaker> { - static std::string convert(std::chrono::duration const& duration) { - ReusableStringStream rss; - rss << duration.count() << ' ' << ratio_string::symbol() << 's'; - return rss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const& duration) { - ReusableStringStream rss; - rss << duration.count() << " s"; - return rss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const& duration) { - ReusableStringStream rss; - rss << duration.count() << " m"; - return rss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const& duration) { - ReusableStringStream rss; - rss << duration.count() << " h"; - return rss.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, timeStampSize - 1); - } - }; -} - - -#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \ -namespace Catch { \ - template<> struct StringMaker { \ - static std::string convert( enumName value ) { \ - static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ - return static_cast(enumInfo.lookup( static_cast( value ) )); \ - } \ - }; \ -} - -#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#endif // CATCH_TOSTRING_HPP_INCLUDED - -#include - -namespace Catch { - - class Approx { - private: - bool equalityComparisonImpl(double other) const; - // Sets and validates the new margin (margin >= 0) - void setMargin(double margin); - // Sets and validates the new epsilon (0 < epsilon < 1) - void setEpsilon(double epsilon); - - public: - explicit Approx ( double value ); - - static Approx custom(); - - Approx operator-() const; - - template ::value>> - Approx operator()( T const& value ) const { - Approx approx( static_cast(value) ); - approx.m_epsilon = m_epsilon; - approx.m_margin = m_margin; - approx.m_scale = m_scale; - return approx; - } - - template ::value>> - explicit Approx( T const& value ): Approx(static_cast(value)) - {} - - - template ::value>> - friend bool operator == ( const T& lhs, Approx const& rhs ) { - auto lhs_v = static_cast(lhs); - return rhs.equalityComparisonImpl(lhs_v); - } - - template ::value>> - friend bool operator == ( Approx const& lhs, const T& rhs ) { - return operator==( rhs, lhs ); - } - - template ::value>> - friend bool operator != ( T const& lhs, Approx const& rhs ) { - return !operator==( lhs, rhs ); - } - - template ::value>> - friend bool operator != ( Approx const& lhs, T const& rhs ) { - return !operator==( rhs, lhs ); - } - - template ::value>> - friend bool operator <= ( T const& lhs, Approx const& rhs ) { - return static_cast(lhs) < rhs.m_value || lhs == rhs; - } - - template ::value>> - friend bool operator <= ( Approx const& lhs, T const& rhs ) { - return lhs.m_value < static_cast(rhs) || lhs == rhs; - } - - template ::value>> - friend bool operator >= ( T const& lhs, Approx const& rhs ) { - return static_cast(lhs) > rhs.m_value || lhs == rhs; - } - - template ::value>> - friend bool operator >= ( Approx const& lhs, T const& rhs ) { - return lhs.m_value > static_cast(rhs) || lhs == rhs; - } - - template ::value>> - Approx& epsilon( T const& newEpsilon ) { - const auto epsilonAsDouble = static_cast(newEpsilon); - setEpsilon(epsilonAsDouble); - return *this; - } - - template ::value>> - Approx& margin( T const& newMargin ) { - const auto marginAsDouble = static_cast(newMargin); - setMargin(marginAsDouble); - return *this; - } - - template ::value>> - 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; - }; - -namespace literals { - Approx operator ""_a(long double val); - Approx operator ""_a(unsigned long long val); -} // end namespace literals - -template<> -struct StringMaker { - static std::string convert(Catch::Approx const& value); -}; - -} // end namespace Catch - -#endif // CATCH_APPROX_HPP_INCLUDED - - -#ifndef CATCH_ASSERTION_INFO_HPP_INCLUDED -#define CATCH_ASSERTION_INFO_HPP_INCLUDED - - - -#ifndef CATCH_SOURCE_LINE_INFO_HPP_INCLUDED -#define CATCH_SOURCE_LINE_INFO_HPP_INCLUDED - -#include -#include - -namespace Catch { - - struct SourceLineInfo { - - SourceLineInfo() = delete; - constexpr SourceLineInfo( char const* _file, std::size_t _line ) noexcept: - file( _file ), - line( _line ) - {} - - bool operator == ( SourceLineInfo const& other ) const noexcept; - bool operator < ( SourceLineInfo const& other ) const noexcept; - - char const* file; - std::size_t line; - - friend std::ostream& operator << (std::ostream& os, SourceLineInfo const& info); - }; -} - -#define CATCH_INTERNAL_LINEINFO \ - ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) - -#endif // CATCH_SOURCE_LINE_INFO_HPP_INCLUDED - -namespace Catch { - - struct AssertionInfo { - // AssertionInfo() = delete; - - StringRef macroName; - SourceLineInfo lineInfo; - StringRef capturedExpression; - ResultDisposition::Flags resultDisposition; - }; - -} // end namespace Catch - -#endif // CATCH_ASSERTION_INFO_HPP_INCLUDED - - -#ifndef CATCH_ASSERTION_RESULT_HPP_INCLUDED -#define CATCH_ASSERTION_RESULT_HPP_INCLUDED - - - -#ifndef CATCH_LAZY_EXPR_HPP_INCLUDED -#define CATCH_LAZY_EXPR_HPP_INCLUDED - -#include - -namespace Catch { - - class ITransientExpression; - - class LazyExpression { - friend class AssertionHandler; - friend struct AssertionStats; - friend class RunContext; - - ITransientExpression const* m_transientExpression = nullptr; - bool m_isNegated; - public: - constexpr LazyExpression( bool isNegated ): - m_isNegated(isNegated) - {} - constexpr LazyExpression(LazyExpression const& other) = default; - LazyExpression& operator = ( LazyExpression const& ) = delete; - - constexpr explicit operator bool() const { - return m_transientExpression != nullptr; - } - - friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; - }; - -} // namespace Catch - -#endif // CATCH_LAZY_EXPR_HPP_INCLUDED - -#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&& 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; - StringRef getMessage() const; - SourceLineInfo getSourceInfo() const; - StringRef getTestMacroName() const; - - //protected: - AssertionInfo m_info; - AssertionResultData m_resultData; - }; - -} // end namespace Catch - -#endif // CATCH_ASSERTION_RESULT_HPP_INCLUDED - - -#ifndef CATCH_CASE_SENSITIVE_HPP_INCLUDED -#define CATCH_CASE_SENSITIVE_HPP_INCLUDED - -namespace Catch { - - enum class CaseSensitive { Yes, No }; - -} // namespace Catch - -#endif // CATCH_CASE_SENSITIVE_HPP_INCLUDED - - -#ifndef CATCH_CONFIG_HPP_INCLUDED -#define CATCH_CONFIG_HPP_INCLUDED - - - -#ifndef CATCH_TEST_SPEC_HPP_INCLUDED -#define CATCH_TEST_SPEC_HPP_INCLUDED - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - - - -#ifndef CATCH_WILDCARD_PATTERN_HPP_INCLUDED -#define CATCH_WILDCARD_PATTERN_HPP_INCLUDED - - -#include - -namespace Catch -{ - class WildcardPattern { - enum WildcardPosition { - NoWildcard = 0, - WildcardAtStart = 1, - WildcardAtEnd = 2, - WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd - }; - - public: - - WildcardPattern( std::string const& pattern, CaseSensitive caseSensitivity ); - bool matches( std::string const& str ) const; - - private: - std::string normaliseString( std::string const& str ) const; - CaseSensitive m_caseSensitivity; - WildcardPosition m_wildcard = NoWildcard; - std::string m_pattern; - }; -} - -#endif // CATCH_WILDCARD_PATTERN_HPP_INCLUDED - -#include -#include -#include - -namespace Catch { - - class IConfig; - struct TestCaseInfo; - class TestCaseHandle; - - class TestSpec { - - class Pattern { - public: - explicit Pattern( std::string const& name ); - virtual ~Pattern(); - virtual bool matches( TestCaseInfo const& testCase ) const = 0; - std::string const& name() const; - private: - virtual void serializeTo( std::ostream& out ) const = 0; - // Writes string that would be reparsed into the pattern - friend std::ostream& operator<<(std::ostream& out, - Pattern const& pattern) { - pattern.serializeTo( out ); - return out; - } - - std::string const m_name; - }; - - class NamePattern : public Pattern { - public: - explicit NamePattern( std::string const& name, std::string const& filterString ); - bool matches( TestCaseInfo const& testCase ) const override; - private: - void serializeTo( std::ostream& out ) const override; - - WildcardPattern m_wildcardPattern; - }; - - class TagPattern : public Pattern { - public: - explicit TagPattern( std::string const& tag, std::string const& filterString ); - bool matches( TestCaseInfo const& testCase ) const override; - private: - void serializeTo( std::ostream& out ) const override; - - std::string m_tag; - }; - - struct Filter { - std::vector> m_required; - std::vector> m_forbidden; - - //! Serializes this filter into a string that would be parsed into - //! an equivalent filter - void serializeTo( std::ostream& out ) const; - friend std::ostream& operator<<(std::ostream& out, Filter const& f) { - f.serializeTo( out ); - return out; - } - - bool matches( TestCaseInfo const& testCase ) const; - }; - - static std::string extractFilterName( Filter const& filter ); - - public: - struct FilterMatch { - std::string name; - std::vector tests; - }; - using Matches = std::vector; - using vectorStrings = std::vector; - - bool hasFilters() const; - bool matches( TestCaseInfo const& testCase ) const; - Matches matchesByFilter( std::vector const& testCases, IConfig const& config ) const; - const vectorStrings & getInvalidSpecs() const; - - private: - std::vector m_filters; - std::vector m_invalidSpecs; - - friend class TestSpecParser; - //! Serializes this test spec into a string that would be parsed into - //! equivalent test spec - void serializeTo( std::ostream& out ) const; - friend std::ostream& operator<<(std::ostream& out, - TestSpec const& spec) { - spec.serializeTo( out ); - return out; - } - }; -} - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -#endif // CATCH_TEST_SPEC_HPP_INCLUDED - - -#ifndef CATCH_OPTIONAL_HPP_INCLUDED -#define CATCH_OPTIONAL_HPP_INCLUDED - - -#include - -namespace Catch { - - // An optional type - template - class Optional { - public: - Optional(): nullableValue( nullptr ) {} - ~Optional() { reset(); } - - Optional( T const& _value ): - nullableValue( new ( storage ) T( _value ) ) {} - Optional( T&& _value ): - nullableValue( new ( storage ) T( CATCH_MOVE( _value ) ) ) {} - - Optional& operator=( T const& _value ) { - reset(); - nullableValue = new ( storage ) T( _value ); - return *this; - } - Optional& operator=( T&& _value ) { - reset(); - nullableValue = new ( storage ) T( CATCH_MOVE( _value ) ); - return *this; - } - - Optional( Optional const& _other ): - nullableValue( _other ? new ( storage ) T( *_other ) : nullptr ) {} - Optional( Optional&& _other ): - nullableValue( _other ? new ( storage ) T( CATCH_MOVE( *_other ) ) - : nullptr ) {} - - Optional& operator=( Optional const& _other ) { - if ( &_other != this ) { - reset(); - if ( _other ) { nullableValue = new ( storage ) T( *_other ); } - } - return *this; - } - Optional& operator=( Optional&& _other ) { - if ( &_other != this ) { - reset(); - if ( _other ) { - nullableValue = new ( storage ) T( CATCH_MOVE( *_other ) ); - } - } - return *this; - } - - void reset() { - if ( nullableValue ) { nullableValue->~T(); } - nullableValue = nullptr; - } - - T& operator*() { - assert(nullableValue); - return *nullableValue; - } - T const& operator*() const { - assert(nullableValue); - return *nullableValue; - } - T* operator->() { - assert(nullableValue); - return nullableValue; - } - const T* operator->() const { - assert(nullableValue); - 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(); - } - - friend bool operator==(Optional const& a, Optional const& b) { - if (a.none() && b.none()) { - return true; - } else if (a.some() && b.some()) { - return *a == *b; - } else { - return false; - } - } - friend bool operator!=(Optional const& a, Optional const& b) { - return !( a == b ); - } - - private: - T* nullableValue; - alignas(alignof(T)) char storage[sizeof(T)]; - }; - -} // end namespace Catch - -#endif // CATCH_OPTIONAL_HPP_INCLUDED - - -#ifndef CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED -#define CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED - -#include - -namespace Catch { - - enum class GenerateFrom { - Time, - RandomDevice, - //! Currently equivalent to RandomDevice, but can change at any point - Default - }; - - std::uint32_t generateRandomSeed(GenerateFrom from); - -} // end namespace Catch - -#endif // CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED - - -#ifndef CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED -#define CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED - - -#include -#include -#include - -namespace Catch { - - enum class ColourMode : std::uint8_t; - - namespace Detail { - //! Splits the reporter spec into reporter name and kv-pair options - std::vector splitReporterSpec( StringRef reporterSpec ); - - Optional stringToColourMode( StringRef colourMode ); - } - - /** - * Structured reporter spec that a reporter can be created from - * - * Parsing has been validated, but semantics have not. This means e.g. - * that the colour mode is known to Catch2, but it might not be - * compiled into the binary, and the output filename might not be - * openable. - */ - class ReporterSpec { - std::string m_name; - Optional m_outputFileName; - Optional m_colourMode; - std::map m_customOptions; - - friend bool operator==( ReporterSpec const& lhs, - ReporterSpec const& rhs ); - friend bool operator!=( ReporterSpec const& lhs, - ReporterSpec const& rhs ) { - return !( lhs == rhs ); - } - - public: - ReporterSpec( - std::string name, - Optional outputFileName, - Optional colourMode, - std::map customOptions ); - - std::string const& name() const { return m_name; } - - Optional const& outputFile() const { - return m_outputFileName; - } - - Optional const& colourMode() const { return m_colourMode; } - - std::map const& customOptions() const { - return m_customOptions; - } - }; - - /** - * Parses provided reporter spec string into - * - * Returns empty optional on errors, e.g. - * * field that is not first and not a key+value pair - * * duplicated keys in kv pair - * * unknown catch reporter option - * * empty key/value in an custom kv pair - * * ... - */ - Optional parseReporterSpec( StringRef reporterSpec ); - -} - -#endif // CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED - -#include -#include -#include -#include - -namespace Catch { - - class IStream; - - /** - * `ReporterSpec` but with the defaults filled in. - * - * Like `ReporterSpec`, the semantics are unchecked. - */ - struct ProcessedReporterSpec { - std::string name; - std::string outputFilename; - ColourMode colourMode; - std::map customOptions; - friend bool operator==( ProcessedReporterSpec const& lhs, - ProcessedReporterSpec const& rhs ); - friend bool operator!=( ProcessedReporterSpec const& lhs, - ProcessedReporterSpec const& rhs ) { - return !( lhs == rhs ); - } - }; - - struct ConfigData { - - bool listTests = false; - bool listTags = false; - bool listReporters = false; - bool listListeners = false; - - bool showSuccessfulTests = false; - bool shouldDebugBreak = false; - bool noThrow = false; - bool showHelp = false; - bool showInvisibles = false; - bool filenamesAsTags = false; - bool libIdentify = false; - bool allowZeroTests = false; - - int abortAfter = -1; - uint32_t rngSeed = generateRandomSeed(GenerateFrom::Default); - - unsigned int shardCount = 1; - unsigned int shardIndex = 0; - - bool skipBenchmarks = false; - bool benchmarkNoAnalysis = false; - unsigned int benchmarkSamples = 100; - double benchmarkConfidenceInterval = 0.95; - unsigned int benchmarkResamples = 100'000; - std::chrono::milliseconds::rep benchmarkWarmupTime = 100; - - Verbosity verbosity = Verbosity::Normal; - WarnAbout::What warnings = WarnAbout::Nothing; - ShowDurations showDurations = ShowDurations::DefaultForReporter; - double minDuration = -1; - TestRunOrder runOrder = TestRunOrder::Declared; - ColourMode defaultColourMode = ColourMode::PlatformDefault; - WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; - - std::string defaultOutputFilename; - std::string name; - std::string processName; - std::vector reporterSpecifications; - - std::vector testsOrTags; - std::vector sectionsToRun; - }; - - - class Config : public IConfig { - public: - - Config() = default; - Config( ConfigData const& data ); - ~Config() override; // = default in the cpp file - - bool listTests() const; - bool listTags() const; - bool listReporters() const; - bool listListeners() const; - - std::vector const& getReporterSpecs() const; - std::vector const& - getProcessedReporterSpecs() const; - - std::vector const& getTestsOrTags() const override; - std::vector const& getSectionsToRun() const override; - - TestSpec const& testSpec() const override; - bool hasTestFilters() const override; - - bool showHelp() const; - - // IConfig interface - bool allowThrows() const override; - StringRef name() const override; - bool includeSuccessfulResults() const override; - bool warnAboutMissingAssertions() const override; - bool warnAboutUnmatchedTestSpecs() const override; - bool zeroTestsCountAsSuccess() const override; - ShowDurations showDurations() const override; - double minDuration() const override; - TestRunOrder runOrder() const override; - uint32_t rngSeed() const override; - unsigned int shardCount() const override; - unsigned int shardIndex() const override; - ColourMode defaultColourMode() const override; - bool shouldDebugBreak() const override; - int abortAfter() const override; - bool showInvisibles() const override; - Verbosity verbosity() const override; - bool skipBenchmarks() const override; - bool benchmarkNoAnalysis() const override; - unsigned int benchmarkSamples() const override; - double benchmarkConfidenceInterval() const override; - unsigned int benchmarkResamples() const override; - std::chrono::milliseconds benchmarkWarmupTime() const override; - - private: - // Reads Bazel env vars and applies them to the config - void readBazelEnvVars(); - - ConfigData m_data; - std::vector m_processedReporterSpecs; - TestSpec m_testSpec; - bool m_hasTestFilters = false; - }; -} // end namespace Catch - -#endif // CATCH_CONFIG_HPP_INCLUDED - - -#ifndef CATCH_GET_RANDOM_SEED_HPP_INCLUDED -#define CATCH_GET_RANDOM_SEED_HPP_INCLUDED - -#include - -namespace Catch { - //! Returns Catch2's current RNG seed. - std::uint32_t getSeed(); -} - -#endif // CATCH_GET_RANDOM_SEED_HPP_INCLUDED - - -#ifndef CATCH_MESSAGE_HPP_INCLUDED -#define CATCH_MESSAGE_HPP_INCLUDED - - - - -/** \file - * Wrapper for the CATCH_CONFIG_PREFIX_MESSAGES configuration option - * - * CATCH_CONFIG_PREFIX_ALL can be used to avoid clashes with other macros - * by prepending CATCH_. This may not be desirable if the only clashes are with - * logger macros such as INFO and WARN. In this cases - * CATCH_CONFIG_PREFIX_MESSAGES can be used to only prefix a small subset - * of relevant macros. - * - */ - -#ifndef CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED -#define CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED - - -#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_PREFIX_MESSAGES) - #define CATCH_CONFIG_PREFIX_MESSAGES -#endif - -#endif // CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED - - -#ifndef CATCH_STREAM_END_STOP_HPP_INCLUDED -#define CATCH_STREAM_END_STOP_HPP_INCLUDED - - -namespace Catch { - - // Use this in variadic streaming macros to allow - // << +StreamEndStop - // as well as - // << stuff +StreamEndStop - struct StreamEndStop { - constexpr StringRef operator+() const { return StringRef(); } - - template - constexpr friend T const& operator+( T const& value, StreamEndStop ) { - return value; - } - }; - -} // namespace Catch - -#endif // CATCH_STREAM_END_STOP_HPP_INCLUDED - - -#ifndef CATCH_MESSAGE_INFO_HPP_INCLUDED -#define CATCH_MESSAGE_INFO_HPP_INCLUDED - - -#include - -namespace Catch { - - struct MessageInfo { - MessageInfo( StringRef _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ); - - StringRef macroName; - std::string message; - SourceLineInfo lineInfo; - ResultWas::OfType type; - unsigned int sequence; - - bool operator == (MessageInfo const& other) const { - return sequence == other.sequence; - } - bool operator < (MessageInfo const& other) const { - return sequence < other.sequence; - } - private: - static unsigned int globalCount; - }; - -} // end namespace Catch - -#endif // CATCH_MESSAGE_INFO_HPP_INCLUDED - -#include -#include - -namespace Catch { - - struct SourceLineInfo; - class IResultCapture; - - struct MessageStream { - - template - MessageStream& operator << ( T const& value ) { - m_stream << value; - return *this; - } - - ReusableStringStream m_stream; - }; - - struct MessageBuilder : MessageStream { - MessageBuilder( StringRef macroName, - SourceLineInfo const& lineInfo, - ResultWas::OfType type ): - m_info(macroName, lineInfo, type) {} - - template - MessageBuilder&& operator << ( T const& value ) && { - m_stream << value; - return CATCH_MOVE(*this); - } - - MessageInfo m_info; - }; - - class ScopedMessage { - public: - explicit ScopedMessage( MessageBuilder&& builder ); - ScopedMessage( ScopedMessage& duplicate ) = delete; - ScopedMessage( ScopedMessage&& old ) noexcept; - ~ScopedMessage(); - - MessageInfo m_info; - bool m_moved = false; - }; - - class Capturer { - std::vector m_messages; - IResultCapture& m_resultCapture; - size_t m_captured = 0; - public: - Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); - - Capturer(Capturer const&) = delete; - Capturer& operator=(Capturer const&) = delete; - - ~Capturer(); - - void captureValue( size_t index, std::string const& value ); - - template - void captureValues( size_t index, T const& value ) { - captureValue( index, Catch::Detail::stringify( value ) ); - } - - template - void captureValues( size_t index, T const& value, Ts const&... values ) { - captureValue( index, Catch::Detail::stringify(value) ); - captureValues( index+1, values... ); - } - }; - -} // end namespace Catch - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ - catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ - catchAssertionHandler.complete(); \ - } while( false ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ - Catch::Capturer varName( macroName##_catch_sr, \ - CATCH_INTERNAL_LINEINFO, \ - Catch::ResultWas::Info, \ - #__VA_ARGS__##_catch_sr ); \ - varName.captureValues( 0, __VA_ARGS__ ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_INFO( macroName, log ) \ - const Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ - Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) - - -#if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) - - #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) - #define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg ) - #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) - #define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__ ) - -#elif defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) - - #define CATCH_INFO( msg ) (void)(0) - #define CATCH_UNSCOPED_INFO( msg ) (void)(0) - #define CATCH_WARN( msg ) (void)(0) - #define CATCH_CAPTURE( ... ) (void)(0) - -#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) - - #define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) - #define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg ) - #define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) - #define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__ ) - -#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) - - #define INFO( msg ) (void)(0) - #define UNSCOPED_INFO( msg ) (void)(0) - #define WARN( msg ) (void)(0) - #define CAPTURE( ... ) (void)(0) - -#endif // end of user facing macro declarations - - - - -#endif // CATCH_MESSAGE_HPP_INCLUDED - - -#ifndef CATCH_SECTION_INFO_HPP_INCLUDED -#define CATCH_SECTION_INFO_HPP_INCLUDED - - - -#ifndef CATCH_TOTALS_HPP_INCLUDED -#define CATCH_TOTALS_HPP_INCLUDED - -#include - -namespace Catch { - - struct Counts { - Counts operator - ( Counts const& other ) const; - Counts& operator += ( Counts const& other ); - - std::uint64_t total() const; - bool allPassed() const; - bool allOk() const; - - std::uint64_t passed = 0; - std::uint64_t failed = 0; - std::uint64_t failedButOk = 0; - std::uint64_t skipped = 0; - }; - - struct Totals { - - Totals operator - ( Totals const& other ) const; - Totals& operator += ( Totals const& other ); - - Totals delta( Totals const& prevTotals ) const; - - Counts assertions; - Counts testCases; - }; -} - -#endif // CATCH_TOTALS_HPP_INCLUDED - -#include - -namespace Catch { - - struct SectionInfo { - // The last argument is ignored, so that people can write - // SECTION("ShortName", "Proper description that is long") and - // still use the `-c` flag comfortably. - SectionInfo( SourceLineInfo const& _lineInfo, std::string _name, - const char* const = nullptr ): - name(CATCH_MOVE(_name)), - lineInfo(_lineInfo) - {} - - std::string name; - SourceLineInfo lineInfo; - }; - - struct SectionEndInfo { - SectionInfo sectionInfo; - Counts prevAssertions; - double durationInSeconds; - }; - -} // end namespace Catch - -#endif // CATCH_SECTION_INFO_HPP_INCLUDED - - -#ifndef CATCH_SESSION_HPP_INCLUDED -#define CATCH_SESSION_HPP_INCLUDED - - - -#ifndef CATCH_COMMANDLINE_HPP_INCLUDED -#define CATCH_COMMANDLINE_HPP_INCLUDED - - - -#ifndef CATCH_CLARA_HPP_INCLUDED -#define CATCH_CLARA_HPP_INCLUDED - -#if defined( __clang__ ) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wweak-vtables" -# pragma clang diagnostic ignored "-Wshadow" -# pragma clang diagnostic ignored "-Wdeprecated" -#endif - -#if defined( __GNUC__ ) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wsign-conversion" -#endif - -#ifndef CLARA_CONFIG_OPTIONAL_TYPE -# ifdef __has_include -# if __has_include( ) && __cplusplus >= 201703L -# include -# define CLARA_CONFIG_OPTIONAL_TYPE std::optional -# endif -# endif -#endif - - -#include -#include -#include -#include -#include -#include -#include - -namespace Catch { - namespace Clara { - - class Args; - class Parser; - - // enum of result types from a parse - enum class ParseResultType { - Matched, - NoMatch, - ShortCircuitAll, - ShortCircuitSame - }; - - struct accept_many_t {}; - constexpr accept_many_t accept_many {}; - - namespace Detail { - struct fake_arg { - template - operator T(); - }; - - template - struct is_unary_function : std::false_type {}; - - template - struct is_unary_function< - F, - Catch::Detail::void_t()( fake_arg() ) ) - > - > : std::true_type {}; - - // 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 = std::remove_const_t>; - using ReturnType = ReturnT; - }; - - class TokenStream; - - // 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; - StringRef token; - }; - - // 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(); - - public: - explicit TokenStream( Args const& args ); - TokenStream( Iterator it, Iterator itEnd ); - - explicit operator bool() const { - return !m_tokenBuffer.empty() || it != itEnd; - } - - size_t count() const { - return m_tokenBuffer.size() + ( itEnd - it ); - } - - Token operator*() const { - assert( !m_tokenBuffer.empty() ); - return m_tokenBuffer.front(); - } - - Token const* operator->() const { - assert( !m_tokenBuffer.empty() ); - return &m_tokenBuffer.front(); - } - - TokenStream& operator++(); - }; - - //! Denotes type of a parsing result - enum class ResultType { - Ok, ///< No errors - LogicError, ///< Error in user-specified arguments for - ///< construction - RuntimeError ///< Error in parsing inputs - }; - - class ResultBase { - protected: - ResultBase( ResultType type ): m_type( type ) {} - virtual ~ResultBase(); // = default; - - - ResultBase(ResultBase const&) = default; - ResultBase& operator=(ResultBase const&) = default; - ResultBase(ResultBase&&) = default; - ResultBase& operator=(ResultBase&&) = default; - - virtual void enforceOk() const = 0; - - ResultType m_type; - }; - - template - class ResultValueBase : public ResultBase { - public: - T const& value() const& { - enforceOk(); - return m_value; - } - T&& value() && { - enforceOk(); - return CATCH_MOVE( m_value ); - } - - protected: - ResultValueBase( ResultType type ): ResultBase( type ) {} - - ResultValueBase( ResultValueBase const& other ): - ResultBase( other ) { - if ( m_type == ResultType::Ok ) - new ( &m_value ) T( other.m_value ); - } - ResultValueBase( ResultValueBase&& other ): - ResultBase( other ) { - if ( m_type == ResultType::Ok ) - new ( &m_value ) T( CATCH_MOVE(other.m_value) ); - } - - - ResultValueBase( ResultType, T const& value ): - ResultBase( ResultType::Ok ) { - new ( &m_value ) T( value ); - } - ResultValueBase( ResultType, T&& value ): - ResultBase( ResultType::Ok ) { - new ( &m_value ) T( CATCH_MOVE(value) ); - } - - ResultValueBase& operator=( ResultValueBase const& other ) { - if ( m_type == ResultType::Ok ) - m_value.~T(); - ResultBase::operator=( other ); - if ( m_type == ResultType::Ok ) - new ( &m_value ) T( other.m_value ); - return *this; - } - ResultValueBase& operator=( ResultValueBase&& other ) { - if ( m_type == ResultType::Ok ) m_value.~T(); - ResultBase::operator=( other ); - if ( m_type == ResultType::Ok ) - new ( &m_value ) T( CATCH_MOVE(other.m_value) ); - return *this; - } - - - ~ResultValueBase() override { - if ( m_type == ResultType::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() != ResultType::Ok ); - } - - template - static auto ok( U&& value ) -> BasicResult { - return { ResultType::Ok, CATCH_FORWARD(value) }; - } - static auto ok() -> BasicResult { return { ResultType::Ok }; } - static auto logicError( std::string&& message ) - -> BasicResult { - return { ResultType::LogicError, CATCH_MOVE(message) }; - } - static auto runtimeError( std::string&& message ) - -> BasicResult { - return { ResultType::RuntimeError, CATCH_MOVE(message) }; - } - - explicit operator bool() const { - return m_type == ResultType::Ok; - } - auto type() const -> ResultType { return m_type; } - auto errorMessage() const -> std::string const& { - return m_errorMessage; - } - - protected: - void enforceOk() const override { - - // Errors shouldn't reach this point, but if they do - // the actual error message will be in m_errorMessage - assert( m_type != ResultType::LogicError ); - assert( m_type != ResultType::RuntimeError ); - if ( m_type != ResultType::Ok ) - std::abort(); - } - - std::string - m_errorMessage; // Only populated if resultType is an error - - BasicResult( ResultType type, - std::string&& message ): - ResultValueBase( type ), m_errorMessage( CATCH_MOVE(message) ) { - assert( m_type != ResultType::Ok ); - } - - using ResultValueBase::ResultValueBase; - using ResultBase::m_type; - }; - - class ParseState { - public: - ParseState( ParseResultType type, - TokenStream remainingTokens ); - - ParseResultType type() const { return m_type; } - TokenStream const& remainingTokens() const& { - return m_remainingTokens; - } - TokenStream&& remainingTokens() && { - return CATCH_MOVE( m_remainingTokens ); - } - - private: - ParseResultType m_type; - TokenStream m_remainingTokens; - }; - - using Result = BasicResult; - using ParserResult = BasicResult; - using InternalParseResult = BasicResult; - - struct HelpColumns { - std::string left; - StringRef descriptions; - }; - - template - ParserResult convertInto( std::string const& source, T& target ) { - std::stringstream ss( source ); - ss >> target; - if ( ss.fail() ) { - return ParserResult::runtimeError( - "Unable to convert '" + source + - "' to destination type" ); - } else { - return ParserResult::ok( ParseResultType::Matched ); - } - } - ParserResult convertInto( std::string const& source, - std::string& target ); - ParserResult convertInto( std::string const& source, bool& target ); - -#ifdef CLARA_CONFIG_OPTIONAL_TYPE - template - auto convertInto( std::string const& source, - CLARA_CONFIG_OPTIONAL_TYPE& target ) - -> ParserResult { - T temp; - auto result = convertInto( source, temp ); - if ( result ) - target = CATCH_MOVE( temp ); - return result; - } -#endif // CLARA_CONFIG_OPTIONAL_TYPE - - struct BoundRef : Catch::Detail::NonCopyable { - virtual ~BoundRef() = default; - virtual bool isContainer() const; - virtual bool isFlag() const; - }; - struct BoundValueRefBase : BoundRef { - virtual auto setValue( std::string const& arg ) - -> ParserResult = 0; - }; - struct BoundFlagRefBase : BoundRef { - virtual auto setFlag( bool flag ) -> ParserResult = 0; - bool isFlag() const override; - }; - - template struct BoundValueRef : BoundValueRefBase { - T& m_ref; - - explicit BoundValueRef( T& ref ): m_ref( ref ) {} - - ParserResult setValue( std::string const& arg ) override { - return convertInto( arg, m_ref ); - } - }; - - template - struct BoundValueRef> : BoundValueRefBase { - std::vector& m_ref; - - explicit BoundValueRef( 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 ) {} - - ParserResult setFlag( bool flag ) override; - }; - - 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 - 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 BoundManyLambda : BoundLambda { - explicit BoundManyLambda( L const& lambda ): BoundLambda( lambda ) {} - bool isContainer() const override { return true; } - }; - - 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 }; - - class ParserBase { - public: - virtual ~ParserBase() = default; - virtual auto validate() const -> Result { return Result::ok(); } - virtual auto parse( std::string const& exeName, - TokenStream tokens ) const - -> InternalParseResult = 0; - virtual size_t cardinality() const; - - InternalParseResult parse( Args const& args ) const; - }; - - 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; - StringRef m_hint; - StringRef m_description; - - explicit ParserRefImpl( std::shared_ptr const& ref ): - m_ref( ref ) {} - - public: - template - ParserRefImpl( accept_many_t, - LambdaT const& ref, - StringRef hint ): - m_ref( std::make_shared>( ref ) ), - m_hint( hint ) {} - - template ::value>> - ParserRefImpl( T& ref, StringRef hint ): - m_ref( std::make_shared>( ref ) ), - m_hint( hint ) {} - - template ::value>> - ParserRefImpl( LambdaT const& ref, StringRef hint ): - m_ref( std::make_shared>( ref ) ), - m_hint( hint ) {} - - DerivedT& operator()( StringRef description ) & { - m_description = description; - return static_cast( *this ); - } - DerivedT&& operator()( StringRef description ) && { - 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; - } - - StringRef hint() const { return m_hint; } - }; - - } // namespace detail - - - // A parser for arguments - class Arg : public Detail::ParserRefImpl { - public: - using ParserRefImpl::ParserRefImpl; - using ParserBase::parse; - - Detail::InternalParseResult - parse(std::string const&, - Detail::TokenStream tokens) const override; - }; - - // A parser for options - class Opt : public Detail::ParserRefImpl { - protected: - std::vector m_optNames; - - public: - template - explicit Opt(LambdaT const& ref) : - ParserRefImpl( - std::make_shared>(ref)) {} - - explicit Opt(bool& ref); - - template ::value>> - Opt( LambdaT const& ref, StringRef hint ): - ParserRefImpl( ref, hint ) {} - - template - Opt( accept_many_t, LambdaT const& ref, StringRef hint ): - ParserRefImpl( accept_many, ref, hint ) {} - - template ::value>> - Opt( T& ref, StringRef hint ): - ParserRefImpl( ref, hint ) {} - - Opt& operator[]( StringRef optName ) & { - m_optNames.push_back(optName); - return *this; - } - Opt&& operator[]( StringRef optName ) && { - m_optNames.push_back( optName ); - return CATCH_MOVE(*this); - } - - Detail::HelpColumns getHelpColumns() const; - - bool isMatch(StringRef optToken) const; - - using ParserBase::parse; - - Detail::InternalParseResult - parse(std::string const&, - Detail::TokenStream tokens) const override; - - Detail::Result validate() const override; - }; - - // Specifies the name of the executable - class ExeName : public Detail::ComposableParserImpl { - std::shared_ptr m_name; - std::shared_ptr m_ref; - - public: - ExeName(); - explicit ExeName(std::string& 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 - Detail::InternalParseResult - parse(std::string const&, - Detail::TokenStream tokens) const override; - - std::string const& name() const { return *m_name; } - Detail::ParserResult set(std::string const& newName); - }; - - - // A Combined parser - class Parser : Detail::ParserBase { - mutable ExeName m_exeName; - std::vector m_options; - std::vector m_args; - - public: - - auto operator|=(ExeName const& exeName) -> Parser& { - m_exeName = exeName; - return *this; - } - - auto operator|=(Arg const& arg) -> Parser& { - m_args.push_back(arg); - return *this; - } - - friend Parser& operator|=( Parser& p, Opt const& opt ) { - p.m_options.push_back( opt ); - return p; - } - friend Parser& operator|=( Parser& p, Opt&& opt ) { - p.m_options.push_back( CATCH_MOVE(opt) ); - return p; - } - - Parser& operator|=(Parser const& other); - - template - friend Parser operator|( Parser const& p, T&& rhs ) { - Parser temp( p ); - temp |= rhs; - return temp; - } - - template - friend Parser operator|( Parser&& p, T&& rhs ) { - p |= CATCH_FORWARD(rhs); - return CATCH_MOVE(p); - } - - std::vector getHelpColumns() const; - - void writeToStream(std::ostream& os) const; - - friend auto operator<<(std::ostream& os, Parser const& parser) - -> std::ostream& { - parser.writeToStream(os); - return os; - } - - Detail::Result validate() const override; - - using ParserBase::parse; - Detail::InternalParseResult - parse(std::string const& exeName, - Detail::TokenStream tokens) const override; - }; - - /** - * Wrapper over argc + argv, assumes that the inputs outlive it - */ - class Args { - friend Detail::TokenStream; - StringRef m_exeName; - std::vector m_args; - - public: - Args(int argc, char const* const* argv); - // Helper constructor for testing - Args(std::initializer_list args); - - StringRef exeName() const { return m_exeName; } - }; - - - // Convenience wrapper for option parser that specifies the help option - struct Help : Opt { - Help(bool& showHelpFlag); - }; - - // Result type for parser operation - using Detail::ParserResult; - - namespace Detail { - template - template - Parser - ComposableParserImpl::operator|(T const& other) const { - return Parser() | static_cast(*this) | other; - } - } - - } // namespace Clara -} // namespace Catch - -#if defined( __clang__ ) -# pragma clang diagnostic pop -#endif - -#if defined( __GNUC__ ) -# pragma GCC diagnostic pop -#endif - -#endif // CATCH_CLARA_HPP_INCLUDED - -namespace Catch { - - struct ConfigData; - - Clara::Parser makeCommandLineParser( ConfigData& config ); - -} // end namespace Catch - -#endif // CATCH_COMMANDLINE_HPP_INCLUDED - -namespace Catch { - - class Session : Detail::NonCopyable { - public: - - Session(); - ~Session(); - - void showHelp() const; - void libIdentify(); - - int applyCommandLine( int argc, char const * const * argv ); - #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) - int applyCommandLine( int argc, wchar_t const * const * argv ); - #endif - - void useConfigData( ConfigData const& configData ); - - template - int run(int argc, CharT const * const argv[]) { - if (m_startupExceptions) - return 1; - int returnCode = applyCommandLine(argc, argv); - if (returnCode == 0) - returnCode = run(); - return returnCode; - } - - int run(); - - Clara::Parser const& cli() const; - void cli( Clara::Parser const& newParser ); - ConfigData& configData(); - Config& config(); - private: - int runInternal(); - - Clara::Parser m_cli; - ConfigData m_configData; - Detail::unique_ptr m_config; - bool m_startupExceptions = false; - }; - -} // end namespace Catch - -#endif // CATCH_SESSION_HPP_INCLUDED - - -#ifndef CATCH_TAG_ALIAS_HPP_INCLUDED -#define CATCH_TAG_ALIAS_HPP_INCLUDED - - -#include - -namespace Catch { - - struct TagAlias { - TagAlias(std::string const& _tag, SourceLineInfo _lineInfo): - tag(_tag), - lineInfo(_lineInfo) - {} - - std::string tag; - SourceLineInfo lineInfo; - }; - -} // end namespace Catch - -#endif // CATCH_TAG_ALIAS_HPP_INCLUDED - - -#ifndef CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED -#define CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED - - -namespace Catch { - - struct RegistrarForTagAliases { - RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - }; - -} // end namespace Catch - -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -#endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED - - -#ifndef CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED -#define CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED - -// We need this suppression to leak, because it took until GCC 10 -// for the front end to handle local suppression via _Pragma properly -// inside templates (so `TEMPLATE_TEST_CASE` and co). -// **THIS IS DIFFERENT FOR STANDARD TESTS, WHERE GCC 9 IS SUFFICIENT** -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 -#pragma GCC diagnostic ignored "-Wparentheses" -#endif - - - - -#ifndef CATCH_TEST_MACROS_HPP_INCLUDED -#define CATCH_TEST_MACROS_HPP_INCLUDED - - - -#ifndef CATCH_TEST_MACRO_IMPL_HPP_INCLUDED -#define CATCH_TEST_MACRO_IMPL_HPP_INCLUDED - - - -#ifndef CATCH_ASSERTION_HANDLER_HPP_INCLUDED -#define CATCH_ASSERTION_HANDLER_HPP_INCLUDED - - - -#ifndef CATCH_DECOMPOSER_HPP_INCLUDED -#define CATCH_DECOMPOSER_HPP_INCLUDED - - - -#ifndef CATCH_COMPARE_TRAITS_HPP_INCLUDED -#define CATCH_COMPARE_TRAITS_HPP_INCLUDED - - -#include - -namespace Catch { - namespace Detail { - -#if defined( __GNUC__ ) && !defined( __clang__ ) -# pragma GCC diagnostic push - // GCC likes to complain about comparing bool with 0, in the decltype() - // that defines the comparable traits below. -# pragma GCC diagnostic ignored "-Wbool-compare" - // "ordered comparison of pointer with integer zero" same as above, - // but it does not have a separate warning flag to suppress -# pragma GCC diagnostic ignored "-Wextra" - // Did you know that comparing floats with `0` directly - // is super-duper dangerous in unevaluated context? -# pragma GCC diagnostic ignored "-Wfloat-equal" -#endif - -#if defined( __clang__ ) -# pragma clang diagnostic push - // Did you know that comparing floats with `0` directly - // is super-duper dangerous in unevaluated context? -# pragma clang diagnostic ignored "-Wfloat-equal" -#endif - -#define CATCH_DEFINE_COMPARABLE_TRAIT( id, op ) \ - template \ - struct is_##id##_comparable : std::false_type {}; \ - template \ - struct is_##id##_comparable< \ - T, \ - U, \ - void_t() op std::declval() )>> \ - : std::true_type {}; \ - template \ - struct is_##id##_0_comparable : std::false_type {}; \ - template \ - struct is_##id##_0_comparable() op 0 )>> \ - : std::true_type {}; - - // We need all 6 pre-spaceship comparison ops: <, <=, >, >=, ==, != - CATCH_DEFINE_COMPARABLE_TRAIT( lt, < ) - CATCH_DEFINE_COMPARABLE_TRAIT( le, <= ) - CATCH_DEFINE_COMPARABLE_TRAIT( gt, > ) - CATCH_DEFINE_COMPARABLE_TRAIT( ge, >= ) - CATCH_DEFINE_COMPARABLE_TRAIT( eq, == ) - CATCH_DEFINE_COMPARABLE_TRAIT( ne, != ) - -#undef CATCH_DEFINE_COMPARABLE_TRAIT - -#if defined( __GNUC__ ) && !defined( __clang__ ) -# pragma GCC diagnostic pop -#endif -#if defined( __clang__ ) -# pragma clang diagnostic pop -#endif - - - } // namespace Detail -} // namespace Catch - -#endif // CATCH_COMPARE_TRAITS_HPP_INCLUDED - - -#ifndef CATCH_LOGICAL_TRAITS_HPP_INCLUDED -#define CATCH_LOGICAL_TRAITS_HPP_INCLUDED - -#include - -namespace Catch { -namespace Detail { - -#if defined( __cpp_lib_logical_traits ) && __cpp_lib_logical_traits >= 201510 - - using std::conjunction; - using std::disjunction; - using std::negation; - -#else - - template struct conjunction : std::true_type {}; - template struct conjunction : B1 {}; - template - struct conjunction - : std::conditional_t, B1> {}; - - template struct disjunction : std::false_type {}; - template struct disjunction : B1 {}; - template - struct disjunction - : std::conditional_t> {}; - - template - struct negation : std::integral_constant {}; - -#endif - -} // namespace Detail -} // namespace Catch - -#endif // CATCH_LOGICAL_TRAITS_HPP_INCLUDED - -#include -#include - -/** \file - * Why does decomposing look the way it does: - * - * Conceptually, decomposing is simple. We change `REQUIRE( a == b )` into - * `Decomposer{} <= a == b`, so that `Decomposer{} <= a` is evaluated first, - * and our custom operator is used for `a == b`, because `a` is transformed - * into `ExprLhs` and then into `BinaryExpr`. - * - * In practice, decomposing ends up a mess, because we have to support - * various fun things. - * - * 1) Types that are only comparable with literal 0, and they do this by - * comparing against a magic type with pointer constructor and deleted - * other constructors. Example: `REQUIRE((a <=> b) == 0)` in libstdc++ - * - * 2) Types that are only comparable with literal 0, and they do this by - * comparing against a magic type with consteval integer constructor. - * Example: `REQUIRE((a <=> b) == 0)` in current MSVC STL. - * - * 3) Types that have no linkage, and so we cannot form a reference to - * them. Example: some implementations of traits. - * - * 4) Starting with C++20, when the compiler sees `a == b`, it also uses - * `b == a` when constructing the overload set. For us this means that - * when the compiler handles `ExprLhs == b`, it also tries to resolve - * the overload set for `b == ExprLhs`. - * - * To accomodate these use cases, decomposer ended up rather complex. - * - * 1) These types are handled by adding SFINAE overloads to our comparison - * operators, checking whether `T == U` are comparable with the given - * operator, and if not, whether T (or U) are comparable with literal 0. - * If yes, the overload compares T (or U) with 0 literal inline in the - * definition. - * - * Note that for extra correctness, we check that the other type is - * either an `int` (literal 0 is captured as `int` by templates), or - * a `long` (some platforms use 0L for `NULL` and we want to support - * that for pointer comparisons). - * - * 2) For these types, `is_foo_comparable` is true, but letting - * them fall into the overload that actually does `T == int` causes - * compilation error. Handling them requires that the decomposition - * is `constexpr`, so that P2564R3 applies and the `consteval` from - * their accompanying magic type is propagated through the `constexpr` - * call stack. - * - * However this is not enough to handle these types automatically, - * because our default is to capture types by reference, to avoid - * runtime copies. While these references cannot become dangling, - * they outlive the constexpr context and thus the default capture - * path cannot be actually constexpr. - * - * The solution is to capture these types by value, by explicitly - * specializing `Catch::capture_by_value` for them. Catch2 provides - * specialization for `std::foo_ordering`s, but users can specialize - * the trait for their own types as well. - * - * 3) If a type has no linkage, we also cannot capture it by reference. - * The solution is once again to capture them by value. We handle - * the common cases by using `std::is_arithmetic` as the default - * for `Catch::capture_by_value`, but that is only a some-effort - * heuristic. But as with 2), users can specialize `capture_by_value` - * for their own types as needed. - * - * 4) To support C++20 and make the SFINAE on our decomposing operators - * work, the SFINAE has to happen in return type, rather than in - * a template type. This is due to our use of logical type traits - * (`conjunction`/`disjunction`/`negation`), that we use to workaround - * an issue in older (9-) versions of GCC. I still blame C++20 for - * this, because without the comparison order switching, the logical - * traits could still be used in template type. - * - * There are also other side concerns, e.g. supporting both `REQUIRE(a)` - * and `REQUIRE(a == b)`, or making `REQUIRE_THAT(a, IsEqual(b))` slot - * nicely into the same expression handling logic, but these are rather - * straightforward and add only a bit of complexity (e.g. common base - * class for decomposed expressions). - */ - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4389) // '==' : signed/unsigned mismatch -#pragma warning(disable:4018) // more "signed/unsigned mismatch" -#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) -#pragma warning(disable:4180) // qualifier applied to function type has no meaning -#pragma warning(disable:4800) // Forcing result to true or false -#endif - -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wsign-compare" -# pragma clang diagnostic ignored "-Wnon-virtual-dtor" -#elif defined __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wsign-compare" -# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -#endif - -#if defined(CATCH_CPP20_OR_GREATER) && __has_include() -# include -# if defined( __cpp_lib_three_way_comparison ) && \ - __cpp_lib_three_way_comparison >= 201907L -# define CATCH_CONFIG_CPP20_COMPARE_OVERLOADS -# endif -#endif - -namespace Catch { - - namespace Detail { - // This was added in C++20, but we require only C++14 for now. - template - using RemoveCVRef_t = std::remove_cv_t>; - } - - // Note: There is nothing that stops us from extending this, - // e.g. to `std::is_scalar`, but the more encompassing - // traits are usually also more expensive. For now we - // keep this as it used to be and it can be changed later. - template - struct capture_by_value - : std::integral_constant{}> {}; - -#if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS ) - template <> - struct capture_by_value : std::true_type {}; - template <> - struct capture_by_value : std::true_type {}; - template <> - struct capture_by_value : std::true_type {}; -#endif - - template - struct always_false : std::false_type {}; - - class ITransientExpression { - bool m_isBinaryExpression; - bool m_result; - - protected: - ~ITransientExpression() = default; - - public: - constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } - constexpr auto getResult() const -> bool { return m_result; } - //! This function **has** to be overriden by the derived class. - virtual void streamReconstructedExpression( std::ostream& os ) const; - - constexpr ITransientExpression( bool isBinaryExpression, bool result ) - : m_isBinaryExpression( isBinaryExpression ), - m_result( result ) - {} - - constexpr ITransientExpression( ITransientExpression const& ) = default; - constexpr ITransientExpression& operator=( ITransientExpression const& ) = default; - - friend std::ostream& operator<<(std::ostream& out, ITransientExpression const& expr) { - expr.streamReconstructedExpression(out); - return out; - } - }; - - void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); - - template - class BinaryExpr : public ITransientExpression { - LhsT m_lhs; - StringRef m_op; - RhsT m_rhs; - - void streamReconstructedExpression( std::ostream &os ) const override { - formatReconstructedExpression - ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); - } - - public: - constexpr BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) - : ITransientExpression{ true, comparisonResult }, - m_lhs( lhs ), - m_op( op ), - m_rhs( rhs ) - {} - - template - auto operator && ( T ) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator || ( T ) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator == ( T ) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator != ( T ) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator > ( T ) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator < ( T ) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator >= ( T ) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - auto operator <= ( T ) const -> BinaryExpr const { - static_assert(always_false::value, - "chained comparisons are not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - }; - - template - class UnaryExpr : public ITransientExpression { - LhsT m_lhs; - - void streamReconstructedExpression( std::ostream &os ) const override { - os << Catch::Detail::stringify( m_lhs ); - } - - public: - explicit constexpr UnaryExpr( LhsT lhs ) - : ITransientExpression{ false, static_cast(lhs) }, - m_lhs( lhs ) - {} - }; - - - template - class ExprLhs { - LhsT m_lhs; - public: - explicit constexpr ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} - -#define CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( id, op ) \ - template \ - constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ - -> std::enable_if_t< \ - Detail::conjunction, \ - Detail::negation>>>::value, \ - BinaryExpr> { \ - return { \ - static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ - } \ - template \ - constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ - -> std::enable_if_t< \ - Detail::conjunction, \ - capture_by_value>::value, \ - BinaryExpr> { \ - return { \ - static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ - } \ - template \ - constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ - -> std::enable_if_t< \ - Detail::conjunction< \ - Detail::negation>, \ - Detail::is_eq_0_comparable, \ - /* We allow long because we want `ptr op NULL` to be accepted */ \ - Detail::disjunction, \ - std::is_same>>::value, \ - BinaryExpr> { \ - if ( rhs != 0 ) { throw_test_failure_exception(); } \ - return { \ - static_cast( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs }; \ - } \ - template \ - constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ - -> std::enable_if_t< \ - Detail::conjunction< \ - Detail::negation>, \ - Detail::is_eq_0_comparable, \ - /* We allow long because we want `ptr op NULL` to be accepted */ \ - Detail::disjunction, \ - std::is_same>>::value, \ - BinaryExpr> { \ - if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); } \ - return { static_cast( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ - } - - CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( eq, == ) - CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( ne, != ) - - #undef CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR - - -#define CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( id, op ) \ - template \ - constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ - -> std::enable_if_t< \ - Detail::conjunction, \ - Detail::negation>>>::value, \ - BinaryExpr> { \ - return { \ - static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ - } \ - template \ - constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ - -> std::enable_if_t< \ - Detail::conjunction, \ - capture_by_value>::value, \ - BinaryExpr> { \ - return { \ - static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ - } \ - template \ - constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ - -> std::enable_if_t< \ - Detail::conjunction< \ - Detail::negation>, \ - Detail::is_##id##_0_comparable, \ - std::is_same>::value, \ - BinaryExpr> { \ - if ( rhs != 0 ) { throw_test_failure_exception(); } \ - return { \ - static_cast( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs }; \ - } \ - template \ - constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ - -> std::enable_if_t< \ - Detail::conjunction< \ - Detail::negation>, \ - Detail::is_##id##_0_comparable, \ - std::is_same>::value, \ - BinaryExpr> { \ - if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); } \ - return { static_cast( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ - } - - CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( lt, < ) - CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( le, <= ) - CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( gt, > ) - CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( ge, >= ) - - #undef CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR - - -#define CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR( op ) \ - template \ - constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ - -> std::enable_if_t< \ - !capture_by_value>::value, \ - BinaryExpr> { \ - return { \ - static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ - } \ - template \ - constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ - -> std::enable_if_t::value, \ - BinaryExpr> { \ - return { \ - static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ - } - - CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(|) - CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(&) - CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(^) - - #undef CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR - - template - friend auto operator && ( ExprLhs &&, RhsT && ) -> BinaryExpr { - static_assert(always_false::value, - "operator&& is not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - template - friend auto operator || ( ExprLhs &&, RhsT && ) -> BinaryExpr { - static_assert(always_false::value, - "operator|| is not supported inside assertions, " - "wrap the expression inside parentheses, or decompose it"); - } - - constexpr auto makeUnaryExpr() const -> UnaryExpr { - return UnaryExpr{ m_lhs }; - } - }; - - struct Decomposer { - template >::value, - int> = 0> - constexpr friend auto operator <= ( Decomposer &&, T && lhs ) -> ExprLhs { - return ExprLhs{ lhs }; - } - - template ::value, int> = 0> - constexpr friend auto operator <= ( Decomposer &&, T value ) -> ExprLhs { - return ExprLhs{ value }; - } - }; - -} // end namespace Catch - -#ifdef _MSC_VER -#pragma warning(pop) -#endif -#ifdef __clang__ -# pragma clang diagnostic pop -#elif defined __GNUC__ -# pragma GCC diagnostic pop -#endif - -#endif // CATCH_DECOMPOSER_HPP_INCLUDED - -#include - -namespace Catch { - - struct AssertionReaction { - bool shouldDebugBreak = false; - bool shouldThrow = false; - bool shouldSkip = false; - }; - - class AssertionHandler { - AssertionInfo m_assertionInfo; - AssertionReaction m_reaction; - bool m_completed = false; - IResultCapture& m_resultCapture; - - public: - AssertionHandler - ( StringRef macroName, - SourceLineInfo const& lineInfo, - StringRef capturedExpression, - ResultDisposition::Flags resultDisposition ); - ~AssertionHandler() { - if ( !m_completed ) { - m_resultCapture.handleIncomplete( m_assertionInfo ); - } - } - - - template - constexpr void handleExpr( ExprLhs const& expr ) { - handleExpr( expr.makeUnaryExpr() ); - } - void handleExpr( ITransientExpression const& expr ); - - void handleMessage(ResultWas::OfType resultType, std::string&& message); - - void handleExceptionThrownAsExpected(); - void handleUnexpectedExceptionNotThrown(); - void handleExceptionNotThrownAsExpected(); - void handleThrowingCallSkipped(); - void handleUnexpectedInflightException(); - - void complete(); - - // query - auto allowThrows() const -> bool; - }; - - void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str ); - -} // namespace Catch - -#endif // CATCH_ASSERTION_HANDLER_HPP_INCLUDED - - -#ifndef CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED -#define CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED - - -#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) - #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__##_catch_sr -#else - #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION"_catch_sr -#endif - -#endif // CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED - -// We need this suppression to leak, because it took until GCC 10 -// for the front end to handle local suppression via _Pragma properly -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ <= 9 - #pragma GCC diagnostic ignored "-Wparentheses" -#endif - -#if !defined(CATCH_CONFIG_DISABLE) - -#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) - -/////////////////////////////////////////////////////////////////////////////// -// Another way to speed-up compilation is to omit local try-catch for REQUIRE* -// macros. -#define INTERNAL_CATCH_TRY -#define INTERNAL_CATCH_CATCH( capturer ) - -#else // CATCH_CONFIG_FAST_COMPILE - -#define INTERNAL_CATCH_TRY try -#define INTERNAL_CATCH_CATCH( handler ) catch(...) { (handler).handleUnexpectedInflightException(); } - -#endif - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ - do { /* NOLINT(bugprone-infinite-loop) */ \ - /* The expression should not be evaluated, but warnings should hopefully be checked */ \ - CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \ - Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ - INTERNAL_CATCH_TRY { \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); /* NOLINT(bugprone-chained-comparison) */ \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ - catchAssertionHandler.complete(); \ - } while( (void)0, (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_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ - try { \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ - static_cast(__VA_ARGS__); \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ - } \ - catch( ... ) { \ - catchAssertionHandler.handleUnexpectedInflightException(); \ - } \ - catchAssertionHandler.complete(); \ - } while( false ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ - CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ - static_cast(__VA_ARGS__); \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ - } \ - catch( ... ) { \ - catchAssertionHandler.handleExceptionThrownAsExpected(); \ - } \ - else \ - catchAssertionHandler.handleThrowingCallSkipped(); \ - catchAssertionHandler.complete(); \ - } while( false ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ - CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ - static_cast(expr); \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ - } \ - catch( exceptionType const& ) { \ - catchAssertionHandler.handleExceptionThrownAsExpected(); \ - } \ - catch( ... ) { \ - catchAssertionHandler.handleUnexpectedInflightException(); \ - } \ - else \ - catchAssertionHandler.handleThrowingCallSkipped(); \ - catchAssertionHandler.complete(); \ - } while( false ) - - - -/////////////////////////////////////////////////////////////////////////////// -// 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_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ - CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ - static_cast(__VA_ARGS__); \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ - } \ - catch( ... ) { \ - Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher ); \ - } \ - else \ - catchAssertionHandler.handleThrowingCallSkipped(); \ - catchAssertionHandler.complete(); \ - } while( false ) - -#endif // CATCH_CONFIG_DISABLE - -#endif // CATCH_TEST_MACRO_IMPL_HPP_INCLUDED - - -#ifndef CATCH_SECTION_HPP_INCLUDED -#define CATCH_SECTION_HPP_INCLUDED - - - - -/** \file - * Wrapper for the STATIC_ANALYSIS_SUPPORT configuration option - * - * Some of Catch2's macros can be defined differently to work better with - * static analysis tools, like clang-tidy or coverity. - * Currently the main use case is to show that `SECTION`s are executed - * exclusively, and not all in one run of a `TEST_CASE`. - */ - -#ifndef CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED -#define CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED - - -#if defined(__clang_analyzer__) || defined(__COVERITY__) - #define CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT -#endif - -#if defined( CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT ) && \ - !defined( CATCH_CONFIG_NO_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ) && \ - !defined( CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ) -# define CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT -#endif - - -#endif // CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED - - -#ifndef CATCH_TIMER_HPP_INCLUDED -#define CATCH_TIMER_HPP_INCLUDED - -#include - -namespace Catch { - - class Timer { - uint64_t m_nanoseconds = 0; - public: - void start(); - auto getElapsedNanoseconds() const -> uint64_t; - auto getElapsedMicroseconds() const -> uint64_t; - auto getElapsedMilliseconds() const -> unsigned int; - auto getElapsedSeconds() const -> double; - }; - -} // namespace Catch - -#endif // CATCH_TIMER_HPP_INCLUDED - -namespace Catch { - - class Section : Detail::NonCopyable { - public: - Section( SectionInfo&& info ); - Section( SourceLineInfo const& _lineInfo, - StringRef _name, - const char* const = nullptr ); - ~Section(); - - // This indicates whether the section should be executed or not - explicit operator bool() const; - - private: - SectionInfo m_info; - - Counts m_assertions; - bool m_sectionIncluded; - Timer m_timer; - }; - -} // end namespace Catch - -#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) -# define INTERNAL_CATCH_SECTION( ... ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - if ( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( \ - catch_internal_Section ) = \ - Catch::Section( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -# define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - if ( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( \ - catch_internal_Section ) = \ - Catch::SectionInfo( \ - CATCH_INTERNAL_LINEINFO, \ - ( Catch::ReusableStringStream() << __VA_ARGS__ ) \ - .str() ) ) \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -#else - -// These section definitions imply that at most one section at one level -// will be intered (because only one section's __LINE__ can be equal to -// the dummy `catchInternalSectionHint` variable from `TEST_CASE`). - -namespace Catch { - namespace Detail { - // Intentionally without linkage, as it should only be used as a dummy - // symbol for static analysis. - // The arguments are used as a dummy for checking warnings in the passed - // expressions. - int GetNewSectionHint( StringRef, const char* const = nullptr ); - } // namespace Detail -} // namespace Catch - - -# define INTERNAL_CATCH_SECTION( ... ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ - if ( [[maybe_unused]] const int catchInternalPreviousSectionHint = \ - catchInternalSectionHint, \ - catchInternalSectionHint = \ - Catch::Detail::GetNewSectionHint(__VA_ARGS__); \ - catchInternalPreviousSectionHint == __LINE__ ) \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -# define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ - if ( [[maybe_unused]] const int catchInternalPreviousSectionHint = \ - catchInternalSectionHint, \ - catchInternalSectionHint = Catch::Detail::GetNewSectionHint( \ - ( Catch::ReusableStringStream() << __VA_ARGS__ ).str()); \ - catchInternalPreviousSectionHint == __LINE__ ) \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -#endif - - -#endif // CATCH_SECTION_HPP_INCLUDED - - -#ifndef CATCH_TEST_REGISTRY_HPP_INCLUDED -#define CATCH_TEST_REGISTRY_HPP_INCLUDED - - - -#ifndef CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED -#define CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED - -namespace Catch { - - class ITestInvoker { - public: - virtual void prepareTestCase(); - virtual void tearDownTestCase(); - virtual void invoke() const = 0; - virtual ~ITestInvoker(); // = default - }; - -} // namespace Catch - -#endif // CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED - - -#ifndef CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED -#define CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED - -#define INTERNAL_CATCH_EXPAND1( param ) INTERNAL_CATCH_EXPAND2( param ) -#define INTERNAL_CATCH_EXPAND2( ... ) INTERNAL_CATCH_NO##__VA_ARGS__ -#define INTERNAL_CATCH_DEF( ... ) INTERNAL_CATCH_DEF __VA_ARGS__ -#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF - -#define INTERNAL_CATCH_REMOVE_PARENS( ... ) \ - INTERNAL_CATCH_EXPAND1( INTERNAL_CATCH_DEF __VA_ARGS__ ) - -#endif // CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED - -// GCC 5 and older do not properly handle disabling unused-variable warning -// with a _Pragma. This means that we have to leak the suppression to the -// user code as well :-( -#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5 -#pragma GCC diagnostic ignored "-Wunused-variable" -#endif - - - -namespace Catch { - -template -class TestInvokerAsMethod : public ITestInvoker { - void (C::*m_testAsMethod)(); -public: - constexpr TestInvokerAsMethod( void ( C::*testAsMethod )() ) noexcept: - m_testAsMethod( testAsMethod ) {} - - void invoke() const override { - C obj; - (obj.*m_testAsMethod)(); - } -}; - -Detail::unique_ptr makeTestInvoker( void(*testAsFunction)() ); - -template -Detail::unique_ptr makeTestInvoker( void (C::*testAsMethod)() ) { - return Detail::make_unique>( testAsMethod ); -} - -template -class TestInvokerFixture : public ITestInvoker { - void ( C::*m_testAsMethod )() const; - Detail::unique_ptr m_fixture = nullptr; - -public: - constexpr TestInvokerFixture( void ( C::*testAsMethod )() const ) noexcept: - m_testAsMethod( testAsMethod ) {} - - void prepareTestCase() override { - m_fixture = Detail::make_unique(); - } - - void tearDownTestCase() override { - m_fixture.reset(); - } - - void invoke() const override { - auto* f = m_fixture.get(); - ( f->*m_testAsMethod )(); - } -}; - -template -Detail::unique_ptr makeTestInvokerFixture( void ( C::*testAsMethod )() const ) { - return Detail::make_unique>( testAsMethod ); -} - -struct NameAndTags { - constexpr NameAndTags( StringRef name_ = StringRef(), - StringRef tags_ = StringRef() ) noexcept: - name( name_ ), tags( tags_ ) {} - StringRef name; - StringRef tags; -}; - -struct AutoReg : Detail::NonCopyable { - AutoReg( Detail::unique_ptr invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept; -}; - -} // end namespace Catch - -#if defined(CATCH_CONFIG_DISABLE) - #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ - static inline void TestName() - #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ - namespace{ \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ - void test(); \ - }; \ - } \ - void TestName::test() -#endif - - -#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ - static void TestName(); \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - namespace{ const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - static void TestName() - #define INTERNAL_CATCH_TESTCASE( ... ) \ - INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__ ) - -#else // ^^ !CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT | vv CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT - - -// Dummy registrator for the dumy test case macros -namespace Catch { - namespace Detail { - struct DummyUse { - DummyUse( void ( * )( int ), Catch::NameAndTags const& ); - }; - } // namespace Detail -} // namespace Catch - -// Note that both the presence of the argument and its exact name are -// necessary for the section support. - -// We provide a shadowed variable so that a `SECTION` inside non-`TEST_CASE` -// tests can compile. The redefined `TEST_CASE` shadows this with param. -static int catchInternalSectionHint = 0; - -# define INTERNAL_CATCH_TESTCASE2( fname, ... ) \ - static void fname( int ); \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - static const Catch::Detail::DummyUse INTERNAL_CATCH_UNIQUE_NAME( \ - dummyUser )( &(fname), Catch::NameAndTags{ __VA_ARGS__ } ); \ - CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ - static void fname( [[maybe_unused]] int catchInternalSectionHint ) \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -# define INTERNAL_CATCH_TESTCASE( ... ) \ - INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( dummyFunction ), __VA_ARGS__ ) - - -#endif // CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - namespace{ \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ - void test(); \ - }; \ - const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \ - Catch::makeTestInvoker( &TestName::test ), \ - CATCH_INTERNAL_LINEINFO, \ - #ClassName##_catch_sr, \ - Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - void TestName::test() - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ - INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ ) - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE2( TestName, ClassName, ... ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - namespace { \ - struct TestName : INTERNAL_CATCH_REMOVE_PARENS( ClassName ) { \ - void test() const; \ - }; \ - const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \ - Catch::makeTestInvokerFixture( &TestName::test ), \ - CATCH_INTERNAL_LINEINFO, \ - #ClassName##_catch_sr, \ - Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ - } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - void TestName::test() const - #define INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( ClassName, ... ) \ - INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ ) - - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - namespace { \ - const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \ - Catch::makeTestInvoker( &QualifiedMethod ), \ - CATCH_INTERNAL_LINEINFO, \ - "&" #QualifiedMethod##_catch_sr, \ - Catch::NameAndTags{ __VA_ARGS__ } ); \ - } /* NOLINT */ \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ - do { \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ - } while(false) - - -#endif // CATCH_TEST_REGISTRY_HPP_INCLUDED - - -// All of our user-facing macros support configuration toggle, that -// forces them to be defined prefixed with CATCH_. We also like to -// support another toggle that can minimize (disable) their implementation. -// Given this, we have 4 different configuration options below - -#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) - - #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_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 | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) - #define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __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_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - - #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) - #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) - #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) - #define CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, __VA_ARGS__ ) - #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) - #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) - #define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) - #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) - #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define CATCH_SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) - - - #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) - #define CATCH_STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) - #define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) - #define CATCH_STATIC_CHECK( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) - #define CATCH_STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) - #else - #define CATCH_STATIC_REQUIRE( ... ) CATCH_REQUIRE( __VA_ARGS__ ) - #define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ ) - #define CATCH_STATIC_CHECK( ... ) CATCH_CHECK( __VA_ARGS__ ) - #define CATCH_STATIC_CHECK_FALSE( ... ) CATCH_CHECK_FALSE( __VA_ARGS__ ) - #endif - - - // "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 ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) - #define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) - #define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) - #define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) - #define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) - #define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) - -#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, implemented | vv prefixed, disabled - - #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_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_NOTHROW( ... ) (void)(0) - - #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) - #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) - #define CATCH_METHOD_AS_TEST_CASE( method, ... ) - #define CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) - #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) - #define CATCH_SECTION( ... ) - #define CATCH_DYNAMIC_SECTION( ... ) - #define CATCH_FAIL( ... ) (void)(0) - #define CATCH_FAIL_CHECK( ... ) (void)(0) - #define CATCH_SUCCEED( ... ) (void)(0) - #define CATCH_SKIP( ... ) (void)(0) - - #define CATCH_STATIC_REQUIRE( ... ) (void)(0) - #define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0) - #define CATCH_STATIC_CHECK( ... ) (void)(0) - #define CATCH_STATIC_CHECK_FALSE( ... ) (void)(0) - - // "BDD-style" convenience wrappers - #define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) - #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) - #define CATCH_GIVEN( desc ) - #define CATCH_AND_GIVEN( desc ) - #define CATCH_WHEN( desc ) - #define CATCH_AND_WHEN( desc ) - #define CATCH_THEN( desc ) - #define CATCH_AND_THEN( desc ) - -#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, disabled | vv unprefixed, implemented - - #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_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 | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) - #define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __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_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - - #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 TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, __VA_ARGS__ ) - #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) - #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) - #define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) - #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) - #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - #define SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) - - - #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) - #define STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) - #define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) - #define STATIC_CHECK( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) - #define STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) - #else - #define STATIC_REQUIRE( ... ) REQUIRE( __VA_ARGS__ ) - #define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ ) - #define STATIC_CHECK( ... ) CHECK( __VA_ARGS__ ) - #define STATIC_CHECK_FALSE( ... ) CHECK_FALSE( __VA_ARGS__ ) - #endif - - // "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 ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) - #define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) - #define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) - #define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) - #define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) - #define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) - -#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ unprefixed, implemented | vv unprefixed, disabled - - #define REQUIRE( ... ) (void)(0) - #define REQUIRE_FALSE( ... ) (void)(0) - - #define REQUIRE_THROWS( ... ) (void)(0) - #define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) - #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_NOTHROW( ... ) (void)(0) - - #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__) - #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) - #define METHOD_AS_TEST_CASE( method, ... ) - #define TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__) - #define REGISTER_TEST_CASE( Function, ... ) (void)(0) - #define SECTION( ... ) - #define DYNAMIC_SECTION( ... ) - #define FAIL( ... ) (void)(0) - #define FAIL_CHECK( ... ) (void)(0) - #define SUCCEED( ... ) (void)(0) - #define SKIP( ... ) (void)(0) - - #define STATIC_REQUIRE( ... ) (void)(0) - #define STATIC_REQUIRE_FALSE( ... ) (void)(0) - #define STATIC_CHECK( ... ) (void)(0) - #define STATIC_CHECK_FALSE( ... ) (void)(0) - - // "BDD-style" convenience wrappers - #define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ) ) - #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) - - #define GIVEN( desc ) - #define AND_GIVEN( desc ) - #define WHEN( desc ) - #define AND_WHEN( desc ) - #define THEN( desc ) - #define AND_THEN( desc ) - -#endif // ^^ unprefixed, disabled - -// end of user facing macros - -#endif // CATCH_TEST_MACROS_HPP_INCLUDED - - -#ifndef CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED -#define CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED - - - -#ifndef CATCH_PREPROCESSOR_HPP_INCLUDED -#define CATCH_PREPROCESSOR_HPP_INCLUDED - - -#if defined(__GNUC__) -// We need to silence "empty __VA_ARGS__ warning", and using just _Pragma does not work -#pragma GCC system_header -#endif - - -#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ -#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) - -#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ -// MSVC needs more evaluations -#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) -#else -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) -#endif - -#define CATCH_REC_END(...) -#define CATCH_REC_OUT - -#define CATCH_EMPTY() -#define CATCH_DEFER(id) id CATCH_EMPTY() - -#define CATCH_REC_GET_END2() 0, CATCH_REC_END -#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 -#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 -#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT -#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) -#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) - -#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) - -#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) - -// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, -// and passes userdata as the first parameter to each invocation, -// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) -#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) -#else -// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) -#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) -#endif - -#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ -#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) -#else -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) -#endif - -#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ - CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) - -#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) -#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) -#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) -#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) -#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) -#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) -#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) -#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) -#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) -#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) -#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) - -#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N - -#define INTERNAL_CATCH_TYPE_GEN\ - template struct TypeList {};\ - template\ - constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ - template class...> struct TemplateTypeList{};\ - template class...Cs>\ - constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ - template\ - struct append;\ - template\ - struct rewrap;\ - template class, typename...>\ - struct create;\ - template class, typename>\ - struct convert;\ - \ - template \ - struct append { using type = T; };\ - template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ - struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ - template< template class L1, typename...E1, typename...Rest>\ - struct append, TypeList, Rest...> { using type = L1; };\ - \ - template< template class Container, template class List, typename...elems>\ - struct rewrap, List> { using type = TypeList>; };\ - template< template class Container, template class List, class...Elems, typename...Elements>\ - struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ - \ - template