test: regression suite for vendored-source staleness invalidation + wire into CI

Proves build-redbear.sh's vendored source/ staleness loop: seeds on first sight
(no spurious rebuild), stays quiet unchanged, and invalidates target/ on
committed/uncommitted/untracked source/ changes. Keeps the loop body in sync.
This commit is contained in:
2026-08-01 03:56:10 +03:00
parent 3e32079b79
commit d22ae0dea2
2 changed files with 69 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# test-vendored-source-staleness.sh — regression test for build-redbear.sh's
# vendored-recipe source-staleness invalidation (the loop that rm's a recipe's
# target/ when its git-committed source/ tree changes).
#
# Why: cookbook keys its build cache on source.tar's hash, but the actual build
# input for a vendored recipe is source/. A version bump that changes source/
# without changing source.tar (the Qt 6.11.0->6.11.1 case) otherwise reuses a
# stale stage and ships the wrong version. This test proves the loop SEEDs on
# first sight (no spurious rebuild), stays quiet when unchanged, and invalidates
# on both committed and uncommitted source/ changes.
#
# The loop body below MUST stay in sync with build-redbear.sh. Zero network.
set -u
PASS=0; FAIL=0
ok(){ PASS=$((PASS+1)); printf ' ok %s\n' "$1"; }
no(){ FAIL=$((FAIL+1)); printf ' FAIL %s\n %s\n' "$1" "${2:-}"; }
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
PROJECT_ROOT="$T"; C_INFO=">>>"; C_RESET=""
git -C "$T" init -q; git -C "$T" config user.email t@t; git -C "$T" config user.name t
# --- loop body copied verbatim from build-redbear.sh (keep in sync) ---
run_loop(){
while IFS= read -r _rt; do
_rdir="$(dirname "$_rt")"; _sdir="$_rdir/source"
[ -d "$_sdir" ] || continue
_tgt="$_rdir/target/x86_64-unknown-redox"
[ -f "$_tgt/stage.pkgar" ] || continue
_relsrc="${_sdir#"$PROJECT_ROOT"/}"
_tree="$(git -C "$PROJECT_ROOT" rev-parse "HEAD:$_relsrc" 2>/dev/null || echo "")"
[ -n "$_tree" ] || continue
_dirty=""
git -C "$PROJECT_ROOT" diff --quiet HEAD -- "$_relsrc" 2>/dev/null || _dirty="-dirty"
[ -n "$(git -C "$PROJECT_ROOT" ls-files --others --exclude-standard -- "$_relsrc" 2>/dev/null)" ] && _dirty="-dirty"
_cur="${_tree}${_dirty}"; _fp="$_tgt/.redbear-source-tree"
_last="$(cat "$_fp" 2>/dev/null || echo "")"
if [ -z "$_last" ]; then printf '%s\n' "$_cur" > "$_fp"; echo "SEED"
elif [ "$_cur" != "$_last" ]; then echo "INVALIDATE"; rm -rf "$_rdir/target"
else echo "UNCHANGED"; fi
done < <(find "$PROJECT_ROOT/local/recipes" -name recipe.toml -not -path '*/wip/*' 2>/dev/null)
}
R="$T/local/recipes/foo"
mkdir -p "$R/source" "$R/target/x86_64-unknown-redox"
echo v1 > "$R/source/f.c"; printf '[package]\nname="foo"\n' > "$R/recipe.toml"
echo pkgar > "$R/target/x86_64-unknown-redox/stage.pkgar"
git -C "$T" add -A; git -C "$T" commit -qm init
[ "$(run_loop)" = SEED ] && [ -d "$R/target" ] && ok "first observation seeds + keeps target" || no "seed"
[ "$(run_loop)" = UNCHANGED ] && [ -d "$R/target" ] && ok "unchanged keeps target" || no "unchanged"
echo v2 > "$R/source/f.c"; git -C "$T" add -A; git -C "$T" commit -qm change
[ "$(run_loop)" = INVALIDATE ] && [ ! -d "$R/target" ] && ok "committed source change invalidates target" || no "committed-change"
# rebuild target + reseed, then dirty (uncommitted) change
mkdir -p "$R/target/x86_64-unknown-redox"; echo pkgar > "$R/target/x86_64-unknown-redox/stage.pkgar"
run_loop >/dev/null
echo v3-dirty > "$R/source/f.c" # uncommitted
[ "$(run_loop)" = INVALIDATE ] && [ ! -d "$R/target" ] && ok "uncommitted (dirty) source change invalidates" || no "dirty-change"
# untracked new file in source/ also counts as dirty
mkdir -p "$R/target/x86_64-unknown-redox"; echo pkgar > "$R/target/x86_64-unknown-redox/stage.pkgar"
git -C "$T" add -A; git -C "$T" commit -qm v3; run_loop >/dev/null
echo new > "$R/source/added.c" # untracked
[ "$(run_loop)" = INVALIDATE ] && ok "untracked new file in source/ invalidates" || no "untracked-file"
echo " PASS: $PASS FAIL: $FAIL"
[ "$FAIL" -eq 0 ] && { echo " ALL GREEN"; exit 0; } || exit 1