9099bc383f
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
591 lines
27 KiB
YAML
591 lines
27 KiB
YAML
# Reusable: unit + integration tests on all platforms
|
|
name: Test
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
skip_perf:
|
|
description: 'Skip incremental perf tests (phases 2-7)'
|
|
type: boolean
|
|
default: true
|
|
broad_platforms:
|
|
description: 'Test the broad platform matrix (older glibc + extra OS versions) instead of the core set'
|
|
type: boolean
|
|
default: false
|
|
shard_suites:
|
|
description: 'Split each C-suite leg across parallel shard jobs (ubuntu x3, windows x2); off = one job per leg, byte-identical to the pre-shard topology'
|
|
type: boolean
|
|
default: false
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
# Emit the platform matrices as JSON. The CORE set is the default (fast,
|
|
# unchanged); the BROAD set adds extra free runners (older glibc /
|
|
# additional OS versions) for a wider "does it build everywhere" picture.
|
|
setup-matrix:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
outputs:
|
|
unix: ${{ steps.set.outputs.unix }}
|
|
windows: ${{ steps.set.outputs.windows }}
|
|
steps:
|
|
- name: Compute matrices
|
|
id: set
|
|
env:
|
|
BROAD: ${{ inputs.broad_platforms }}
|
|
SHARDS: ${{ inputs.shard_suites }}
|
|
run: |
|
|
CORE_UNIX='[
|
|
{"os":"ubuntu-latest","cc":"gcc","cxx":"g++"},
|
|
{"os":"ubuntu-24.04-arm","cc":"gcc","cxx":"g++"},
|
|
{"os":"macos-14","cc":"cc","cxx":"c++"},
|
|
{"os":"macos-15-intel","cc":"cc","cxx":"c++"}
|
|
]'
|
|
# Broad matrix legs are REQUIRED gates (no optional/continue-on-error
|
|
# escape hatches): the release dry run must be all-green, every platform.
|
|
BROAD_UNIX='[
|
|
{"os":"ubuntu-22.04","cc":"gcc","cxx":"g++"},
|
|
{"os":"ubuntu-22.04-arm","cc":"gcc","cxx":"g++"},
|
|
{"os":"macos-15","cc":"cc","cxx":"c++"}
|
|
]'
|
|
# Each Windows leg pins the msys2 environment + package arch to the
|
|
# RUNNER architecture so the build is native, never emulated:
|
|
# x86-64 runners -> CLANG64 (mingw-w64-clang-x86_64-*)
|
|
# ARM64 runner -> CLANGARM64 (mingw-w64-clang-aarch64-*)
|
|
# windows-11-arm previously used the x86-64 CLANG64 toolchain, so its
|
|
# binary ran under Windows-on-ARM x86-64 emulation and ASan's function
|
|
# interception crashed (interception_win: unhandled instruction). With
|
|
# the native ARM64 toolchain ASan instruments native ARM64 code, so it
|
|
# is a real (non-optional) gate, not a tolerated emulated-flake.
|
|
CORE_WIN='[{"os":"windows-latest","msystem":"CLANG64","pkg":"x86_64"}]'
|
|
BROAD_WIN='[{"os":"windows-2025","msystem":"CLANG64","pkg":"x86_64"},{"os":"windows-11-arm","msystem":"CLANGARM64","pkg":"aarch64"}]'
|
|
if [ "$BROAD" = "true" ]; then
|
|
UNIX=$(jq -cn --argjson a "$CORE_UNIX" --argjson b "$BROAD_UNIX" '$a + $b')
|
|
WIN=$(jq -cn --argjson a "$CORE_WIN" --argjson b "$BROAD_WIN" '$a + $b')
|
|
else
|
|
UNIX=$(jq -cn --argjson a "$CORE_UNIX" '$a')
|
|
WIN=$(jq -cn --argjson a "$CORE_WIN" '$a')
|
|
fi
|
|
# Suite sharding (opt-in via the shard_suites input): each platform's
|
|
# C-suite run is split across N runner jobs. CBM_TEST_SHARD="i/N"
|
|
# slices deterministically inside run-tests-parallel.sh; each
|
|
# shard's union guard proves it ran exactly its slice; shards are
|
|
# minted HERE from one expansion so a missing shard cannot be
|
|
# configured silently; and the shard-completeness job re-proves the
|
|
# cross-shard union at runtime. Ubuntu runners are plentiful (3
|
|
# shards); Windows gets 2 (every extra shard re-pays ~5 min of
|
|
# MSYS2 setup); macOS stays at 1 — mac legs are not the critical
|
|
# path and mac runner concurrency ceilings are the tightest.
|
|
if [ "$SHARDS" = "true" ]; then
|
|
UNIX=$(jq -c '[.[] | . as $e
|
|
| (if ($e.os | startswith("ubuntu")) then 3 else 1 end) as $n
|
|
| range(1; $n + 1) as $i
|
|
| $e + {shard: "\($i)/\($n)"}]' <<< "$UNIX")
|
|
WIN=$(jq -c '[.[] | . as $e
|
|
| range(1; 3) as $i
|
|
| $e + {shard: "\($i)/2"}]' <<< "$WIN")
|
|
fi
|
|
echo "unix={\"include\":$UNIX}" >> "$GITHUB_OUTPUT"
|
|
echo "windows={\"include\":$WIN}" >> "$GITHUB_OUTPUT"
|
|
|
|
test-unix:
|
|
needs: setup-matrix
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJSON(needs.setup-matrix.outputs.unix) }}
|
|
runs-on: ${{ matrix.os }}
|
|
# Broad-only legs (extra OS versions) are informational: visible but
|
|
# non-blocking, so a flaky/less-common runner can't block a release.
|
|
continue-on-error: ${{ matrix.optional == true }}
|
|
timeout-minutes: 240
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Install deps (Ubuntu)
|
|
if: startsWith(matrix.os, 'ubuntu')
|
|
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev ccache
|
|
|
|
- name: Install ccache (macOS)
|
|
if: startsWith(matrix.os, 'macos')
|
|
run: command -v ccache >/dev/null 2>&1 || brew install ccache
|
|
|
|
# Verified compiler cache: CCACHE_COMPILERCHECK=content keys every entry
|
|
# on the compiler-binary CONTENT plus the fully preprocessed input, so a
|
|
# hit is provably the identical compilation — a stale or foreign cache
|
|
# can only miss, never return wrong output (see scripts/env.sh).
|
|
# Keys embed github.ref; the final ref-less restore key falls back to
|
|
# the newest cache GitHub's scoping permits (a PR can only ever see its
|
|
# own ref and the base branch, and base-branch caches are written only
|
|
# by base-branch runs), so a fresh PR starts from main's warm cache
|
|
# instead of building cold. Content verification is what makes that
|
|
# safe; the fallback buys ~8-12 minutes on every first-of-ref build.
|
|
- name: Compiler cache (content-verified, restore)
|
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ github.workspace }}/.ccache
|
|
key: ccache-test-${{ matrix.os }}-${{ matrix.cc }}-${{ github.ref }}-${{ github.sha }}
|
|
# The cross-ref fallback is what keeps fresh PRs warm: without it a
|
|
# new PR always started from a cold cache (observed 0% hit legs).
|
|
# ccache verifies content hashes itself, so a stale cache can only
|
|
# miss, never mis-compile.
|
|
restore-keys: |
|
|
ccache-test-${{ matrix.os }}-${{ matrix.cc }}-${{ github.ref }}-
|
|
ccache-test-${{ matrix.os }}-${{ matrix.cc }}-
|
|
|
|
- name: Test
|
|
run: scripts/test.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
|
|
env:
|
|
CBM_TEST_SHARD: ${{ matrix.shard }}
|
|
CBM_TEST_LEG: ${{ matrix.os }}-${{ matrix.cc }}
|
|
CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }}
|
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
|
CCACHE_MAXSIZE: 1500M
|
|
|
|
# Every shard of a leg builds the identical runner, so one shard's
|
|
# cache carries the complete object set: shard 1 (or the unsharded
|
|
# job) saves, the rest only restore — same save volume as before
|
|
# sharding, no quota thrash, no same-key save races. Saving on failure
|
|
# too keeps red iterations warm.
|
|
- name: Shard manifest (completeness proof)
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: shard-manifest-${{ matrix.os }}-${{ matrix.cc }}-${{ strategy.job-index }}
|
|
path: build/c/test-logs/shard-manifest.txt
|
|
# The harness writes the manifest BEFORE any suite runs, so a
|
|
# missing file here means the job died before the harness started
|
|
# (contract step / build) — that failure is already the job's red;
|
|
# warn instead of stacking a second error on top of it. The
|
|
# shard-completeness job still gates the cross-shard union.
|
|
if-no-files-found: warn
|
|
|
|
- name: Compiler cache (save)
|
|
if: always() && (matrix.shard == null || startsWith(matrix.shard, '1/'))
|
|
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ github.workspace }}/.ccache
|
|
key: ccache-test-${{ matrix.os }}-${{ matrix.cc }}-${{ github.ref }}-${{ github.sha }}
|
|
|
|
- name: ccache stats
|
|
if: always()
|
|
run: ccache -s || true
|
|
env:
|
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
|
|
|
# The package wrappers own their own runtime-set publication and lease-lock
|
|
# implementations. Run the same suites against both host families so Unix
|
|
# inode/flock behavior and Windows handle/link behavior are each gating,
|
|
# without adding Node/Go provisioning to every sanitizer/container leg.
|
|
test-package-wrappers:
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, windows-latest]
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version-file: pkg/go/go.mod
|
|
cache: false
|
|
|
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: "22"
|
|
|
|
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Test Go, npm, and PyPI runtime-set wrappers
|
|
shell: bash
|
|
run: scripts/ci/test-package-wrappers.sh
|
|
|
|
# ThreadSanitizer data-race gate. The threaded production surfaces (watcher,
|
|
# subprocess, httpd, pipeline, mcp, worker pool, diagnostics, the runnable
|
|
# daemon-coordination paths) plus allocator concurrency all run under TSan
|
|
# here — see TEST_TSAN_SUITES in Makefile.cbm. Runs on both Linux
|
|
# architectures AND native ARM64 macOS: the threading code is shared, so a
|
|
# race is usually caught on all three, but scheduler differences mean each
|
|
# platform can surface a race the others miss. Windows has no TSan runtime on
|
|
# any toolchain (documented irreducible gap); this shared-code coverage is
|
|
# its substitute.
|
|
# Memory-diagnostics lane (user decision 2026-08-03: runner cost accepted).
|
|
# Newest pinned LLVM sanitizer runtime + straighter stacks over the full
|
|
# parallel suite — catches what an older compiler-rt can miss. Same
|
|
# canonical scripts/test.sh wave as every other leg.
|
|
test-diag:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 120
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Install deps
|
|
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev ccache
|
|
|
|
- name: Install LLVM 22 (pinned diagnostic toolchain)
|
|
run: |
|
|
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
|
|
echo "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-22 main" | sudo tee /etc/apt/sources.list.d/llvm-22.list
|
|
sudo apt-get update
|
|
sudo apt-get install -y clang-22
|
|
|
|
- name: Compiler cache (content-verified, restore)
|
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ github.workspace }}/.ccache
|
|
key: ccache-diag-${{ github.ref }}-${{ github.sha }}
|
|
restore-keys: |
|
|
ccache-diag-${{ github.ref }}-
|
|
ccache-diag-
|
|
|
|
# Also enables the ASan checks that are off by default in every other
|
|
# lane: stack-use-after-return, stack-use-after-scope, and strict string
|
|
# checks. Mirrors `make -f Makefile.cbm diag`, including its reasoning
|
|
# for leaving detect_invalid_pointer_pairs out.
|
|
- name: Test (clang-22, ASan+UBSan + off-by-default ASan checks)
|
|
env:
|
|
ASAN_OPTIONS: "detect_stack_use_after_return=1:strict_string_checks=1:detect_stack_use_after_scope=1"
|
|
run: |
|
|
scripts/test.sh CC=clang-22 CXX=clang++-22 \
|
|
SANITIZE="-fsanitize=address,undefined -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
|
|
|
- name: Compiler cache (save)
|
|
if: always()
|
|
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ github.workspace }}/.ccache
|
|
key: ccache-diag-${{ github.ref }}-${{ github.sha }}
|
|
|
|
# MemorySanitizer lane (stage 2). Uninitialized READS are the one
|
|
# memory-error class no other lane covers dynamically. The image carries
|
|
# MSan-instrumented libc++/libc++abi/libunwind + zlib (vendored C deps
|
|
# instrument in-tree); scripts/msan.sh drives the build and the suite.
|
|
#
|
|
# THIS JOB IS THE AUTHORITATIVE VENUE for the lane. It previously ran WITHOUT
|
|
# the script's default exclusions to settle whether those were an aarch64
|
|
# artifact — the local ladder cannot answer that, having no faithful x86-64
|
|
# emulation. It has now answered it, and the answer SPLIT the list:
|
|
#
|
|
# - the five deep-recursion suites overflow their thread stacks on x86-64
|
|
# too, so that limit is NOT architectural;
|
|
# - `cli` does not overflow here at all — it fails in the install path for
|
|
# an unrelated, still-undiagnosed reason;
|
|
# - `incremental` was a shadow-memory RSS artifact, now fixed in the test
|
|
# itself rather than skipped, so it stays in the lane.
|
|
#
|
|
# Per-cause detail lives in scripts/msan.sh, which is the single authoritative
|
|
# list both venues now share. Zero use-of-uninitialized-value findings were
|
|
# reported across the lane on either architecture.
|
|
test-msan:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 180
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Buildx cache
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: /tmp/.buildx-msan
|
|
key: buildx-msan-${{ hashFiles('test-infrastructure/Dockerfile.msan') }}
|
|
|
|
- name: Build MSan image (cached layers)
|
|
run: scripts/ci/msan-lane.sh build
|
|
|
|
- name: MSan suite (exclusions per scripts/msan.sh)
|
|
run: scripts/ci/msan-lane.sh run
|
|
|
|
# macOS leak coverage. LeakSanitizer runs by default under ASan on Linux, so
|
|
# test-unix's ubuntu legs have always had it; on macOS it is off by default
|
|
# and Apple's clang refuses to turn it on, which left an entire platform with
|
|
# zero leak detection. Upstream (Homebrew) LLVM supports LSan on darwin/arm64,
|
|
# so this leg is the ordinary ASan suite built with that toolchain and run
|
|
# with detect_leaks=1. Mirrors the local `make -f Makefile.cbm test-lsan`.
|
|
test-lsan-macos:
|
|
runs-on: macos-latest
|
|
timeout-minutes: 120
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Install LSan-capable toolchain
|
|
run: brew install llvm ccache
|
|
|
|
- name: Compiler cache (content-verified, restore)
|
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ github.workspace }}/.ccache
|
|
key: ccache-lsan-${{ github.ref }}-${{ github.sha }}
|
|
restore-keys: |
|
|
ccache-lsan-${{ github.ref }}-
|
|
ccache-lsan-
|
|
|
|
- name: Test (Homebrew clang, ASan + LeakSanitizer)
|
|
env:
|
|
ASAN_OPTIONS: "detect_leaks=1:halt_on_error=1"
|
|
run: |
|
|
LLVM_PREFIX="$(brew --prefix llvm)"
|
|
scripts/test.sh CC="$LLVM_PREFIX/bin/clang" CXX="$LLVM_PREFIX/bin/clang++"
|
|
|
|
- name: Compiler cache (save)
|
|
if: always()
|
|
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ github.workspace }}/.ccache
|
|
key: ccache-lsan-${{ github.ref }}-${{ github.sha }}
|
|
|
|
test-tsan:
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- { os: ubuntu-latest, cc: clang, cxx: clang++ }
|
|
- { os: ubuntu-24.04-arm, cc: clang, cxx: clang++ }
|
|
- { os: macos-14, cc: cc, cxx: c++ }
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 120
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Install deps (Ubuntu)
|
|
if: runner.os == 'Linux'
|
|
run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev ccache
|
|
|
|
- name: Install deps (macOS)
|
|
if: runner.os == 'macOS'
|
|
run: brew install ccache
|
|
|
|
- name: Compiler cache (content-verified)
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ github.workspace }}/.ccache
|
|
key: ccache-tsan-${{ matrix.os }}-${{ github.ref }}-${{ github.sha }}
|
|
restore-keys: |
|
|
ccache-tsan-${{ matrix.os }}-${{ github.ref }}-
|
|
ccache-tsan-${{ matrix.os }}-
|
|
|
|
- name: ThreadSanitizer tests
|
|
# Calls make directly (no env.sh), so route the compilers through
|
|
# ccache's masquerade dir: /usr/lib/ccache on Debian, libexec under the
|
|
# Homebrew prefix on macOS. On Linux, reduce mmap ASLR entropy first:
|
|
# modern (esp. aarch64) kernels randomize high enough that TSan's
|
|
# shadow mapping aborts with "unexpected memory mapping" before any
|
|
# test runs. macOS TSan needs no such adjustment.
|
|
run: |
|
|
if [ "$RUNNER_OS" = "macOS" ]; then
|
|
export PATH="$(brew --prefix ccache)/libexec:$PATH"
|
|
else
|
|
export PATH=/usr/lib/ccache:$PATH
|
|
sudo sysctl -w vm.mmap_rnd_bits=28 || true
|
|
fi
|
|
# The TSan leg lives in the canonical test entry (same file the
|
|
# compose test-tsan service runs); env.sh supplies the verified
|
|
# ccache masquerade.
|
|
scripts/test.sh --tsan CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
|
|
env:
|
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
|
CCACHE_MAXSIZE: 1500M
|
|
|
|
- name: ccache stats
|
|
if: always()
|
|
run: ccache -s || true
|
|
env:
|
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
|
|
|
test-windows:
|
|
needs: setup-matrix
|
|
strategy:
|
|
fail-fast: false
|
|
matrix: ${{ fromJSON(needs.setup-matrix.outputs.windows) }}
|
|
runs-on: ${{ matrix.os }}
|
|
continue-on-error: ${{ matrix.optional == true }}
|
|
timeout-minutes: 240
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
# Defender posture: real-time protection is POLICY-LOCKED OFF on hosted
|
|
# Windows images (verified 2026-07-27: WinDefend starts and
|
|
# Set-MpPreference accepts, but RTP stays off — untamperable from the
|
|
# job). The local VM leg enforces Defender-ON via the canonical Defender
|
|
# preflight script, so AV-interaction coverage lives there; runners run
|
|
# without RTP by platform constraint.
|
|
|
|
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
|
with:
|
|
msystem: ${{ matrix.msystem }}
|
|
path-type: inherit
|
|
install: >-
|
|
mingw-w64-clang-${{ matrix.pkg }}-clang
|
|
mingw-w64-clang-${{ matrix.pkg }}-compiler-rt
|
|
mingw-w64-clang-${{ matrix.pkg }}-zlib
|
|
mingw-w64-clang-${{ matrix.pkg }}-python3
|
|
mingw-w64-clang-${{ matrix.pkg }}-ccache
|
|
make
|
|
git
|
|
zip
|
|
|
|
- name: Create protected per-user temp root
|
|
# One implementation, shared with _soak.yml and the VM leg
|
|
# (test-infrastructure/vm/vm-run-tests.sh) — see the script's header for
|
|
# why the harness cannot run under a shared or ACL-inherited temp.
|
|
shell: pwsh
|
|
run: |
|
|
$root = & scripts/ci/new-protected-temp-root.ps1 `
|
|
-Prefix 'cbm-ci-tmp-' `
|
|
-ProtectDir (Join-Path $env:GITHUB_WORKSPACE "build\c")
|
|
"CBM_CI_TEMP_ROOT=$root" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
|
|
|
- name: Compiler cache (content-verified)
|
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ github.workspace }}/.ccache
|
|
key: ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-${{ github.ref }}-${{ github.sha }}
|
|
restore-keys: |
|
|
ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-${{ github.ref }}-
|
|
ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-
|
|
|
|
- name: Test
|
|
shell: msys2 {0}
|
|
# AddressSanitizer is unavailable on native ARM64 Windows (LLVM ships no
|
|
# libclang_rt.asan for aarch64-w64-windows-gnu) and cannot intercept the
|
|
# system DLLs under x86-64 emulation either. Instead of running the
|
|
# ARM64 leg unsanitized, it runs UBSan in TRAP mode: -fsanitize-trap
|
|
# needs no runtime library (the very thing aarch64-w64-windows-gnu
|
|
# lacks), so it instruments natively and turns any undefined behavior
|
|
# into an illegal-instruction trap; -fstack-protector-strong adds
|
|
# stack-smash coverage the heap tools miss. To see WHICH check fired,
|
|
# reproduce under the emulated x86_64 UBSan (win.sh ubsan-build), which
|
|
# carries the full message. x86-64 Windows keeps full ASan+UBSan; the
|
|
# other native-ARM legs (Linux/macOS) carry ASan+LSan+TSan.
|
|
run: |
|
|
# Native tests resolve temp via TEMP/TMP (cbm_tmpdir); shell tools use
|
|
# TMPDIR. Route both through the protected root created above so no
|
|
# fixture lands under a shared or ACL-inherited ancestry.
|
|
export TEMP="$(cygpath -m "$CBM_CI_TEMP_ROOT")"
|
|
export TMP="$TEMP"
|
|
export TMPDIR="$(cygpath -u "$CBM_CI_TEMP_ROOT")"
|
|
# No per-leg flag logic here: test.sh itself applies the CLANGARM64
|
|
# trap-UBSan default, so local and CI build identical test binaries.
|
|
scripts/test.sh CC=clang CXX=clang++
|
|
env:
|
|
CBM_TEST_SHARD: ${{ matrix.shard }}
|
|
CBM_TEST_LEG: ${{ matrix.os }}-${{ matrix.msystem }}
|
|
CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }}
|
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
|
CCACHE_MAXSIZE: 1500M
|
|
|
|
- name: ccache stats
|
|
if: always()
|
|
shell: msys2 {0}
|
|
run: ccache -s || true
|
|
env:
|
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
|
|
|
- name: Shard manifest (completeness proof)
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: shard-manifest-${{ matrix.os }}-${{ matrix.msystem }}-${{ strategy.job-index }}
|
|
path: build/c/test-logs/shard-manifest.txt
|
|
# The harness writes the manifest BEFORE any suite runs, so a
|
|
# missing file here means the job died before the harness started
|
|
# (contract step / build) — that failure is already the job's red;
|
|
# warn instead of stacking a second error on top of it. The
|
|
# shard-completeness job still gates the cross-shard union.
|
|
if-no-files-found: warn
|
|
|
|
# Shard 1 (or the unsharded job) saves the compiler cache; every shard
|
|
# builds the identical runner, so one cache carries the full object set.
|
|
- name: Compiler cache (save)
|
|
if: always() && (matrix.shard == null || startsWith(matrix.shard, '1/'))
|
|
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ github.workspace }}/.ccache
|
|
key: ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-${{ github.ref }}-${{ github.sha }}
|
|
|
|
# Cross-shard completeness: re-proves at runtime that each leg's shards
|
|
# agreed on the full suite list and that the union of their slices IS that
|
|
# list. This is the guard the per-shard union checks cannot provide: a
|
|
# mis-plumbed CBM_TEST_SHARD (two jobs running the same slice) passes every
|
|
# per-shard guard while a slice runs nowhere — only a cross-shard view
|
|
# catches it. Runs and gates on unsharded topologies too (every leg then
|
|
# contributes one 1/1 manifest whose slice must equal the full list).
|
|
shard-completeness:
|
|
needs: [test-unix, test-windows]
|
|
if: always()
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
# The union proof lives in the canonical scripts/ci entry, not inline
|
|
# YAML (venue-parity contract) — checkout (into repo/, so the manifest
|
|
# download below stays untouched) provides the script.
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
path: repo
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
pattern: shard-manifest-*
|
|
path: manifests
|
|
- name: Verify cross-shard union per leg
|
|
run: repo/scripts/ci/verify-shard-union.sh manifests
|
|
|
|
# Windows product-surface regression guards. Distinct from test-windows above
|
|
# (the sanitizer C suite): these drive a real product binary + external UI pack
|
|
# over stdio / CLI / HTTP and fail if a Windows bug already fixed on main comes
|
|
# back -- non-ASCII repo paths dropping definitions (#636/#357, fixed by #700),
|
|
# the PreToolUse hook augmenter no-op on drive-letter cwd (#618, fixed by #619),
|
|
# the UI directory picker not enumerating drives (#548, roots field), and non-ASCII
|
|
# CLI arguments being mangled by the narrow-argv main() (#423/#20, fixed by the
|
|
# wide-argv entrypoint that reads GetCommandLineW).
|
|
test-windows-guards:
|
|
runs-on: windows-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
|
with:
|
|
msystem: CLANG64
|
|
path-type: inherit
|
|
install: >-
|
|
mingw-w64-clang-x86_64-clang
|
|
mingw-w64-clang-x86_64-zlib
|
|
mingw-w64-clang-x86_64-ccache
|
|
make
|
|
git
|
|
|
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: "22"
|
|
|
|
- name: Compiler cache (content-verified)
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ github.workspace }}/.ccache
|
|
key: ccache-guards-${{ github.ref }}-${{ github.sha }}
|
|
restore-keys: |
|
|
ccache-guards-${{ github.ref }}-
|
|
ccache-guards-
|
|
|
|
- name: Build product binary with external UI assets and guard seams
|
|
shell: msys2 {0}
|
|
# --with-ui builds the frontend and its content-addressed asset pack, so
|
|
# the drive-picker guard's HTTP UI is available. Functional gate only
|
|
# (no sanitizers).
|
|
run: scripts/build.sh --with-ui CC=clang CXX=clang++ TEST_SEAMS=1
|
|
env:
|
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
|
CCACHE_MAXSIZE: 1500M
|
|
|
|
- name: Windows regression guards (#636/#357, #618, #548, #423/#20)
|
|
shell: pwsh
|
|
# -GuardsOnly runs the four green guards and gates on them. All guards run
|
|
# against the real supervisor->worker spawn path: under the mandatory
|
|
# coordination daemon, CBM_INDEX_SUPERVISOR=0 is a fail-closed refusal, so
|
|
# the driver no longer sets it anywhere.
|
|
run: ./scripts/test-windows.ps1 -GuardsOnly -Binary build/c/codebase-memory-mcp.exe
|