Skip to content

tools: add a re-runnable test-baseline gate for the repo consolidation - #76

Merged
balbasty merged 3 commits into
mainfrom
tools/test-baseline-gate
Aug 18, 2026
Merged

tools: add a re-runnable test-baseline gate for the repo consolidation#76
balbasty merged 3 commits into
mainfrom
tools/test-baseline-gate

Conversation

@balbasty

@balbastybalbasty commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Why

The six C++/CUDA repos are about to be consolidated into one, which rewrites git history and relocates every file. The only thing that will prove the migration was mechanically correct is that the test suite produces identical results before and after — so that evidence has to be a re-runnable artifact, not a number written down once.

A previous investigation reported "11 suites, 27,925 checks". That was measured on a stale dev branch (11 test files) under the default sparse configuration only, and does not describe origin/main. The real figures are 13 suites and 59,886 checks per configuration leg, across five cpu-lib legs plus this repo's own two tests.

What this adds

  • tools/test-baseline.sh — builds fastfields-cpu-lib's suite once per configuration leg and emits a sorted, machine-comparable report:

    suite <TAB> config <TAB> checks <TAB> failures
    

    Two runs compare with plain diff. --check compares against the recorded file and exits non-zero on any failure, any suite that did not run, or any check-count difference.

  • tools/test-baseline.expected — the recorded result for all six legs, with the exact commits it was measured at in its header.

The measured baseline

Measured at cpu-lib 1fb2b37impl 5e2c78ekernels 1df9fd3 (the pin chain is currently fully aligned with each repo's main tip). All five cpu-lib legs produce identical counts — 13 suites, 59,886 checks, 0 failures:

suitecheckssuitechecks
distance2352reg_field19250
distance_mesh4622reg_flow16347
distance_spline704reg_op186
posdef4012resize630
pushpull308restrict65
pushpull_backward6381solve_field452
splinc4577total59886

fastfields-lib's two tests add 14 checks (device_check 5, splinc_bound 9).

The sanitizer leg (ASan + UBSan, -fno-sanitize-recover=all) is clean — same 59,886 checks, zero failures, no runtime errors.

The legs

The three BOUNDFLAGS/SPLINEFLAGS legs and the sanitizer job mirror .github/workflows/test.yaml exactly. Two more are added:

  • default — a bare make test. Identical to cuda-default by construction; recorded separately so a change to the Makefile's target-specific defaults shows up here rather than silently weakening the other legs.
  • lib — this repo's two standalone argument-validation tests, gated by nothing else.

FF_TEST_SPARSE is not a configuration axis: it is hard-coded into TESTCPPFLAGS, so it is on for every leg here and in CI.

Load-bearing details, documented in the script

  • Every leg goes through make test, never make build/test_<x>. The Makefile sets BOUNDFLAGS/SPLINEFLAGS with target-specific plain = assignments on test: — they cannot be ?=, because the global ?= defaults already count as "set" at parse time, so a target-specific ?= would never fire. Those values propagate to the prerequisites make test builds, but building a test binary by its own path does not enter that context and would silently compile the fully-static policy instead of the requested leg.
  • The Makefiles are clang-only by default.CXXFLAGS picks up -ferror-limit and -ftemplate-backtrace-limit, so make CXX=g++ fails on the flags before reaching any source. The script detects a non-clang compiler and replaces CXXFLAGS wholesale, which is what makes --cxx g++ work.
  • Three output formats are parsed (checks: N, failures: M; P/N checks passed; ok:/FAIL: lines). A fourth is reported as UNPARSED and fails the run rather than being skipped.

Determinism

The gate is only sound if check counts are reproducible. Verified:

  • Test sources contain no #ifdef at all, so counts are fixed by loop structure rather than compile policy — which is why all five legs agree exactly.
  • All randomness is std::mt19937 seeded from literal loop counters (for seed = 1..5), never time or entropy.
  • Forcing FF_NUM_THREADS to 1, 2, 8 and 16 gives byte-identical output, including solve_field's CG iteration counts; repeat runs are identical too. strace shows zero clone syscalls: every test workload is below GRAIN_SIZE (32768), so parallel_for never takes its parallel branch and the thread pool is never instantiated. Machine core count is therefore irrelevant, and pushpull's atomic-scatter path never runs concurrently during tests.
  • g++ 13.3 reproduces clang 18.1.3's report byte for byte across all five legs they share (static, dynamic, cuda-default, default, lib) — 54 rows, identical md5. The sanitizer leg is clang-only, as in CI.

Usage

tools/test-baseline.sh --ref main --legs all --check tools/test-baseline.expected

Exit 0 means the tree behaves identically to the recording. All paths are resolved up front, so it runs from any working directory.

Scope

Measurement plus this new tooling only — no source, test, or Makefile is modified.

🤖 Generated with Claude Code

https://claude.ai/code/session_016AjQcY78NgbagPSbPJRr6Z

The six C++/CUDA repos are about to be consolidated into one, which rewrites
git history and relocates every file. The only mechanical proof that such a
migration changed nothing is that the test suite produces identical results
before and after -- so that evidence needs to be a re-runnable artifact rather
than a number recorded once by hand.
tools/test-baseline.sh builds fastfields-cpu-lib's suite once per configuration
leg and emits a sorted, machine-comparable report:
suite <TAB> config <TAB> checks <TAB> failures
The legs mirror .github/workflows/test.yaml exactly -- the three-way
BOUNDFLAGS/SPLINEFLAGS matrix (static, dynamic, cuda-default) plus the separate
ASan+UBSan job -- and add two more: `default` (a bare `make test`, so a change
to the Makefile's target-specific defaults shows up rather than silently
weakening the gate) and `lib` (fastfields-lib's own two standalone
argument-validation tests).
tools/test-baseline.expected records the measured result for all six legs at
the commits named in its header: 13 cpu-lib suites totalling 53988 checks in
each of the five cpu-lib configs, and 14 checks across fastfields-lib's two.
Zero failures everywhere, including under the sanitizers.
`--check` compares a fresh run against that file and exits non-zero on any
failure, any suite that did not run, or any check-count difference, so the
migration gate is a single command with a single pass/fail condition.
Three details in here are load-bearing and are documented at length in the
script rather than left to be rediscovered:
* Every leg goes through `make test`. The Makefile sets BOUNDFLAGS and
SPLINEFLAGS with target-specific plain `=` assignments on `test:` (they
cannot be `?=` -- the global `?=` defaults already count as set at parse
time, so a target-specific `?=` would never fire). Building a test binary
by its own path does not enter that context and would silently measure the
fully-static policy instead of the requested leg.
* -DFF_TEST_SPARSE is hard-coded into TESTCPPFLAGS, so it is not a
configuration axis -- it is on for every leg here and in CI.
* The Makefiles are clang-only by default (CXXFLAGS picks up -ferror-limit
and -ftemplate-backtrace-limit), so `make CXX=g++` fails on the flags
rather than the source. The script detects a non-clang compiler and
replaces CXXFLAGS wholesale, which is what makes --cxx g++ work.
Measurement only -- no source, test or Makefile is touched.
@github-actions

github-actionsBot commented Aug 18, 2026

Copy link
Copy Markdown

Submodule staleness (non-blocking -- fastfields-lib#15)

submodulepinned committracksstatusbehind by
cpu1fb2b37fastfields/fastfields-cpu-lib@mainup to date0
cuda2b2ad55fastfields/fastfields-cuda-lib@mainup to date0

balbasty added a commit that referenced this pull request Aug 18, 2026
…it (#77)
Moves cpu dbd3274 -> 1fb2b37 and cuda 6ae52bd -> 2b2ad55, the current mains
of fastfields-cpu-lib and fastfields-cuda-lib.
This is the top of a bottom-up cascade that closes a live pin skew. Before
it, the two backends resolved to different kernels commits:
CPU path : lib -> cpu-lib -> cpu-impl -> kernels be7be08
CUDA path: lib -> cuda-lib -> cuda-impl -> kernels b09b284 (three behind)
The CUDA side was missing kernels#74 (heap over-read from the past-the-end
FaceIterator, a memory-safety fix), #76 (make the host BVH/normal builders
visible to nvcc's host pass -- a CUDA-specific fix the CUDA path lacked) and
#78 (drop the pointless virtual destructors).
After this commit every path resolves to kernels 1df9fd3:
lib -> cpu-lib 1fb2b37 -> cpu-impl 5e2c78e -> kernels 1df9fd3
lib -> cuda-lib 2b2ad55 -> cuda-impl 83fa026 -> kernels 1df9fd3
The cascade also carries the diag_bending/diag_all corner cross-term
correctness fix (kernels#81) to both backends, its regression test
(cpu-lib#89), the reg_flow Lame dispatch routing (cpu-lib#87) and the
cuda-impl include guards (cuda-impl#48).
Claude-Session: https://claude.ai/code/session_016AjQcY78NgbagPSbPJRr6Z
Co-authored-by: Claude <noreply@anthropic.com>
The first recording in this branch was measured against cpu-lib dbd3274 /
kernels be7be08. While it was being taken, three things landed upstream:
kernels 1df9fd3 fix(regularisers): correct diag_bending/diag_all corner
cross-term
cpu-impl 5e2c78e deps: bump kernels pin to that fix
cpu-lib 1fb2b37 test(reg): cover the diag_bending/diag_all corner
cross-term bug (+ db3c3b5 perf(reg_flow) routing)
The old recording was internally consistent -- every leg was measured against
one frozen set of clones that was never re-fetched -- but it described a tree
that main has since moved past, so it would have failed as a gate for the wrong
reason. Re-recorded against the current pin chain, which is now fully aligned
(each recorded pin equals the pinned repo's own main tip).
The only rows that move are reg_field and reg_flow, in all five cpu-lib legs,
which is what the new corner cross-term coverage should do and nothing else:
reg_field 18284 -> 19250 (+966)
reg_flow 11415 -> 16347 (+4932)
per leg 53988 -> 59886 (+5898)
The other eleven suites are unchanged to the check, and all five legs still
agree with each other exactly. Still zero failures everywhere, including under
ASan+UBSan.
The header now also records that the pin chain is aligned, and why that is
worth stating: when a pin lags its repo's main (as the kernels pin did for the
first recording), "clone main and follow the pins" and "check out main
everywhere" are different trees, and a baseline has to say which one it means.
--check compares whole reports, so running a subset of the recorded legs
diffed as 'every recorded row vanished' -- which reads like catastrophic
breakage rather than the operator error it is. Compare the config sets first
and name both sides.
@balbasty
balbasty merged commit 85f987e into mainAug 18, 2026
4 checks passed
@balbasty
balbasty deleted the tools/test-baseline-gate branch August 18, 2026 20:35
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@balbasty