test(dirty-content-fingerprint): add dev-loop distinguishability regression
Adds src/tools/test-dirty-fingerprint-distinguishability.sh, driving a real xce-build.sh scheduler cycle through the 4 required --version fingerprint states (clean/dirty-A/dirty-B-differs/reverted-A-matches). Uses a genuine isolated git worktree (git worktree add --detach at a unique mktemp -u path) to sidestep this actively-shared repo's ambient concurrent-editing noise -- all file edits confined strictly inside the isolated worktree, cleaned up via a trap registered before creation so it safely no-ops if creation never happened. Verified independently by review across two full live runs, including a deliberately sabotaged copy forcing edit-B to reproduce edit-A's content: the sabotaged run correctly failed with a clear message and exit 1, proving genuine discriminating power. git worktree list and the real repo's working tree confirmed byte-identical to baseline before and after every run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
fd53525651
commit
a7d64c8225
2 changed files with 485 additions and 3 deletions
|
|
@ -94,9 +94,10 @@
|
|||
- _Depends: 5.1_
|
||||
|
||||
- [ ] 6. Integration: cross-toolchain consistency and regression verification
|
||||
- [ ] 6.1 Dev-loop distinguishability and clean-tree regression test
|
||||
- New standalone script (following the `build-time-versioning` feature's `src/tools/test-*.sh` convention) drives a real `xce-build.sh scheduler` build cycle: build on a clean tree and confirm `--version` shows the commit id alone (no `-dirty` suffix, no fingerprint suffix); edit a tracked file, rebuild, confirm a fingerprint suffix appears and `-dirty` does not; edit again with different content, rebuild, confirm the fingerprint differs from the previous one; revert to the first edit's exact content, rebuild, confirm the fingerprint matches the first capture exactly
|
||||
- Observable: the script exits non-zero and reports clearly if any of the four expected states (clean/no-suffix-at-all, dirty-A, dirty-B different from A, reverted-to-A matching A) does not hold, or if a literal `-dirty` substring ever reappears anywhere in `--version` output
|
||||
- [x] 6.1 Dev-loop distinguishability and clean-tree regression test
|
||||
- New script `src/tools/test-dirty-fingerprint-distinguishability.sh` uses a genuine isolated `git worktree add --detach` (unique `mktemp -u` path, `trap cleanup EXIT` registered before creation, safe no-op if creation never happened, `rm -rf`+`prune` fallback) to sidestep this actively-shared repo's ambient concurrent-editing noise -- all file edits confined strictly inside the isolated worktree, the real repo's own working tree never touched
|
||||
- Drives a real `xce-build.sh scheduler` cycle through the 4 required states via genuine builds; fails loudly with a clear message on any mismatch
|
||||
- Observable confirmed live (independently re-verified by review across two full live runs, including a deliberately sabotaged copy forcing edit-B to reproduce edit-A's content): clean/no-suffix, dirty-A, dirty-B-differs-from-A, reverted-to-A-matches-A all held with real fingerprints; sabotaged run correctly failed with a clear message and exit 1, proving genuine (not just synthetic) discriminating power; `git worktree list` and the real repo's working tree confirmed byte-identical to baseline before and after every run, both by implementer and reviewer
|
||||
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.1_
|
||||
- _Depends: 2.1, 3.2_
|
||||
|
||||
|
|
|
|||
481
src/tools/test-dirty-fingerprint-distinguishability.sh
Executable file
481
src/tools/test-dirty-fingerprint-distinguishability.sh
Executable file
|
|
@ -0,0 +1,481 @@
|
|||
#!/usr/bin/env bash
|
||||
# test-dirty-fingerprint-distinguishability.sh — dirty-content-fingerprint
|
||||
# task 6.1 (.kiro/specs/dirty-content-fingerprint/tasks.md, Requirements
|
||||
# 1.1, 1.2, 1.3, 1.4, 2.1)
|
||||
#
|
||||
# MOTIVATING GAP this guards against: the already-shipped build-time-
|
||||
# versioning feature's plain "-dirty" suffix cannot tell apart two builds
|
||||
# made from the same commit but with DIFFERENT uncommitted content -- edit
|
||||
# a file, rebuild, edit further, rebuild again: both binaries' --version
|
||||
# output showed the exact same "<commit>-dirty" string. The
|
||||
# dirty-content-fingerprint feature (tasks 1-5.2, already implemented and
|
||||
# committed) replaces that boolean marker with a content-derived hash for
|
||||
# --version specifically. THIS script is the end-to-end regression proof,
|
||||
# via a REAL `xce-build.sh scheduler` build cycle, that the four states
|
||||
# Requirement 1/2 require actually hold: clean tree -> commit id alone;
|
||||
# edit A -> a fingerprint suffix (never "-dirty"); edit B (different
|
||||
# content) -> a DIFFERENT fingerprint; revert to A's exact content -> the
|
||||
# SAME fingerprint as A's original capture.
|
||||
#
|
||||
# ISOLATION FROM AMBIENT SESSION NOISE (read before changing this script):
|
||||
# this repo is routinely under continuous, unrelated concurrent editing in
|
||||
# this environment (other agent sessions, OpenWolf hooks checkpointing
|
||||
# .wolf/buglog.json, etc.) -- `git status --porcelain` on the shared
|
||||
# working tree this script lives in can be, and usually is, already dirty
|
||||
# for reasons that have nothing to do with this test. A "clean tree" state
|
||||
# (Requirement 2.1) can therefore not be reliably observed by pointing
|
||||
# xce-build.sh at the ambient checkout. Instead, this script creates its
|
||||
# own throwaway, fully-isolated `git worktree` checked out at the CURRENT
|
||||
# HEAD commit (a linked worktree, `git worktree add --detach`) before doing
|
||||
# anything else. That worktree starts genuinely clean by construction (a
|
||||
# fresh checkout has zero uncommitted changes), and nothing outside this
|
||||
# script's own controlled edits (to one file, inside that private
|
||||
# worktree) can dirty it during the run -- concurrent activity in the
|
||||
# original shared worktree is a completely separate directory tree and
|
||||
# cannot touch it. This also sidesteps a subtler hazard: xce-build.sh
|
||||
# resolves its own SRC_DIR from `$0`'s location and `cd`s there itself
|
||||
# (`src/tools/xce-build.sh` line ~75-78) before running any `git`
|
||||
# command -- invoking the ISOLATED worktree's own copy of the script (not
|
||||
# the shared tree's copy from a different cwd) is what makes every git
|
||||
# command inside it (rev-parse/status/diff/ls-files) scope to the isolated
|
||||
# worktree automatically, with no extra `-C`/`cd` bookkeeping needed here.
|
||||
#
|
||||
# The isolated worktree is destroyed (git worktree remove --force) on
|
||||
# exit unless --keep-worktree is given. This script's own edits never
|
||||
# touch the shared working tree at all (they land only inside the
|
||||
# throwaway worktree), so there is nothing to restore there -- verified
|
||||
# explicitly at the end anyway (git status --porcelain on this repo's own
|
||||
# README.md must be empty).
|
||||
#
|
||||
# Build path / directory-reuse note: all four builds reuse the SAME build
|
||||
# directory inside the isolated worktree (build 1 is a normal fresh
|
||||
# `meson setup`; builds 2-4 use xce-build.sh's own `--incremental` flag,
|
||||
# i.e. `meson setup --reconfigure`, to avoid four full `rm -rf` + fresh
|
||||
# `meson setup` cycles). This is safe here specifically because every
|
||||
# build in this sequence targets the SAME single component (`scheduler`)
|
||||
# in the SAME dir -- none of bug-1565's "another component's binary
|
||||
# silently deleted" risk applies. Reusing one dir also matters for
|
||||
# correctness, not just speed: meson auto-writes a self-ignoring
|
||||
# `.gitignore` (`*`) into a build dir the moment `meson setup` first
|
||||
# creates it, which is what keeps that build dir's own artifacts out of
|
||||
# `git status --porcelain`/`git ls-files --others` -- and therefore out of
|
||||
# xce-build.sh's own dirty_fingerprint computation -- for every build
|
||||
# after the first. `debug` profile is used throughout: it is build-only
|
||||
# (xce-build.sh's own RUN_TESTS=0 for `debug`), so no bundled `meson test`
|
||||
# run can interfere with or slow down this script (unlike the `asan`
|
||||
# profile the sibling task-6.1 script from build-time-versioning had to
|
||||
# work around).
|
||||
#
|
||||
# Usage:
|
||||
# bash src/tools/test-dirty-fingerprint-distinguishability.sh [--keep-worktree]
|
||||
#
|
||||
# Exit code is the sole pass/fail signal (0 = pass, non-zero = a real
|
||||
# regression or a demonstration inconsistency). Four real build steps
|
||||
# happen (one full, three incremental reconfigure+recompile) -- budget
|
||||
# several minutes of wall-clock time.
|
||||
#
|
||||
# Why this file lives here: same convention as build-time-versioning's
|
||||
# test-build-version-regression.sh / test-version-startup-consistency.sh /
|
||||
# test-xceadmin-build-install-version.sh -- a standalone, CLI-runnable,
|
||||
# real-build regression check for a cross-cutting concern, deliberately
|
||||
# NOT a meson test() (real builds are far too expensive for every `meson
|
||||
# test` run).
|
||||
#
|
||||
# Revalidation triggers:
|
||||
# - VERSION_PREFIX below must match the literal prefix passed to
|
||||
# xce_compose_version_ex() in scheduler/src/main.c (task 3.2).
|
||||
# - The literal "-dirty" suffix text (XCE_DIRTY_SUFFIX in
|
||||
# src/lib/utils.c) must stay "-dirty" for the substring check below to
|
||||
# mean what it says.
|
||||
set -euo pipefail
|
||||
|
||||
# ── paths ───────────────────────────────────────────────────────────────────
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
WORKTREE="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
VERSION_PREFIX="xce v0.0.0.1-"
|
||||
BUILD_DIR_NAME="dirtyfp.debug"
|
||||
README_REL="README.md"
|
||||
MARKER_A="<!-- xce-dirty-fingerprint-test: edit A -->"
|
||||
MARKER_B="<!-- xce-dirty-fingerprint-test: edit B (different content) -->"
|
||||
|
||||
KEEP_WORKTREE=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--keep-worktree) KEEP_WORKTREE=1 ;;
|
||||
*)
|
||||
echo "Unknown argument: $arg" >&2
|
||||
echo "Usage: $0 [--keep-worktree]" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── colour / pass-fail helpers (matches test-build-version-regression.sh /
|
||||
# test-version-startup-consistency.sh) ───────────────────────────────────────
|
||||
green() { printf '\033[32m%s\033[0m\n' "$*"; }
|
||||
red() { printf '\033[31m%s\033[0m\n' "$*"; }
|
||||
yellow() { printf '\033[33m%s\033[0m\n' "$*"; }
|
||||
|
||||
trap 'red "FATAL: command failed (line $LINENO). See output above."' ERR
|
||||
|
||||
ISO_DIR=""
|
||||
|
||||
cleanup() {
|
||||
if [ "$KEEP_WORKTREE" = 1 ]; then
|
||||
if [ -n "$ISO_DIR" ] && [ -d "$ISO_DIR" ]; then
|
||||
yellow "--keep-worktree given: leaving isolated worktree in place at $ISO_DIR"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
if [ -n "$ISO_DIR" ] && [ -d "$ISO_DIR" ]; then
|
||||
git -C "$WORKTREE" worktree remove --force "$ISO_DIR" 2>/dev/null || {
|
||||
rm -rf "$ISO_DIR"
|
||||
git -C "$WORKTREE" worktree prune 2>/dev/null || true
|
||||
}
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# ── pure extraction helpers (no printing -- safe to call via command
|
||||
# substitution; mirrors test-build-version-regression.sh's
|
||||
# extract_commit_id) ─────────────────────────────────────────────────────────
|
||||
|
||||
# extract_commit_id RAW -> prints "<commit-field>" (may itself contain a
|
||||
# "-<fingerprint>" suffix, or not). Returns 1 if VERSION_PREFIX is absent.
|
||||
extract_commit_id() {
|
||||
local raw="$1" rest
|
||||
rest="${raw#*"$VERSION_PREFIX"}"
|
||||
if [ "$rest" = "$raw" ]; then
|
||||
return 1
|
||||
fi
|
||||
printf '%s' "${rest%% built *}"
|
||||
}
|
||||
|
||||
# extract_fingerprint COMMIT_FIELD BASE_COMMIT -> prints the fingerprint
|
||||
# suffix (empty string if COMMIT_FIELD is exactly BASE_COMMIT, i.e. no
|
||||
# suffix at all -- the clean-tree shape). Returns 1 if COMMIT_FIELD is
|
||||
# neither exactly BASE_COMMIT nor "BASE_COMMIT-<anything>" -- a stronger
|
||||
# failure than "wrong fingerprint" (the base commit id itself is wrong).
|
||||
extract_fingerprint() {
|
||||
local commit_field="$1" base="$2" prefixed
|
||||
if [ "$commit_field" = "$base" ]; then
|
||||
printf ''
|
||||
return 0
|
||||
fi
|
||||
prefixed="${base}-"
|
||||
if [ "${commit_field#"$prefixed"}" != "$commit_field" ]; then
|
||||
printf '%s' "${commit_field#"$prefixed"}"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# has_dirty_substring RAW -> return 0 (true) iff the literal substring
|
||||
# "-dirty" appears anywhere in RAW.
|
||||
has_dirty_substring() {
|
||||
case "$1" in
|
||||
*-dirty*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── the single validating function every real check AND the demonstration
|
||||
# below both exercise (mirrors check_result/check_consistency in the
|
||||
# build-time-versioning sibling scripts) ─────────────────────────────────────
|
||||
#
|
||||
# check_capture LABEL RAW BASE_COMMIT MODE(clean|dirty)
|
||||
# On success: prints a PASS line, sets global FP_OUT to the extracted
|
||||
# fingerprint (empty for "clean"), returns 0.
|
||||
# On failure: prints a FAIL line with a clear reason, returns 1. FP_OUT is
|
||||
# reset to "" first so a stale value can never leak past a failure.
|
||||
FP_OUT=""
|
||||
check_capture() {
|
||||
local label="$1" raw="$2" base="$3" mode="$4"
|
||||
local commit_field fp
|
||||
FP_OUT=""
|
||||
|
||||
if has_dirty_substring "$raw"; then
|
||||
red "FAIL [$label]: raw --version output contains a literal '-dirty' substring -- this must never reappear once a fingerprint is in play. Raw: [$raw]"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! commit_field="$(extract_commit_id "$raw")"; then
|
||||
red "FAIL [$label]: --version output did not contain the expected prefix '$VERSION_PREFIX'. Raw: [$raw]"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! fp="$(extract_fingerprint "$commit_field" "$base")"; then
|
||||
red "FAIL [$label]: commit field '$commit_field' does not match expected base commit '$base', with or without a fingerprint suffix. Raw: [$raw]"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$mode" = "clean" ] && [ -n "$fp" ]; then
|
||||
red "FAIL [$label]: expected the bare commit id with NO suffix (clean tree, Requirement 2.1), but got fingerprint suffix '$fp'. Raw: [$raw]"
|
||||
return 1
|
||||
fi
|
||||
if [ "$mode" = "dirty" ] && [ -z "$fp" ]; then
|
||||
red "FAIL [$label]: expected a fingerprint suffix (dirty tree, Requirement 1.1), but --version showed the bare commit id with no suffix at all. Raw: [$raw]"
|
||||
return 1
|
||||
fi
|
||||
|
||||
FP_OUT="$fp"
|
||||
green "PASS [$label]: commit field='$commit_field' fingerprint='${fp:-<none>}'"
|
||||
return 0
|
||||
}
|
||||
|
||||
# assert_fp_differs / assert_fp_matches -- the discriminating comparisons
|
||||
# Requirement 1.2/1.3 require. Print their own PASS/FAIL, return 0/1.
|
||||
assert_fp_differs() {
|
||||
local label="$1" fp_a="$2" fp_b="$3"
|
||||
if [ "$fp_a" = "$fp_b" ]; then
|
||||
red "FAIL [$label]: fingerprints are IDENTICAL ('$fp_a') but were expected to differ (different uncommitted content, Requirement 1.2)."
|
||||
return 1
|
||||
fi
|
||||
green "PASS [$label]: fingerprints differ ('$fp_a' vs '$fp_b'), as required for different content."
|
||||
return 0
|
||||
}
|
||||
assert_fp_matches() {
|
||||
local label="$1" fp_a="$2" fp_b="$3"
|
||||
if [ "$fp_a" != "$fp_b" ]; then
|
||||
red "FAIL [$label]: fingerprints DIFFER ('$fp_a' vs '$fp_b') but were expected to match exactly (identical uncommitted content, Requirement 1.3)."
|
||||
return 1
|
||||
fi
|
||||
green "PASS [$label]: fingerprints match exactly ('$fp_a'), as required for identical content."
|
||||
return 0
|
||||
}
|
||||
|
||||
# Proves check_capture/assert_fp_differs/assert_fp_matches -- the EXACT
|
||||
# functions the real flow below uses -- would themselves have caught a
|
||||
# regression: a wrong base commit, a resurrected literal "-dirty"
|
||||
# substring, a missing/unexpected suffix for the given mode, and both
|
||||
# comparison directions (identical-when-should-differ,
|
||||
# different-when-should-match), plus a sanity check that genuine real
|
||||
# values still pass.
|
||||
demonstrate_detection_capability() {
|
||||
local base="$1" real_fp_a="$2" real_fp_b="$3"
|
||||
|
||||
yellow "=== DEMONSTRATION: proving the comparison logic catches every required failure mode ==="
|
||||
|
||||
yellow "--- (a) wrong base commit id (prefix/format fine, base mismatched) ---"
|
||||
if check_capture "DEMO-wrong-base" "${VERSION_PREFIX}deadbeef built 2026-01-01T00:00:00Z" "notdeadbeef" "clean"; then
|
||||
red "DEMONSTRATION FAILED: incorrectly PASSED against a mismatched base commit."
|
||||
exit 1
|
||||
fi
|
||||
green "Confirmed: a mismatched base commit id is correctly rejected."
|
||||
|
||||
yellow "--- (b) a literal '-dirty' substring resurfacing despite a valid prefix/base ---"
|
||||
if check_capture "DEMO-dirty-substring" "${VERSION_PREFIX}${base}-dirty built 2026-01-01T00:00:00Z" "$base" "clean"; then
|
||||
red "DEMONSTRATION FAILED: incorrectly PASSED output containing a literal '-dirty' substring."
|
||||
exit 1
|
||||
fi
|
||||
green "Confirmed: a resurfaced literal '-dirty' substring is correctly rejected."
|
||||
|
||||
yellow "--- (c) dirty mode expects a fingerprint suffix; synthetic output has none ---"
|
||||
if check_capture "DEMO-dirty-no-suffix" "${VERSION_PREFIX}${base} built 2026-01-01T00:00:00Z" "$base" "dirty"; then
|
||||
red "DEMONSTRATION FAILED: incorrectly PASSED a dirty-mode capture with no fingerprint suffix at all."
|
||||
exit 1
|
||||
fi
|
||||
green "Confirmed: a missing fingerprint suffix where one is required is correctly rejected."
|
||||
|
||||
yellow "--- (d) clean mode forbids a fingerprint suffix; synthetic output has one ---"
|
||||
if check_capture "DEMO-clean-has-suffix" "${VERSION_PREFIX}${base}-abcd1234 built 2026-01-01T00:00:00Z" "$base" "clean"; then
|
||||
red "DEMONSTRATION FAILED: incorrectly PASSED a clean-mode capture that carries a fingerprint suffix."
|
||||
exit 1
|
||||
fi
|
||||
green "Confirmed: an unexpected fingerprint suffix on a clean-mode capture is correctly rejected."
|
||||
|
||||
yellow "--- (e) sanity: genuinely well-formed captures must still PASS ---"
|
||||
if ! check_capture "DEMO-sanity-clean" "${VERSION_PREFIX}${base} built 2026-01-01T00:00:00Z" "$base" "clean"; then
|
||||
red "DEMONSTRATION FAILED: incorrectly FAILED a genuinely clean, well-formed capture."
|
||||
exit 1
|
||||
fi
|
||||
if ! check_capture "DEMO-sanity-dirty" "${VERSION_PREFIX}${base}-${real_fp_a} built 2026-01-01T00:00:00Z" "$base" "dirty"; then
|
||||
red "DEMONSTRATION FAILED: incorrectly FAILED a genuinely dirty, well-formed capture."
|
||||
exit 1
|
||||
fi
|
||||
green "Confirmed: genuinely well-formed captures pass in both modes."
|
||||
|
||||
yellow "--- (f) differs-check / matches-check discriminating power ---"
|
||||
if assert_fp_differs "DEMO-differs-on-identical" "$real_fp_a" "$real_fp_a"; then
|
||||
red "DEMONSTRATION FAILED: differs-check incorrectly PASSED on two identical fingerprint strings."
|
||||
exit 1
|
||||
fi
|
||||
green "Confirmed: the differs-check correctly rejects two identical fingerprints."
|
||||
|
||||
if assert_fp_matches "DEMO-matches-on-different" "$real_fp_a" "$real_fp_b"; then
|
||||
red "DEMONSTRATION FAILED: matches-check incorrectly PASSED on two different fingerprint strings."
|
||||
exit 1
|
||||
fi
|
||||
green "Confirmed: the matches-check correctly rejects two different fingerprints."
|
||||
|
||||
if ! assert_fp_differs "DEMO-sanity-differs" "$real_fp_a" "$real_fp_b"; then
|
||||
red "DEMONSTRATION FAILED: differs-check incorrectly FAILED on genuinely different real fingerprints ('$real_fp_a' vs '$real_fp_b')."
|
||||
exit 1
|
||||
fi
|
||||
if ! assert_fp_matches "DEMO-sanity-matches" "$real_fp_a" "$real_fp_a"; then
|
||||
red "DEMONSTRATION FAILED: matches-check incorrectly FAILED on two copies of the same real fingerprint."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
green "=== DEMONSTRATION complete: wrong base commit / resurfaced '-dirty' / missing or unexpected suffix / identical-when-should-differ / different-when-should-match are ALL rejected; genuine matches are accepted ==="
|
||||
}
|
||||
|
||||
# ── build helper (mirrors run_build() in test-build-version-regression.sh;
|
||||
# debug profile never runs a bundled test suite, but the same defensive
|
||||
# non-abort-on-nonzero-exit + binary-existence-check pattern is kept for
|
||||
# consistency and because a genuine compile failure must still be caught) ──
|
||||
run_build() {
|
||||
local iso_src="$1" incremental_flag="$2" rc=0
|
||||
if [ "$incremental_flag" = 1 ]; then
|
||||
yellow "=== nix develop $iso_src --command bash $iso_src/tools/xce-build.sh scheduler debug $BUILD_DIR_NAME --incremental ==="
|
||||
nix develop "$iso_src" --command bash "$iso_src/tools/xce-build.sh" scheduler debug "$BUILD_DIR_NAME" --incremental || rc=$?
|
||||
else
|
||||
yellow "=== nix develop $iso_src --command bash $iso_src/tools/xce-build.sh scheduler debug $BUILD_DIR_NAME ==="
|
||||
nix develop "$iso_src" --command bash "$iso_src/tools/xce-build.sh" scheduler debug "$BUILD_DIR_NAME" || rc=$?
|
||||
fi
|
||||
if [ "$rc" != 0 ]; then
|
||||
yellow "WARNING: xce-build.sh exited non-zero (rc=$rc). Proceeding to check whether the binary was still produced -- the binary-existence check right after this call is the authoritative compile-failure signal."
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
capture_version() {
|
||||
# NOTE: iso_src and binary are deliberately two separate `local`
|
||||
# statements, not one combined `local a=... b="$a/..."` -- bash expands
|
||||
# every word of a single `local` command before any of that same
|
||||
# command's assignments take effect, so a same-statement cross-
|
||||
# reference like that would silently see the OLD (unset) value of
|
||||
# iso_src under `set -u`. Found live while first running this script.
|
||||
local iso_src="$1"
|
||||
local binary="$iso_src/$BUILD_DIR_NAME/scheduler/xce_scheduler"
|
||||
if [ ! -x "$binary" ]; then
|
||||
red "FATAL: expected binary not found/executable at $binary after a successful-looking build."
|
||||
exit 1
|
||||
fi
|
||||
"$binary" --version 2>&1
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# Set up the isolated worktree
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
ISO_DIR="$(mktemp -u "${TMPDIR:-/tmp}/xce-dirty-fp-worktree.XXXXXX")"
|
||||
yellow "=== Creating isolated git worktree at HEAD (detached), immune to ambient concurrent edits in $WORKTREE: $ISO_DIR ==="
|
||||
git -C "$WORKTREE" worktree add --detach "$ISO_DIR" HEAD >/dev/null
|
||||
ISO_SRC="$ISO_DIR/src"
|
||||
|
||||
if [ ! -f "$ISO_SRC/tools/xce-build.sh" ]; then
|
||||
red "FATAL: isolated worktree does not contain $ISO_SRC/tools/xce-build.sh -- unexpected checkout layout."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
untracked_at_start="$(git -C "$ISO_DIR" status --porcelain)"
|
||||
if [ -n "$untracked_at_start" ]; then
|
||||
red "FATAL: freshly-created isolated worktree is not clean at creation time -- this should be impossible for a fresh 'git worktree add'. Status:"
|
||||
printf '%s\n' "$untracked_at_start" >&2
|
||||
exit 1
|
||||
fi
|
||||
base_commit="$(git -C "$ISO_DIR" rev-parse --short HEAD)"
|
||||
yellow "Isolated worktree base commit (clean, by construction): $base_commit"
|
||||
|
||||
readme_path="$ISO_DIR/$README_REL"
|
||||
if [ ! -f "$readme_path" ]; then
|
||||
red "FATAL: expected $readme_path to exist -- pick a different safe edit target if this repo's layout changed."
|
||||
exit 1
|
||||
fi
|
||||
readme_original="$(cat "$readme_path")"
|
||||
|
||||
overall_pass=1
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# State 1: clean tree -- commit id alone, no suffix at all (Requirement 2.1)
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
run_build "$ISO_SRC" 0
|
||||
raw_clean="$(capture_version "$ISO_SRC")"
|
||||
yellow "[clean] --version raw output: [$raw_clean]"
|
||||
check_capture "clean" "$raw_clean" "$base_commit" "clean" || overall_pass=0
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# State 2: edit A -- fingerprint suffix present, never "-dirty" (Req 1.1)
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
printf '\n%s\n' "$MARKER_A" >> "$readme_path"
|
||||
run_build "$ISO_SRC" 1
|
||||
raw_a="$(capture_version "$ISO_SRC")"
|
||||
yellow "[dirty-A] --version raw output: [$raw_a]"
|
||||
fp_a=""
|
||||
if check_capture "dirty-A" "$raw_a" "$base_commit" "dirty"; then
|
||||
fp_a="$FP_OUT"
|
||||
else
|
||||
overall_pass=0
|
||||
fi
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# State 3: edit B, DIFFERENT content -- a DIFFERENT fingerprint (Req 1.2)
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
git -C "$ISO_DIR" checkout -- "$README_REL"
|
||||
printf '\n%s\n' "$MARKER_B" >> "$readme_path"
|
||||
run_build "$ISO_SRC" 1
|
||||
raw_b="$(capture_version "$ISO_SRC")"
|
||||
yellow "[dirty-B] --version raw output: [$raw_b]"
|
||||
fp_b=""
|
||||
if check_capture "dirty-B" "$raw_b" "$base_commit" "dirty"; then
|
||||
fp_b="$FP_OUT"
|
||||
else
|
||||
overall_pass=0
|
||||
fi
|
||||
if [ -n "$fp_a" ] && [ -n "$fp_b" ]; then
|
||||
assert_fp_differs "dirty-B-vs-A" "$fp_a" "$fp_b" || overall_pass=0
|
||||
fi
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# State 4: revert to A's exact content -- fingerprint matches A exactly
|
||||
# (Requirement 1.3)
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
git -C "$ISO_DIR" checkout -- "$README_REL"
|
||||
printf '\n%s\n' "$MARKER_A" >> "$readme_path"
|
||||
run_build "$ISO_SRC" 1
|
||||
raw_revert="$(capture_version "$ISO_SRC")"
|
||||
yellow "[reverted-to-A] --version raw output: [$raw_revert]"
|
||||
fp_revert=""
|
||||
if check_capture "reverted-to-A" "$raw_revert" "$base_commit" "dirty"; then
|
||||
fp_revert="$FP_OUT"
|
||||
else
|
||||
overall_pass=0
|
||||
fi
|
||||
if [ -n "$fp_a" ] && [ -n "$fp_revert" ]; then
|
||||
assert_fp_matches "reverted-vs-A" "$fp_a" "$fp_revert" || overall_pass=0
|
||||
fi
|
||||
|
||||
# ── restore the isolated worktree's README.md before the worktree is torn
|
||||
# down (tidiness only -- the whole directory is about to be removed either
|
||||
# way, but this keeps a --keep-worktree run's `git status` honest too) ──────
|
||||
printf '%s' "$readme_original" > "$readme_path"
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# Discrimination demonstration
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
if [ -n "$fp_a" ] && [ -n "$fp_b" ]; then
|
||||
demonstrate_detection_capability "$base_commit" "$fp_a" "$fp_b"
|
||||
else
|
||||
red "FATAL: could not obtain two real, distinct fingerprints from the actual build sequence above -- cannot run the demonstration. This itself means the real sequence already failed."
|
||||
overall_pass=0
|
||||
fi
|
||||
|
||||
# ── constraint check: the SHARED working tree's own README.md must be
|
||||
# completely untouched by this run (all edits above happened only inside
|
||||
# the throwaway isolated worktree) ──────────────────────────────────────────
|
||||
shared_readme_diff="$(git -C "$WORKTREE" status --porcelain -- "$README_REL")"
|
||||
if [ -n "$shared_readme_diff" ]; then
|
||||
red "FAIL: the SHARED working tree's own $README_REL was modified by this run -- it must never be touched (all edits must land only in the isolated worktree). git status: [$shared_readme_diff]"
|
||||
overall_pass=0
|
||||
else
|
||||
green "PASS: shared working tree's own $README_REL is untouched (git status --porcelain empty)."
|
||||
fi
|
||||
|
||||
if [ "$overall_pass" = 1 ]; then
|
||||
green "=== ALL CHECKS PASS: clean tree shows the commit id alone; dirty-A carries a fingerprint (never '-dirty'); dirty-B's fingerprint differs from A's; reverting to A's exact content reproduces A's fingerprint exactly ==="
|
||||
exit 0
|
||||
else
|
||||
red "=== REGRESSION DETECTED: see FAIL line(s) above ==="
|
||||
exit 1
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue