Pantheon/tests/ab_build_arm.sh

109 lines
4.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2026 David (Corey) Herrera
#
# ab_build_arm.sh — build ONE arm of an A/B and prove the binary really changed.
#
# WHY THIS EXISTS
# A kernel/engine A/B is only a measurement if the two arms are two different
# binaries. A build that quietly hands back the PREVIOUS binary makes the A/B
# read 1.000x and makes a bit-exactness gate pass vacuously — both of which
# look like an honest "no change" rather than a broken build. That has
# already happened once here: regenerating the generated PTX header
# foundry/cuda/fc_cuda_ptx.h changed no .c file, so make said "is up to date"
# and returned a byte-identical harness binary.
#
# The Makefile now depends on headers, so that specific hole is closed. This
# script is the belt to that braces: make can still be defeated by an
# mtime tie (a restored/copied file landing in the same second as the binary),
# by a clock skew over a network mount, by a flag change make cannot see, or
# by the next generated artefact nobody wired up. Identical bytes out of two
# arms is ALWAYS an error, never a result.
#
# USAGE
# tests/ab_build_arm.sh <arm-name> <make-target> [MAKE VARS/ARGS...]
# Deletes the target, builds it, stores it under $AB_DIR/<arm-name>/, and
# FAILS if the result is byte-identical to any arm already stored.
#
# tests/ab_build_arm.sh --compare <fileA> <fileB>
# Just compares two binaries; fails if identical.
#
# Env: AB_DIR (default build/ab) — where arms are kept.
#
# EXIT CODES
# 0 built, and this arm differs from every other stored arm
# 1 usage / build failure
# 2 IDENTICAL BINARY — the build produced no change (this is the bug)
#
# EXAMPLE (the standard shape for a kernel A/B)
# git checkout <base> && tests/ab_build_arm.sh base tests/test_spec_qwen35 USE_CUDA=1
# git checkout <new> && tests/ab_build_arm.sh new tests/test_spec_qwen35 USE_CUDA=1
# # then run each arm's stored binary, not whatever is in the worktree.
set -uo pipefail
AB_DIR="${AB_DIR:-build/ab}"
digest() {
if command -v md5sum >/dev/null 2>&1; then md5sum "$1" | awk '{print $1}'
else md5 -q "$1"; fi
}
fail_identical() {
echo "" >&2
echo "FAIL: the build produced a byte-identical binary." >&2
echo " $1" >&2
echo " $2" >&2
echo "" >&2
echo " This is a BROKEN BUILD, not a measurement. A change was made and" >&2
echo " the compiler emitted the same bytes, so any A/B on these two" >&2
echo " binaries reads 1.000x and any bit-exactness gate passes" >&2
echo " vacuously. Find out why the change did not reach the binary" >&2
echo " (stale generated header? mtime tie? wrong tree? wrong flags?)" >&2
echo " before you record a single number." >&2
exit 2
}
if [ "${1:-}" = "--compare" ]; then
[ $# -eq 3 ] || { echo "usage: $0 --compare <fileA> <fileB>" >&2; exit 1; }
[ -f "$2" ] && [ -f "$3" ] || { echo "missing file" >&2; exit 1; }
if cmp -s "$2" "$3"; then fail_identical "$2 $(digest "$2")" "$3 $(digest "$3")"; fi
echo "OK: $2 and $3 differ ($(digest "$2") vs $(digest "$3"))"
exit 0
fi
[ $# -ge 2 ] || { sed -n '2,40p' "$0"; exit 1; }
ARM="$1"; TARGET="$2"; shift 2
# Delete first: never let make's opinion of "up to date" decide whether this arm
# gets a fresh link.
rm -f "$TARGET"
echo "== building arm '$ARM': make $TARGET $*"
if ! make "$TARGET" "$@"; then
echo "FAIL: build of $TARGET failed" >&2
exit 1
fi
[ -f "$TARGET" ] || { echo "FAIL: make reported success but $TARGET does not exist" >&2; exit 1; }
mkdir -p "$AB_DIR/$ARM"
BASE="$(basename "$TARGET")"
DEST="$AB_DIR/$ARM/$BASE"
cp -p "$TARGET" "$DEST"
SUM="$(digest "$DEST")"
printf '%s %s\n' "$SUM" "$BASE" > "$DEST.md5"
# Compare against every other arm holding a binary of the same name.
for other in "$AB_DIR"/*/"$BASE"; do
[ -f "$other" ] || continue
[ "$other" = "$DEST" ] && continue
if cmp -s "$other" "$DEST"; then
fail_identical "$other $(digest "$other")" "$DEST $SUM"
fi
echo " differs from $other ($(digest "$other"))"
done
echo "OK: arm '$ARM' -> $DEST md5 $SUM"
echo " run THIS binary for the measurement, not the one in the worktree."