name: Tests on: push: branches: [main, master, experimental] pull_request: branches: [main] # Or your default branch permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} # Keep main-branch history intact so slower matrix lanes still publish evidence. cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: warm: uses: ./.github/workflows/matrix-warm.yml with: build_type: Debug enable_onnx: false quality: name: Static Analysis runs-on: ubuntu-24.04 timeout-minutes: 20 permissions: contents: read steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 submodules: false - name: Initialize required submodules shell: bash run: | git submodule update --init --depth 1 third_party/sqlite-vec-cpp || true git submodule update --init --depth 1 third_party/simeon || true git submodule update --init --depth 1 third_party/symspell || true - name: Install static analysis tools shell: bash run: | sudo apt-get update sudo apt-get install -y --no-install-recommends cppcheck clang-tidy - name: Run cppcheck (CI profile, changed lines) shell: bash run: | set -euo pipefail BASE="" if [[ "${{ github.event_name }}" == "pull_request" ]]; then BASE="${{ github.event.pull_request.base.sha }}" elif [[ -n "${{ github.event.before }}" && "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]]; then BASE="${{ github.event.before }}" elif git rev-parse HEAD^ >/dev/null 2>&1; then BASE="HEAD^" fi # Push events can report a `before` SHA that is no longer reachable # after a force-push/rewrite. In that case, fall back to the checked # out parent instead of failing before cppcheck runs. if [[ -n "$BASE" ]] && ! git rev-parse --verify "${BASE}^{commit}" >/dev/null 2>&1; then echo "Diff base '$BASE' is not available in this checkout; falling back to HEAD^." if git rev-parse HEAD^ >/dev/null 2>&1; then BASE="HEAD^" else BASE="" fi fi if [[ -n "$BASE" ]]; then scripts/lint.sh cppcheck --diff "$BASE" else scripts/lint.sh cppcheck fi - name: Run clang-tidy on changed files (PR only) if: github.event_name == 'pull_request' shell: bash run: | set -euo pipefail COMPILE_DB_DIR="" if [ -f builddir/compile_commands.json ]; then COMPILE_DB_DIR="builddir" elif [ -f compile_commands.json ]; then COMPILE_DB_DIR="." else echo "No compile_commands.json found; skipping clang-tidy." | tee clang-tidy-pr.txt exit 0 fi mapfile -t CHANGED_FILES < <(git diff --name-only "origin/${{ github.base_ref }}...HEAD" \ | grep -E '\.(cpp|h|hpp|cc|cxx)$' \ | grep -v -E '(third_party|external|subprojects|\.pb\.(h|cc))' \ || true) if [ "${#CHANGED_FILES[@]}" -eq 0 ]; then echo "No C++ files changed in this PR." exit 0 fi printf 'Running clang-tidy on:\n' printf ' %s\n' "${CHANGED_FILES[@]}" clang-tidy \ -p "$COMPILE_DB_DIR" \ --warnings-as-errors='bugprone-*,clang-analyzer-*,concurrency-*' \ "${CHANGED_FILES[@]}" \ 2>&1 | tee clang-tidy-pr.txt - name: Upload clang-tidy results if: github.event_name == 'pull_request' && always() uses: actions/upload-artifact@v4 with: name: clang-tidy-pr-${{ github.run_number }} path: clang-tidy-pr.txt if-no-files-found: ignore retention-days: 7 tests: needs: warm if: ${{ success() }} name: Tests (${{ matrix.suffix }}) env: BUILD_DIR: builddir YAMS_DISABLE_FAISS: 1 strategy: fail-fast: false matrix: include: - suffix: linux-x86_64 runs_on: ubuntu-24.04 conan_host_profile: ./conan/profiles/host-linux-clang conan_arch: x86_64 needs_rosetta: false build_type: Debug coverage: true extra_meson_flags: "--buildtype=debug -Dbuild-tests=true -Denable-onnx=disabled -Dtest-timeout-scale=2 -Denable-dcheck=true" - suffix: linux-arm64 runs_on: ubuntu-24.04-arm conan_host_profile: ./conan/profiles/host-linux-clang conan_arch: armv8 needs_rosetta: false build_type: Debug coverage: false extra_meson_flags: "--buildtype=debug -Dbuild-tests=true -Denable-onnx=disabled -Dtest-timeout-scale=2 -Denable-bench-tests=false -Dbuild-benchmarks=false -Denable-dcheck=true" - suffix: macos-arm64 runs_on: macos-15 conan_host_profile: ./conan/profiles/host-macos-apple-clang conan_arch: armv8 needs_rosetta: false build_type: Debug coverage: false extra_meson_flags: "--buildtype=debug -Dbuild-tests=true -Denable-onnx=disabled -Dtest-timeout-scale=2 -Denable-dcheck=true" - suffix: windows-x86_64 runs_on: windows-latest conan_host_profile: ./conan/profiles/host-windows-msvc conan_arch: x86_64 needs_rosetta: false build_type: Debug coverage: false extra_meson_flags: "--buildtype=debug -Dbuild-tests=true -Denable-onnx=disabled -Dtest-timeout-scale=2 -Denable-dcheck=true" - suffix: linux-x86_64-asan runs_on: ubuntu-24.04 conan_host_profile: ./conan/profiles/host-linux-clang conan_arch: x86_64 needs_rosetta: false build_type: Debug coverage: false extra_meson_flags: "--buildtype=debug -Dbuild-tests=true -Denable-onnx=disabled -Denable-asan=true -Db_sanitize=address -Db_lundef=false -Dtest-timeout-scale=2 -Denable-bench-tests=false -Dbuild-benchmarks=false -Denable-dcheck=true" - suffix: linux-x86_64-ubsan runs_on: ubuntu-24.04 conan_host_profile: ./conan/profiles/host-linux-clang conan_arch: x86_64 needs_rosetta: false build_type: Debug coverage: false extra_meson_flags: "--buildtype=debug -Dbuild-tests=true -Denable-onnx=disabled -Denable-ubsan=true -Db_sanitize=undefined -Db_lundef=false -Dtest-timeout-scale=2 -Denable-dcheck=true" # TSAN intentionally excluded from CI: boost::asio strand destruction triggers # a heap-use-after-free that TSAN detects but cannot be suppressed via tsan.suppressions. # Run TSAN locally with: meson setup builddir-tsan -Denable-tsan=true -Db_sanitize=thread runs-on: ${{ matrix.runs_on }} # Windows debug builds can consume most of the previous 100-minute budget before tests start. timeout-minutes: 180 steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 submodules: false # Manual submodule init below (some use SSH URLs) - name: Skip non-linux-x86_64 matrix entries under act if: github.actor == 'nektos/act' && matrix.suffix != 'linux-x86_64' shell: bash run: | echo "Skipping matrix entry '${{ matrix.suffix }}' under act." echo "Act provides Linux container execution only; keep local runs deterministic on linux-x86_64." exit 0 - name: Free disk space (Linux) if: runner.os == 'Linux' uses: jlumbroso/free-disk-space@v1.3.1 with: tool-cache: false # Keep tools we might need android: true # ~14GB savings dotnet: true # ~2.7GB savings haskell: true # Haskell runtime large-packages: false # Keep - might need some of these docker-images: true # Cached docker images swap-storage: false # Keep swap for large links - name: Initialize required submodules shell: bash run: | retry_cmd() { local attempts="$1" shift local attempt=1 until "$@"; do if [ "$attempt" -ge "$attempts" ]; then return 1 fi echo "Command failed (attempt ${attempt}/${attempts}): $*" >&2 sleep $((attempt * 5)) attempt=$((attempt + 1)) done } # Force HTTPS for GitHub to handle SSH submodule URLs in CI git config --global url."https://github.com/".insteadOf "git@github.com:" # Only init submodules required for the build retry_cmd 4 git submodule update --init --depth 1 third_party/sqlite-vec-cpp retry_cmd 4 git submodule update --init --depth 1 third_party/simeon retry_cmd 4 git submodule update --init --depth 1 third_party/symspell - name: Install system dependencies (Linux) if: runner.os == 'Linux' shell: bash run: | set -euo pipefail retry_cmd() { local attempts="$1" shift local attempt=1 until "$@"; do if [ "$attempt" -ge "$attempts" ]; then return 1 fi echo "Command failed (attempt ${attempt}/${attempts}): $*" >&2 sleep $((attempt * 10)) attempt=$((attempt + 1)) done } if [ "$(dpkg --print-architecture)" = "arm64" ]; then source_files="$(sudo grep -RIl '://ports.ubuntu.com/ubuntu-ports' \ /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null || true)" if [ -n "$source_files" ]; then printf '%s\n' "$source_files" | xargs sudo sed -i \ 's|http://ports.ubuntu.com/ubuntu-ports|https://azure.ports.ubuntu.com/ubuntu-ports|g; s|https://ports.ubuntu.com/ubuntu-ports|https://azure.ports.ubuntu.com/ubuntu-ports|g' fi fi retry_cmd 4 sudo apt-get -o Acquire::Retries=5 -o Acquire::ForceIPv4=true update # Provide system Boost as a fallback per docs/BUILD-GCC.md when CMakeDeps resolution # is not detected by CMake (module mode will pick this up). # Install libbenchmark-dev for Google Benchmark support retry_cmd 4 sudo apt-get -o Acquire::Retries=5 -o Acquire::ForceIPv4=true \ install -y --fix-missing --no-install-recommends \ libboost-all-dev pkg-config libbenchmark-dev xz-utils libomp-18-dev PYTOOLS_DIR="${RUNNER_TEMP}/yams-python-tools" python3 -m venv "$PYTOOLS_DIR" "$PYTOOLS_DIR/bin/python" -m pip install --upgrade pip "$PYTOOLS_DIR/bin/python" -m pip install --upgrade 'meson==1.4.*' 'ninja==1.11.*' echo "$PYTOOLS_DIR/bin" >> "$GITHUB_PATH" export PATH="$PYTOOLS_DIR/bin:$PATH" echo "meson version: $(meson --version || echo 'missing')" echo "ninja version: $(ninja --version || echo 'missing')" echo "Installed system Boost headers/libs for module-mode fallback" - name: Install Zig (Linux) if: runner.os == 'Linux' shell: bash run: | set -euo pipefail ARCH=$(uname -m) case "$ARCH" in x86_64) ZIG_ARCH="x86_64" ;; aarch64) ZIG_ARCH="aarch64" ;; *) echo "Unsupported architecture: $ARCH"; exit 1 ;; esac ZIG_VERSION="0.15.2" curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}.tar.xz" -o /tmp/zig.tar.xz sudo tar -xJf /tmp/zig.tar.xz -C /opt sudo mv /opt/zig-${ZIG_ARCH}-linux-${ZIG_VERSION} /opt/zig sudo ln -sf /opt/zig/zig /usr/local/bin/zig rm /tmp/zig.tar.xz zig version - name: Install system dependencies (macOS) if: runner.os == 'macOS' shell: bash run: | set -euo pipefail for attempt in 1 2 3; do if brew update; then break fi if [ "$attempt" -eq 3 ]; then echo "brew update failed after ${attempt} attempts" >&2 exit 1 fi sleep $((attempt * 10)) done # Align with release workflow to ensure Meson builds have required toolchain bits. # Install google-benchmark for benchmark tests, zig for zyp PDF plugin brew install pkg-config cmake ccache boost google-benchmark zig || true PYTOOLS_DIR="${RUNNER_TEMP}/yams-python-tools" python3 -m venv "$PYTOOLS_DIR" "$PYTOOLS_DIR/bin/python" -m pip install --upgrade pip "$PYTOOLS_DIR/bin/python" -m pip install --upgrade 'meson==1.4.*' 'ninja==1.11.*' echo "$PYTOOLS_DIR/bin" >> "$GITHUB_PATH" export PATH="$PYTOOLS_DIR/bin:$PATH" echo "meson version: $(meson --version || echo 'missing')" echo "ninja version: $(ninja --version || echo 'missing')" echo "zig version: $(zig version || echo 'missing')" - name: Install system dependencies (Windows) if: runner.os == 'Windows' shell: bash run: | set -euo pipefail PYTOOLS_DIR="${RUNNER_TEMP}/yams-python-tools" python -m venv "$PYTOOLS_DIR" "$PYTOOLS_DIR/Scripts/python.exe" -m pip install --upgrade pip "$PYTOOLS_DIR/Scripts/python.exe" -m pip install --upgrade 'meson==1.4.*' 'ninja==1.11.*' echo "$PYTOOLS_DIR/Scripts" >> "$GITHUB_PATH" export PATH="$PYTOOLS_DIR/Scripts:$PATH" echo "meson version: $(meson --version || echo 'missing')" echo "ninja version: $(ninja --version || echo 'missing')" # Clean stale builddir to avoid Meson version incompatibility if [ -d "${{ env.BUILD_DIR }}" ]; then echo "Removing stale builddir to ensure clean Meson configuration" rm -rf "${{ env.BUILD_DIR }}" fi - name: Install Zig (Windows) if: runner.os == 'Windows' shell: pwsh run: | $zigVersion = "0.15.2" $zigUrl = "https://ziglang.org/download/${zigVersion}/zig-x86_64-windows-${zigVersion}.zip" $zigZip = "$env:TEMP\zig.zip" $zigDir = "C:\zig" Invoke-WebRequest -Uri $zigUrl -OutFile $zigZip Expand-Archive -Path $zigZip -DestinationPath $zigDir -Force Move-Item "$zigDir\zig-x86_64-windows-${zigVersion}\*" $zigDir -Force Remove-Item "$zigDir\zig-x86_64-windows-${zigVersion}" -Recurse -ErrorAction SilentlyContinue $env:PATH = "$zigDir;$env:PATH" "$zigDir" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append zig version - name: Cache Conan packages id: cache_conan uses: actions/cache@v4 with: path: | ~/.conan2/p ~/.conan2/r ~/.conan2/recipes ~/.conan2/metadata ~/.conan2/s key: conan2-${{ runner.os }}-${{ matrix.suffix }}-${{ hashFiles('conanfile.py', 'conan/profiles/**') }} restore-keys: | conan2-${{ runner.os }}-${{ matrix.suffix }}- conan2-${{ runner.os }}-linux-x86_64- conan2-${{ runner.os }}- - name: Conan cache diagnostics shell: bash run: | echo "Conan cache hit: ${{ steps.cache_conan.outputs.cache-hit }}" du -sh ~/.conan2/p 2>/dev/null || true ls ~/.conan2/r 2>/dev/null | head || true - name: Cache ccache if: matrix.suffix != 'linux-arm64' uses: actions/cache@v4 with: path: | ~/.cache/ccache ~/.ccache key: ccache-${{ runner.os }}-${{ matrix.suffix }}-${{ hashFiles('conanfile.py', 'meson.build', 'meson_options.txt') }} restore-keys: | ccache-${{ runner.os }}-${{ matrix.suffix }}- ccache-${{ runner.os }}-linux-x86_64- ccache-${{ runner.os }}- - name: Install Conan (latest) shell: bash run: | set -euo pipefail PYTOOLS_DIR="${RUNNER_TEMP}/yams-python-tools" if [ "$RUNNER_OS" = "Windows" ]; then VENV_PYTHON="$PYTOOLS_DIR/Scripts/python.exe" TOOL_BIN="$PYTOOLS_DIR/Scripts" if [ ! -f "$VENV_PYTHON" ]; then python -m venv "$PYTOOLS_DIR" fi else VENV_PYTHON="$PYTOOLS_DIR/bin/python" TOOL_BIN="$PYTOOLS_DIR/bin" if [ ! -x "$VENV_PYTHON" ]; then python3 -m venv "$PYTOOLS_DIR" fi fi "$VENV_PYTHON" -m pip install --upgrade pip "$VENV_PYTHON" -m pip install --upgrade 'conan==2.21.*' 'meson==1.4.*' 'ninja==1.11.*' echo "$TOOL_BIN" >> "$GITHUB_PATH" export PATH="$TOOL_BIN:$PATH" conan --version meson --version ninja --version mkdir -p ~/.conan2 cp .conan/global.conf ~/.conan2/global.conf 2>/dev/null || true # Continue directly with build/test steps (single job path) - name: Install optional system dependencies if: runner.os == 'Linux' shell: bash run: | set -euo pipefail extra_packages=(liburing-dev libarchive-dev libtag1-dev) if [ "${{ matrix.suffix }}" != "linux-arm64" ]; then extra_packages+=(ccache) fi sudo apt-get install -y --no-install-recommends "${extra_packages[@]}" || true echo "Optional packages installed (best-effort)." - name: Setup ccache env if: matrix.suffix != 'linux-arm64' shell: bash run: | set -euo pipefail echo "CCACHE_DIR=$HOME/.cache/ccache" >> $GITHUB_ENV echo "CCACHE_BASEDIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV echo "CCACHE_COMPRESS=1" >> $GITHUB_ENV echo "CCACHE_MAXSIZE=5G" >> $GITHUB_ENV echo "CCACHE_SLOPPINESS=time_macros,include_file_mtime,include_file_ctime,file_macro" >> $GITHUB_ENV ccache -p 2>/dev/null || true ccache -z 2>/dev/null || true - name: Reclaim disk for linux-arm64 builds if: matrix.suffix == 'linux-arm64' shell: bash run: | set -euo pipefail echo "YAMS_SKIP_CCACHE=1" >> "$GITHUB_ENV" rm -rf ~/.cache/ccache ~/.ccache || true python3 -m pip cache purge || true sudo apt-get clean || true df -h # Conan already installed above - name: Setup Conan Profile shell: bash run: | conan profile detect --force sed -i 's/compiler.cppstd=.*/compiler.cppstd=20/' ~/.conan2/profiles/default || true echo "Conan profile configured:" >&2 conan profile show >&2 - name: Detect clang version (log only) shell: bash run: | set -euo pipefail if command -v clang++ >/dev/null 2>&1; then RAW_VER=$(clang++ --version | sed -n 's/.*version \([0-9][0-9]*\).*/\1/p' | head -1 || true) elif command -v clang >/dev/null 2>&1; then RAW_VER=$(clang --version | sed -n 's/.*version \([0-9][0-9]*\).*/\1/p' | head -1 || true) else RAW_VER="" fi if [ -n "$RAW_VER" ]; then echo "Detected clang major: $RAW_VER" else echo "WARNING: clang not found; default profile may resolve gcc instead" >&2 fi - name: Clean corrupted Conan cache if needed shell: bash run: | if ! conan list "*" 2>/dev/null; then echo "Conan cache appears corrupted, cleaning locks and temp files..." conan cache clean --locks --temp 2>/dev/null || true echo "Conan cache locks/temp cleaned" fi - name: Tune Conan client (parallel downloads) shell: bash run: | set -euo pipefail CONF_FILE="$HOME/.conan2/global.conf" LINE="core.download:parallel=8" mkdir -p "$(dirname "$CONF_FILE")" if [ -f "$CONF_FILE" ]; then TMP_FILE="$(mktemp)" awk -v line="$LINE" ' BEGIN { replaced = 0 } /^core.download:parallel=/ { if (!replaced) { print line; replaced = 1 } ; next } { print } END { if (!replaced) print line } ' "$CONF_FILE" > "$TMP_FILE" mv "$TMP_FILE" "$CONF_FILE" else printf '%s\n' "$LINE" > "$CONF_FILE" fi echo "Configured $LINE in $CONF_FILE" grep -n '^core.download:parallel=' "$CONF_FILE" || true conan config show core.download:parallel 2>/dev/null || true conan --version - name: Configure and Build with Meson (Unix) id: build_unix if: runner.os != 'Windows' shell: bash run: | set -euo pipefail ARCH_PREFIX="" if [ "${{ matrix.needs_rosetta }}" = "true" ]; then softwareupdate --install-rosetta --agree-to-license || true ARCH_PREFIX="arch -x86_64" fi # Keep AF_UNIX socket path short on Linux to avoid sun_path length limits if [ "$RUNNER_OS" = "Linux" ]; then XDG_RUNTIME_DIR="/run/user/$(id -u)" mkdir -p "$XDG_RUNTIME_DIR" || true echo "XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" >> "$GITHUB_ENV" fi # Use setup.sh with CI environment variables for consistent build configuration export YAMS_CONAN_HOST_PROFILE="${{ matrix.conan_host_profile }}" export YAMS_CONAN_ARCH="${{ matrix.conan_arch }}" export YAMS_EXTRA_MESON_FLAGS="${{ matrix.extra_meson_flags }}" export YAMS_ENABLE_MOBILE_BINDINGS=false # Disable TSAN in CI - boost::asio strand destruction causes heap-use-after-free # that TSAN detects but cannot be suppressed. Run TSAN locally for development. export ENABLE_TSAN=false chmod +x setup.sh SETUP_ARGS=(Debug) if [ "${{ matrix.coverage }}" = "true" ]; then SETUP_ARGS+=(--coverage) fi $ARCH_PREFIX ./setup.sh "${SETUP_ARGS[@]}" # Source Conan build env so meson compile uses the same meson version # that setup.sh used for meson setup (Conan provides meson 1.10.x which # creates build.dat incompatible with older system meson). CONAN_BUILD_ENV="builddir/build-debug/conan/conanbuild.sh" if [ -f "${CONAN_BUILD_ENV}" ]; then set +u; source "${CONAN_BUILD_ENV}"; set -u; fi if [ "${{ matrix.suffix }}" = "linux-x86_64-asan" ] || \ [ "${{ matrix.suffix }}" = "linux-x86_64-ubsan" ]; then $ARCH_PREFIX meson compile -C builddir \ catch2_daemon_metrics \ catch2_post_ingest_queue_unit \ catch2_storage_submodule \ catch2_work_coordinator else $ARCH_PREFIX meson compile -C builddir fi - name: Configure and Build with Meson (Windows) id: build_windows if: runner.os == 'Windows' shell: pwsh env: YAMS_CONAN_HOST_PROFILE: ${{ matrix.conan_host_profile }} YAMS_CONAN_ARCH: ${{ matrix.conan_arch }} YAMS_EXTRA_MESON_FLAGS: ${{ matrix.extra_meson_flags }} YAMS_ENABLE_MOBILE_BINDINGS: "false" run: | .\setup.ps1 Debug # Diagnostic for Windows Boost lookup. Reads BOOST_* env vars set by # setup.ps1 (persisted to GITHUB_ENV) and lists the actual Conan boost # lib filenames so we can pattern-match meson's expected naming if the # build above fails. continue-on-error + always() so it never blocks. - name: (diag) Boost env + Conan lib layout (Windows) if: runner.os == 'Windows' && always() shell: pwsh continue-on-error: true run: | Write-Host "BOOST_ROOT=$env:BOOST_ROOT" Write-Host "BOOST_INCLUDEDIR=$env:BOOST_INCLUDEDIR" Write-Host "BOOST_LIBRARYDIR=$env:BOOST_LIBRARYDIR" if ($env:BOOST_LIBRARYDIR -and (Test-Path $env:BOOST_LIBRARYDIR)) { Write-Host "--- $env:BOOST_LIBRARYDIR contents (boost_*) ---" Get-ChildItem $env:BOOST_LIBRARYDIR -Filter 'boost_*' | Select Name | Format-Table -AutoSize | Out-String Write-Host "--- $env:BOOST_LIBRARYDIR contents (libboost_*) ---" Get-ChildItem $env:BOOST_LIBRARYDIR -Filter 'libboost_*' -ErrorAction SilentlyContinue | Select Name | Format-Table -AutoSize | Out-String } - name: ccache stats (post-build) if: always() && runner.os != 'Windows' shell: bash run: | set +e ccache -s 2>/dev/null || true du -sh "${CCACHE_DIR:-$HOME/.cache/ccache}" 2>/dev/null || true # - name: Check mobile ABI surface # shell: bash # run: | # set -euo pipefail # LIB_PATH=$(find "${{ env.BUILD_DIR }}" -type f \( -name 'libyams_mobile*.so' -o -name 'libyams_mobile*.dylib' -o -name 'yams_mobile.dll' \) | head -n1) # if [ -z "$LIB_PATH" ]; then # echo "libyams_mobile shared library not found under ${{ env.BUILD_DIR }}" >&2 # exit 1 # fi # scripts/ci/check_mobile_abi.sh "$LIB_PATH" - name: Run Tests with Meson (Unix) id: meson_tests_unix if: ${{ !matrix.needs_rosetta && runner.os != 'Windows' }} shell: bash continue-on-error: true env: YAMS_TEST_SAFE_SINGLE_INSTANCE: "1" YAMS_SQLITE_MINIMAL_PRAGMAS: "1" YAMS_SQLITE_VEC_INIT_TIMEOUT_MS: "1500" YAMS_SQLITE_BUSY_TIMEOUT_MS: "1000" YAMS_VDB_IN_MEMORY: "1" YAMS_SQLITE_VEC_SKIP_INIT: "1" YAMS_DISABLE_VECTORS: "1" YAMS_TESTING: "1" run: | set -euo pipefail if [ "$RUNNER_OS" = "Linux" ]; then mkdir -p "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" || true echo "Using XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" fi CONAN_BUILD_ENV="" for candidate in \ "${{ env.BUILD_DIR }}/build-debug/conan/conanbuild.sh" \ "${{ env.BUILD_DIR }}/conan/conanbuild.sh" do if [ -f "$candidate" ]; then CONAN_BUILD_ENV="$candidate" break fi done if [ -n "$CONAN_BUILD_ENV" ]; then set +u source "$CONAN_BUILD_ENV" set -u fi # Run unit and integration tests (TSAN disabled in CI). ASAN is a sanitizer smoke lane; # build/test selected targets only so hosted runners do not get recycled. if [ "${{ matrix.suffix }}" = "linux-x86_64-asan" ] || \ [ "${{ matrix.suffix }}" = "linux-x86_64-ubsan" ]; then meson test -C ${{ env.BUILD_DIR }} --no-rebuild --print-errorlogs --timeout-multiplier 2 \ daemon_metrics_status \ post_ingest_queue_unit \ storage_submodule \ work_coordinator else meson test -C ${{ env.BUILD_DIR }} --suite unit --suite integration --print-errorlogs --timeout-multiplier 2 fi - name: Run Tests with Meson (Windows) id: meson_tests_windows if: runner.os == 'Windows' shell: pwsh continue-on-error: true env: YAMS_TEST_SAFE_SINGLE_INSTANCE: "1" YAMS_SQLITE_MINIMAL_PRAGMAS: "1" YAMS_SQLITE_VEC_INIT_TIMEOUT_MS: "1500" YAMS_SQLITE_BUSY_TIMEOUT_MS: "1000" YAMS_VDB_IN_MEMORY: "1" YAMS_SQLITE_VEC_SKIP_INIT: "1" YAMS_DISABLE_VECTORS: "1" YAMS_TESTING: "1" run: | $ErrorActionPreference = "Stop" $conanBuildPs1 = $null $candidates = @( (Join-Path "${{ env.BUILD_DIR }}" "build-debug/conan/conanbuild.ps1"), (Join-Path "${{ env.BUILD_DIR }}" "conan/conanbuild.ps1") ) foreach ($candidate in $candidates) { if (Test-Path $candidate) { $conanBuildPs1 = $candidate break } } if ($conanBuildPs1) { . $conanBuildPs1 } # Run only unit and integration tests (exclude bench, stress, daemon-long, etc.) meson test -C ${{ env.BUILD_DIR }} --suite unit --suite integration --print-errorlogs --timeout-multiplier 2 - name: Run Tests with Meson (Rosetta x86_64) id: meson_tests_rosetta if: ${{ matrix.needs_rosetta }} shell: bash continue-on-error: true env: YAMS_TEST_SAFE_SINGLE_INSTANCE: "1" YAMS_SQLITE_MINIMAL_PRAGMAS: "1" YAMS_SQLITE_VEC_INIT_TIMEOUT_MS: "1500" YAMS_SQLITE_BUSY_TIMEOUT_MS: "1000" YAMS_VDB_IN_MEMORY: "1" YAMS_SQLITE_VEC_SKIP_INIT: "1" YAMS_DISABLE_VECTORS: "1" YAMS_TESTING: "1" run: | set -euo pipefail # Ensure Rosetta is present (idempotent) softwareupdate --install-rosetta --agree-to-license || true CONAN_BUILD_ENV="" for candidate in \ "${{ env.BUILD_DIR }}/build-debug/conan/conanbuild.sh" \ "${{ env.BUILD_DIR }}/conan/conanbuild.sh" do if [ -f "$candidate" ]; then CONAN_BUILD_ENV="$candidate" break fi done if [ -n "$CONAN_BUILD_ENV" ]; then set +u source "$CONAN_BUILD_ENV" set -u fi # Run only unit and integration tests (exclude bench, stress, daemon-long, etc.) arch -x86_64 meson test -C ${{ env.BUILD_DIR }} --suite unit --suite integration --print-errorlogs --timeout-multiplier 2 - name: Upload build and test logs (artifacts) if: ${{ always() && (steps.build_unix.outcome == 'failure' || steps.build_windows.outcome == 'failure' || steps.meson_tests_unix.outcome == 'failure' || steps.meson_tests_windows.outcome == 'failure' || steps.meson_tests_rosetta.outcome == 'failure') }} uses: actions/upload-artifact@v4 with: name: logs-${{ matrix.suffix }}-${{ github.run_number }} path: | ${{ env.BUILD_DIR }}/meson-logs/ ${{ env.BUILD_DIR }}/meson-info/ ${{ env.BUILD_DIR }}/meson-logs/testlog.txt ${{ env.BUILD_DIR }}/**/conan-install/**/*.log ${{ env.BUILD_DIR }}/**/conan*.log if-no-files-found: ignore retention-days: 14 - name: Job Summary (artifacts link) if: always() shell: bash run: | echo "### Artifacts" >> "$GITHUB_STEP_SUMMARY" echo "[View artifacts for this run]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID)" >> "$GITHUB_STEP_SUMMARY" - name: Enforce test failure if: ${{ always() && (steps.meson_tests_unix.outcome == 'failure' || steps.meson_tests_windows.outcome == 'failure' || steps.meson_tests_rosetta.outcome == 'failure') }} shell: bash run: | echo "Meson tests reported failures; enforcing job failure." >&2 exit 1 - name: Generate Coverage Report if: matrix.coverage == true shell: bash run: | set -euo pipefail pip3 install gcovr GCOV_EXECUTABLE="gcov" if command -v clang++ >/dev/null 2>&1; then CLANG_MAJOR=$(clang++ --version | sed -n 's/.*version \([0-9][0-9]*\).*/\1/p' | head -1 || true) if [ -n "$CLANG_MAJOR" ] && command -v "llvm-cov-${CLANG_MAJOR}" >/dev/null 2>&1; then GCOV_EXECUTABLE="llvm-cov-${CLANG_MAJOR} gcov -p" elif command -v llvm-cov >/dev/null 2>&1; then GCOV_EXECUTABLE="llvm-cov gcov -p" fi fi echo "Using gcov executable: $GCOV_EXECUTABLE" COMMON_GCOVR_ARGS=( --root "$GITHUB_WORKSPACE" --gcov-executable "$GCOV_EXECUTABLE" --object-directory "$BUILD_DIR" --exclude-directories "$BUILD_DIR/plugins" --merge-mode-functions merge-use-line-min --filter "$GITHUB_WORKSPACE/src" --filter "$GITHUB_WORKSPACE/include" --exclude "$GITHUB_WORKSPACE/src/benchmarks" "$BUILD_DIR" ) gcovr "${COMMON_GCOVR_ARGS[@]}" \ --html --html-details \ --output "$BUILD_DIR/coverage.html" gcovr "${COMMON_GCOVR_ARGS[@]}" \ --xml --output "$BUILD_DIR/coverage.xml" gcovr "${COMMON_GCOVR_ARGS[@]}" \ --print-summary --output "$BUILD_DIR/coverage.txt" python3 - <<'PY' import os import pathlib import xml.etree.ElementTree as ET xml_path = pathlib.Path(os.environ["BUILD_DIR"]) / "coverage.xml" root = ET.parse(xml_path).getroot() lines_valid = int(root.attrib.get("lines-valid", "0")) if lines_valid == 0: raise SystemExit(f"{xml_path} has no instrumented lines") print(f"coverage.xml lines-valid={lines_valid}") PY - name: Upload Coverage Report if: matrix.coverage == true uses: actions/upload-artifact@v4 with: name: coverage-report path: | ${{ env.BUILD_DIR }}/coverage*.html ${{ env.BUILD_DIR }}/coverage.txt if-no-files-found: error retention-days: 5 - name: Upload Coverage XML if: matrix.coverage == true uses: actions/upload-artifact@v4 with: name: coverage-xml path: ${{ env.BUILD_DIR }}/coverage.xml if-no-files-found: error retention-days: 5 - name: Upload Benchmark Results if: github.actor != 'nektos/act' && matrix.conan_arch == 'x86_64' uses: actions/upload-artifact@v4 with: name: benchmark-results-linux-hosted-yams path: | ${{ env.BUILD_DIR }}/bench_results/**/*.json ${{ env.BUILD_DIR }}/bench_results/*.json if-no-files-found: ignore retention-days: 5