#!/usr/bin/env bash # ci/tests/touched-requirements-test-attribution.test.sh — regression tests for DETECTION # RULE 3 in ci/touched-requirements.sh: a change anywhere under `conformance/tests//` # attributes THAT TEST'S OWN `conformance.home.k3s/R-*` labels. # # THE GAP THESE LOCK DOWN (2026-09-12). The coarse path map keys `conformance/*` to # R-TEST-01..04 — the requirements about the HARNESS — and says nothing about what a given # test PROVES. So weakening `conformance/tests/.../check.sh` attributed R-TEST-01..04 and # nothing else: the merge gate ran the harness's self-tests and NOT the weakened test's own # requirement subset. Weakening (or deleting) a test is the one change that most needs the # requirement it certifies to be re-verified, and it was the change least likely to select it. # The content scan cannot close this: it reads only ADDED/REMOVED lines, so it sees a change # to the LABEL BLOCK but not one to the test BODY — exactly the case that matters. # # Also covers the two path-map entries added in the same change (infra/* -> R-ENV-01, and # R-TEST-06 on .gitea/*|ci/*), which is how the two L0 checks that are DELIBERATELY not # whole-repo-scoped get selected by the paths that can break them. # # Same harness as the sibling suites: the real script, pointed at a THROWAWAY git repo via # the REPO_ROOT override. No network, no cluster, the working repo is never touched. # # Run: ci/tests/touched-requirements-test-attribution.test.sh (or `make -C ci test`) set -euo pipefail CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SCRIPT="${CI_DIR}/touched-requirements.sh" PASS=0 FAIL=0 ok() { printf ' ok %s\n' "$*"; PASS=$((PASS+1)); } bad() { printf ' FAIL %s\n' "$*" >&2; FAIL=$((FAIL+1)); } assert_present() { if grep -qxF "$1" <<<"$2"; then ok "$3" else bad "$3 (missing '$1' from: $(tr '\n' ' ' <<<"$2"))"; fi } assert_absent() { if grep -qxF "$1" <<<"$2"; then bad "$3 (found unexpected '$1' in: $(tr '\n' ' ' <<<"$2"))" else ok "$3"; fi } TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT git -C "$TMP" init -q -b main git -C "$TMP" config user.email t@t.t git -C "$TMP" config user.name t git -C "$TMP" config commit.gpgsign false # --- Baseline: two conformance tests, each declaring its own requirement labels ---------- # TEST A: a flat l0-static test. TEST B: a test with a NESTED fixture dir, to prove the # walk-up from an arbitrary changed file to the enclosing test dir. mkdir -p "$TMP/conformance/tests/l0-static/alpha" \ "$TMP/conformance/tests/l2-behavioral/beta/fixtures" \ "$TMP/conformance/lib" "$TMP/infra" "$TMP/ci" cat > "$TMP/conformance/tests/l0-static/alpha/chainsaw-test.yaml" <<'YAML' # HEADER PROSE. This test used to also cover conformance.home.k3s/R-STALE-09: "true", # which is a REFERENCE in a comment, not a declaration, and must NOT be attributed. apiVersion: chainsaw.kyverno.io/v1alpha1 kind: Test metadata: name: alpha labels: conformance.home.k3s/R-ALPHA-01: "true" conformance.home.k3s/R-ALPHA-02: "true" annotations: conformance.home.k3s/layer: "L0" spec: {} YAML # The check BODY deliberately contains NO R-ID text, so anything the scan finds for a # body-only edit came from rule 3 and not from the content scan. cat > "$TMP/conformance/tests/l0-static/alpha/check.sh" <<'SH' #!/usr/bin/env bash echo "assert something" SH cat > "$TMP/conformance/tests/l2-behavioral/beta/chainsaw-test.yaml" <<'YAML' apiVersion: chainsaw.kyverno.io/v1alpha1 kind: Test metadata: name: beta labels: conformance.home.k3s/R-BETA-07: "true" spec: {} YAML echo "seed: 1" > "$TMP/conformance/tests/l2-behavioral/beta/fixtures/data.yaml" echo "harness helper" > "$TMP/conformance/lib/helper.sh" git -C "$TMP" add -A && git -C "$TMP" commit -qm baseline # ======================================================================================== echo "== case 1: a BODY-ONLY edit to a test attributes that test's own labels" git -C "$TMP" checkout -q -b body-edit cat > "$TMP/conformance/tests/l0-static/alpha/check.sh" <<'SH' #!/usr/bin/env bash echo "assert something weaker" SH git -C "$TMP" add -A && git -C "$TMP" commit -qm weaken OUT="$(REPO_ROOT="$TMP" BASE_REF=main "$SCRIPT" 2>/dev/null)" printf 'touched set:\n%s\n---\n' "$OUT" assert_present "R-ALPHA-01" "$OUT" "body-only test edit attributes the test's own R-ALPHA-01" assert_present "R-ALPHA-02" "$OUT" "body-only test edit attributes the test's own R-ALPHA-02" assert_present "R-TEST-03" "$OUT" "the conformance/* path map still applies (R-TEST-03)" assert_absent "R-STALE-09" "$OUT" "an R-ID cited only in the test's HEADER COMMENT is NOT attributed" assert_absent "R-BETA-07" "$OUT" "an UNRELATED test's labels are NOT attributed" # ======================================================================================== echo "== case 2: an edit to a NESTED file walks up to the enclosing test" git -C "$TMP" checkout -q -b nested-edit main echo "seed: 2" > "$TMP/conformance/tests/l2-behavioral/beta/fixtures/data.yaml" git -C "$TMP" add -A && git -C "$TMP" commit -qm fixture OUT="$(REPO_ROOT="$TMP" BASE_REF=main "$SCRIPT" 2>/dev/null)" printf 'touched set:\n%s\n---\n' "$OUT" assert_present "R-BETA-07" "$OUT" "nested fixture edit attributes the enclosing test's R-BETA-07" assert_absent "R-ALPHA-01" "$OUT" "nested fixture edit does NOT attribute a sibling test's labels" # ======================================================================================== echo "== case 3: DELETING a test still attributes what it used to certify" # Removing the only verifier of a requirement must not be the cheapest way to make the gate # stop checking it — so the labels are read from the BASE when HEAD no longer has them. git -C "$TMP" checkout -q -b delete-test main git -C "$TMP" rm -rq "$TMP/conformance/tests/l0-static/alpha" git -C "$TMP" commit -qm "delete the alpha test" OUT="$(REPO_ROOT="$TMP" BASE_REF=main "$SCRIPT" 2>/dev/null)" printf 'touched set:\n%s\n---\n' "$OUT" assert_present "R-ALPHA-01" "$OUT" "deleting a test STILL attributes its R-ALPHA-01 (read from base)" assert_present "R-ALPHA-02" "$OUT" "deleting a test STILL attributes its R-ALPHA-02 (read from base)" # ======================================================================================== echo "== case 4: a change OUTSIDE conformance/tests/ gains no test labels" git -C "$TMP" checkout -q -b lib-edit main echo "harness helper v2" > "$TMP/conformance/lib/helper.sh" git -C "$TMP" add -A && git -C "$TMP" commit -qm lib OUT="$(REPO_ROOT="$TMP" BASE_REF=main "$SCRIPT" 2>/dev/null)" printf 'touched set:\n%s\n---\n' "$OUT" assert_present "R-TEST-02" "$OUT" "a conformance/lib edit still attributes the harness requirements" assert_absent "R-ALPHA-01" "$OUT" "a conformance/lib edit attributes NO individual test's labels" assert_absent "R-BETA-07" "$OUT" "a conformance/lib edit attributes NO individual test's labels (B)" # ======================================================================================== echo "== case 5: path-map entries for the two DELIBERATELY path-scoped L0 checks" # infra/* -> R-ENV-01 (dev-nodes-provisioned-as-code reads nothing but infra/). git -C "$TMP" checkout -q -b infra-edit main printf 'resource "x" "y" {}\n' > "$TMP/infra/main.tf" git -C "$TMP" add -A && git -C "$TMP" commit -qm infra OUT="$(REPO_ROOT="$TMP" BASE_REF=main "$SCRIPT" 2>/dev/null)" printf 'touched set:\n%s\n---\n' "$OUT" assert_present "R-ENV-01" "$OUT" "an infra/ edit attributes R-ENV-01 (was UNMAPPED: attributed nothing)" # ci/* -> ... R-TEST-06 (spec-immutability-enforced reads nothing but ci/ + the spec repo). git -C "$TMP" checkout -q -b ci-edit main printf 'echo hello\n' > "$TMP/ci/thing.sh" git -C "$TMP" add -A && git -C "$TMP" commit -qm ci OUT="$(REPO_ROOT="$TMP" BASE_REF=main "$SCRIPT" 2>/dev/null)" printf 'touched set:\n%s\n---\n' "$OUT" assert_present "R-TEST-06" "$OUT" "a ci/ edit attributes R-TEST-06 (the spec-immutability test's own ID)" assert_present "R-GITOPS-01" "$OUT" "a ci/ edit still attributes R-GITOPS-01" echo if [ "$FAIL" -eq 0 ]; then echo "touched-requirements test-attribution tests: PASS (${PASS} assertions)"; exit 0 fi echo "touched-requirements test-attribution tests: FAIL (${FAIL} failed, ${PASS} passed)" >&2 exit 1