name: Unit tests # TRACES: DP-007 | PR-004 # # The tier the verification strategy is built on, finally executing. # # docs/requirements.md describes a four-tier plan in which T1 (functor unit) # and T2 (replay) are "the only tiers that can exist in CI at all", and the # traceability gate reports a CI-scope coverage fraction over exactly those # tiers. Until this workflow existed, nothing ran them: "covered" meant a # TRACES tag was present in a file, not that any test had been executed. That # is the same failure mode as counting a test that cannot run, one level up, # and the gate cannot detect it because a tag is all it can see. # # The runner is an Intel N100 with no discrete GPU. Nothing here calls a model: # T1 constructs node functors directly, and T2 replays a precomputed HDF5 dump. # T3 (ORT CPU smoke) and T4 (GPU) are deliberately absent -- the embedder is # ~930 ms/frame on this hardware, so a 77 s clip at 5 fps would be six minutes # of inference alone. on: push: branches: - main - master - develop pull_request: branches: - main - master - develop jobs: unit-tests: runs-on: linux/amd64 name: Build and run the GPU-free suite # Pinned by tag, never `latest`, so rebuilding the image cannot silently # change what a previous green build meant. Bumping the dependency set means # bumping the tag in scripts/ci/build_builder_image.sh AND here, in one # commit -- see that script's header. container: image: gitea.tourolle.paris/dtourolle/sae-builder-cpu:v1 steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 # KPN is a submodule and the pipeline does not build without it. # # NOTE: this checks out the commit this repo PINS, which is the whole # point and is also the first thing this job will disagree with a # developer about. A local KPN working copy that is ahead of # origin/master builds and passes here while CI builds something else # entirely; the AR-004 evidence in docs/requirements.md was gathered # that way. If this job fails on tests that pass locally, check # `git -C external/KPN log origin/master..HEAD` before suspecting the # tests. # LFS is deliberately NOT fetched: SAE_MODELS_DIR is baked into the # binary as a path string and nothing in T1/T2 opens a model file, so # pulling ~hundreds of MB of ONNX would cost the job everything and # buy it nothing. submodules: recursive lfs: false - name: Assert the builder image is the pinned one run: | set -e echo "builder=$SAE_BUILDER version=$SAE_BUILDER_VERSION" echo "ort=$SAE_ORT_VERSION opencv=$SAE_OPENCV_VERSION" # The image reports its own tag. A mismatch means the `container:` # line above and the image that actually landed disagree, which is # exactly the drift the pinning exists to prevent -- so it fails the # job rather than building against an unknown toolchain. [ "$SAE_BUILDER_VERSION" = "v1" ] || { echo "image reports version '$SAE_BUILDER_VERSION', workflow pins v1" >&2 exit 1 } - name: Fetch replay fixtures env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} # bash, not sh: the script declares #!/bin/bash and uses `set -o # pipefail` and arrays, which dash does not have. run: bash scripts/artifacts/pull_artifacts.sh replay-fixtures latest # pull_artifacts.sh warns and continues when a package version is missing, # which is right for a developer pulling one artifact of several and wrong # here. A T2 test whose fixture never arrived must not look like a pass: # the dumps are the entire input to the replay tier, and VR-002's claim is # that replay drives the real nodes over real data. - name: Verify the fixtures actually arrived run: | set -e missing=0 for f in tests/fixtures/dumps/superhero.h5; do if [ -s "$f" ]; then echo " ok: $f ($(wc -c < "$f") bytes)" else echo " MISSING: $f" >&2 missing=1 fi done if [ "$missing" -ne 0 ]; then echo "" >&2 echo "Replay fixtures are absent, so the T2 tier cannot run." >&2 echo "They are not in git (tests/fixtures/dumps/.gitignore) -- they" >&2 echo "live in the Gitea generic package registry and are pulled by" >&2 echo "the step above, which needs GITEA_TOKEN to resolve 'latest'." >&2 exit 1 fi - name: Configure run: | set -e # SAE_GEMM_BACKEND defaults to ROCM and the auto-detect prefers a GPU # backend where it finds one; CPU is stated explicitly so this job # cannot start depending on what happens to be installed on the runner. # The CPU kernel is OpenBLAS in this image (tests/CMakeLists.txt fails # the configure if it is not), so the suite exercises the kernel the # CPU release actually ships. cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DSAE_BUILD_TESTS=ON \ -DSAE_GEMM_BACKEND=CPU - name: Build the test suite run: cmake --build build --target sae_tests --parallel - name: Run the tests run: ctest --test-dir build --output-on-failure - name: Save test output if: always() uses: actions/upload-artifact@v3 with: name: unit-test-results path: build/Testing/ retention-days: 30