bin/ka-promote: implement resolver + cumulative + manifest.lock (Phase 6 of #22)
First of the three [ka:cli-build-out] verbs (umbrella #21). Reads fleet/<host>.yaml, resolves includes[] (single-file + series-dir), concatenates in apply order, emits build/<host>/<ref>/{cumulative.patch, manifest.lock}. Phase-3 ground truth on fresnel parity: b2sum 4d9d93c655ea701b… matches bit-for-bit. Five tests in tests/ka-promote/ (fresnel parity, series-dir resolver, bad-include, missing-patch, dup-include) all pass. Validator (--validate-against <linux-checkout>) hard-fails on: missing .git, baseline.ref not in checkout, HEAD-tree != baseline.ref tree, or uncommitted/untracked changes. Verified on boltzmann against the torvalds v7.0 worktree (all 3 negative paths exit 3 with clear errors). Side fix: fleet/fresnel.yaml baseline.tree mmind/linux-rockchip → torvalds/linux. mmind doesn't ship a plain v7.0 tag; baseline was actually torvalds the whole time. mmind kept as informational patch_authoring_context. Phase-5 reviewer (sonnet outside-look, #22 comment 1135) followups addressed: series-dir fixture count 7 (not 6), divergence = hard error, raw-bytes manifest hash, duplicate-include pre-flight check, explicit yaml.dump(sort_keys=True). Language choice (vs ka-status's bash): pure python3 — YAML round-trip, dict construction, and per-file hashing made bash+heredoc python quoting hell with no readability gain. Phase 7 (verify on ampere parity) + Phase 8 (close + README rewrite + PR) to follow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+138
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env bash
|
||||
# ka-promote test suite.
|
||||
#
|
||||
# Each test runs ka-promote against a fixture from fixtures/ in a temporary
|
||||
# sandboxed kernel-agent tree (bin/, fleet/, patches/ — patches/ symlinked
|
||||
# from the real repo so we exercise the real scope-tagged patch files).
|
||||
#
|
||||
# Exit 0 iff every test passes. Non-zero on first failure.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
fixtures="${repo_root}/tests/ka-promote/fixtures"
|
||||
|
||||
# Phase-3 ground truth — recorded 2026-05-18, fresnel cumulative b2sum.
|
||||
FRESNEL_EXPECTED_B2SUM=4d9d93c655ea701b587bf1383c794f41b1aeb3bc32bca69ce3488852ec2c1474a2f47585608598b39ac05671490b8df63c5bc7d093f87e1afd5a92f908891b67
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
results=()
|
||||
|
||||
note() { printf ' %s\n' "$*"; }
|
||||
ok() { results+=("PASS $1"); pass=$((pass+1)); note "PASS"; }
|
||||
ko() { results+=("FAIL $1: $2"); fail=$((fail+1)); note "FAIL: $2"; }
|
||||
|
||||
make_sandbox() {
|
||||
# $1 = fixture yaml path. Builds a scratch tree with bin/+fleet/+patches/
|
||||
# and copies the fixture into fleet/<host>.yaml (extracting host from fixture).
|
||||
local fixture="$1"
|
||||
local scratch
|
||||
scratch=$(mktemp -d -t ka-promote-test.XXXXXX)
|
||||
mkdir -p "$scratch/bin" "$scratch/fleet"
|
||||
cp "$repo_root/bin/ka-promote" "$scratch/bin/ka-promote"
|
||||
ln -s "$repo_root/patches" "$scratch/patches"
|
||||
local host
|
||||
host=$(python3 -c "import yaml,sys; print(yaml.safe_load(open(sys.argv[1]))['host'])" "$fixture")
|
||||
cp "$fixture" "$scratch/fleet/${host}.yaml"
|
||||
echo "$scratch|$host"
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local name="$1"
|
||||
local fixture="$2"
|
||||
local expected_exit="$3"
|
||||
local check_fn="${4:-}"
|
||||
|
||||
echo "::: $name"
|
||||
local pair scratch host
|
||||
pair=$(make_sandbox "$fixture")
|
||||
scratch="${pair%|*}"
|
||||
host="${pair#*|}"
|
||||
set +e
|
||||
out=$("$scratch/bin/ka-promote" "$host" --output-dir "$scratch/build" 2>&1)
|
||||
actual_exit=$?
|
||||
set -e
|
||||
|
||||
if [ "$actual_exit" -ne "$expected_exit" ]; then
|
||||
ko "$name" "expected exit $expected_exit, got $actual_exit. Output: $out"
|
||||
rm -rf "$scratch"
|
||||
return
|
||||
fi
|
||||
if [ -n "$check_fn" ]; then
|
||||
if ! "$check_fn" "$scratch" "$host" "$out"; then
|
||||
ko "$name" "check function reported failure (see notes above)"
|
||||
rm -rf "$scratch"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
ok "$name"
|
||||
rm -rf "$scratch"
|
||||
}
|
||||
|
||||
check_series_dir() {
|
||||
# 7 patches resolved, all from_series:true, in filename order.
|
||||
local scratch="$1" host="$2"
|
||||
local lock="$scratch/build/$host/v7.0/manifest.lock"
|
||||
[ -f "$lock" ] || { note "manifest.lock missing"; return 1; }
|
||||
local n
|
||||
n=$(python3 -c "import yaml; print(len(yaml.safe_load(open('$lock'))['resolved_patches']))")
|
||||
if [ "$n" -ne 7 ]; then
|
||||
note "expected 7 resolved patches, got $n"
|
||||
return 1
|
||||
fi
|
||||
python3 - "$lock" <<'PY' || { note "from_series check failed"; exit 1; }
|
||||
import yaml, sys
|
||||
lk = yaml.safe_load(open(sys.argv[1]))
|
||||
for r in lk['resolved_patches']:
|
||||
assert r['from_series'] is True, f"{r['include']} not flagged from_series"
|
||||
expected_basenames = [f'0{i:03d}'[1:] for i in range(1,8)] # 0001..0007
|
||||
got = [r['include'].split('/')[-1][:4] for r in lk['resolved_patches']]
|
||||
assert got == ['000'+str(i) for i in range(1,8)], f'apply order mismatch: {got}'
|
||||
PY
|
||||
}
|
||||
|
||||
check_fresnel_parity() {
|
||||
local scratch="$1" host="$2"
|
||||
local lock="$scratch/build/$host/v7.0/manifest.lock"
|
||||
[ -f "$lock" ] || { note "manifest.lock missing"; return 1; }
|
||||
local b2
|
||||
b2=$(python3 -c "import yaml; print(yaml.safe_load(open('$lock'))['cumulative']['b2sum'])")
|
||||
if [ "$b2" != "$FRESNEL_EXPECTED_B2SUM" ]; then
|
||||
note "b2sum mismatch"
|
||||
note " expected: $FRESNEL_EXPECTED_B2SUM"
|
||||
note " got: $b2"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo
|
||||
echo "Running ka-promote test suite from $repo_root"
|
||||
echo
|
||||
|
||||
# Use the real fleet/fresnel.yaml — copy into a sandbox so the test is hermetic.
|
||||
mkdir -p /tmp/ka-promote-parity-fixture
|
||||
cp "$repo_root/fleet/fresnel.yaml" /tmp/ka-promote-parity-fixture/fresnel.yaml
|
||||
run_test "fresnel parity (= Phase-3 ground truth b2sum)" \
|
||||
/tmp/ka-promote-parity-fixture/fresnel.yaml 0 check_fresnel_parity
|
||||
rm -rf /tmp/ka-promote-parity-fixture
|
||||
|
||||
run_test "series-dir resolver (bes2600/staging-prep-series-danctnix → 7 patches)" \
|
||||
"$fixtures/series-dir.yaml" 0 check_series_dir
|
||||
|
||||
run_test "bad-include rejection (exit 4)" \
|
||||
"$fixtures/bad-include.yaml" 4
|
||||
|
||||
run_test "missing-patch rejection (exit 2)" \
|
||||
"$fixtures/missing-patch.yaml" 2
|
||||
|
||||
run_test "duplicate-include rejection (exit 4)" \
|
||||
"$fixtures/duplicate-include.yaml" 4
|
||||
|
||||
echo
|
||||
echo "===================="
|
||||
printf '%s\n' "${results[@]}"
|
||||
echo "===================="
|
||||
echo "passed: $pass"
|
||||
echo "failed: $fail"
|
||||
[ "$fail" -eq 0 ] || exit 1
|
||||
Reference in New Issue
Block a user