Files
Linux_post_install/tests/t-install-version.sh
T
Your Name 9a164462e7
gates / consistency-and-conventions (push) Successful in 33s
feat: install.sh version gate — --force + per-commit 0.0cN
Architect-approved (C): install.sh now skips+aborts (exit 0) when the
installed version equals the current git-derived version, with a --force
flag to re-install. Version scheme 0.0cN (N = git commit count) bumps by
construction on every commit.

- install_version(): derives 0.0c$count via git rev-list; empty when .git
  absent (gate skipped); INSTALL_VERSION_OVERRIDE presence-check seam for
  tests (empty override simulates no-git deterministically)
- Gate after arg parse, before phases; numeric comparison (strip 0.0c, -eq);
  messages: 'Already installed (X). Use --force to re-install.' and
  '(dry-run) Would skip install: already at version X', both exit 0
- flag_set installed_version after 'Bootstrap complete' banner (not in
  dry-run, even under --force, never on phase failure)
- Docs: SCRIPTS.md flag table + gate description, AGENT_Context line count
  248->301 + flags + flow diagram, README --force row, tests/README row,
  AGENT_TODO Done entry

Verified: new tests/t-install-version.sh 21 checks (9 contracted cases,
real install.sh + hermetic env seams); suite 20 files / 461 checks /
0 fail / 0 skip; make gen byte-idempotent; make check OK; make lint
0 FAIL, 0 WARN; bash -n clean; git diff --check clean; Reviewer
APPROVE_WITH_NOTES with 3 mutation disproofs (8/21, 9/21, 4/21 fail)
2026-09-08 10:17:31 -04:00

175 lines
9.8 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
# t-install-version.sh — install.sh version gate contract:
# match→skip with "Already installed", mismatch→proceed, --force bypass,
# no-git (empty) skip, dry-run variant, flag write after run,
# numeric comparison correctness (0.0c10 > 0.0c9).
#
# Seam: INSTALL_VERSION_OVERRIDE env var (presence check → empty = no-git path),
# FLAGS_DIR env var (existing flag store seam).
#
# Proceed-path cases (2/3/4/5/8) use --skip all phases instead of --dry-run
# because postinstall.sh is not fully dry-run safe (cat on non-existent file
# after dry-run copy). Skipping all phases is functionally equivalent — the
# gate fires before phases, and the banner + flag write still execute.
run_test() {
local sandbox stubs flags_dir install_sh
sandbox="$(mksandbox install-version)"
stubs="$sandbox/stubs"
flags_dir="$sandbox/flags"
mkdir -p "$stubs" "$flags_dir"
install_sh="$ROOT/install.sh"
# sudo stub: just exec its arguments (bypasses real sudo in sandbox)
cat > "$stubs/sudo" <<'STUB'
#!/usr/bin/env bash
exec "$@"
STUB
chmod +x "$stubs/sudo"
# install stub: delegate to the real install binary (needed so -d/-m flags
# work; we only shadow 'sudo' to avoid needing a tty/password)
cat > "$stubs/install" <<'STUB'
#!/usr/bin/env bash
exec /usr/bin/install "$@"
STUB
chmod +x "$stubs/install"
# mkdir stub: just exec its arguments
cat > "$stubs/mkdir" <<'STUB'
#!/usr/bin/env bash
exec "$@"
STUB
chmod +x "$stubs/mkdir"
local base_env=(PATH="$stubs:/usr/bin:/bin"
HOME="$sandbox/home"
FLAGS_DIR="$flags_dir")
# Helper: run install.sh with env overrides and capture output+rc.
run_install() {
local envs=() args=()
while [ $# -gt 0 ]; do
if [ "$1" = "--" ]; then shift; break; fi
envs+=("$1"); shift
done
args=("$@")
set +e
TR_OUT="$(env "${base_env[@]}" "${envs[@]}" bash "$install_sh" "${args[@]}" 2>&1)"
TR_RC=$?
set -e
}
# Helper: set a flag value directly (for pre-seeding installed_version)
set_flag() {
local name="$1" value="$2"
mkdir -p "$flags_dir"
printf '%s' "$value" > "$flags_dir/$name"
}
# Helper: read a flag value
get_flag() {
local name="$1"
local f="$flags_dir/$name"
[ -f "$f" ] && cat "$f" || printf ''
}
# ═══════════════════════════════════════════════════════════════
# Case 1: Match → skip ("Already installed")
# Gate exits BEFORE any phase — safe without --dry-run or --skip.
# ═══════════════════════════════════════════════════════════════
set_flag "installed_version" "0.0c10"
run_install INSTALL_VERSION_OVERRIDE=0.0c10
check_rc "C1 match→skip exit code" 0 "$TR_RC"
check_contains "C1 match→skip message" "Already installed (0.0c10)" "$TR_OUT"
# ═══════════════════════════════════════════════════════════════
# Case 2: Mismatch → proceed (gate doesn't match, phases skipped)
# ═══════════════════════════════════════════════════════════════
rm -f "$flags_dir/installed_version"
set_flag "installed_version" "0.0c10"
run_install INSTALL_VERSION_OVERRIDE=0.0c11 -- --skip preinstall,scripts,postinstall,scalepoint
check_rc "C2 mismatch→proceed exit code" 0 "$TR_RC"
check_not_contains "C2 mismatch does not skip" "Already installed" "$TR_OUT"
# ═══════════════════════════════════════════════════════════════
# Case 3: Force bypasses match
# ═══════════════════════════════════════════════════════════════
rm -f "$flags_dir/installed_version"
set_flag "installed_version" "0.0c10"
run_install INSTALL_VERSION_OVERRIDE=0.0c10 -- --skip preinstall,scripts,postinstall,scalepoint --force
check_rc "C3 force bypasses exit code" 0 "$TR_RC"
check_not_contains "C3 force bypasses skip" "Already installed" "$TR_OUT"
# ═══════════════════════════════════════════════════════════════
# Case 4: No git (empty version via presence check)
# INSTALL_VERSION_OVERRIDE="" with ${var+x} → empty → gate skipped
# ═══════════════════════════════════════════════════════════════
rm -f "$flags_dir/installed_version"
set_flag "installed_version" "0.0c10"
run_install INSTALL_VERSION_OVERRIDE="" -- --skip preinstall,scripts,postinstall,scalepoint
check_rc "C4 empty override→proceeds exit code" 0 "$TR_RC"
check_not_contains "C4 empty override does not skip" "Already installed" "$TR_OUT"
# ═══════════════════════════════════════════════════════════════
# Case 5: No installed flag → gate doesn't block
# ═══════════════════════════════════════════════════════════════
rm -f "$flags_dir/installed_version"
run_install INSTALL_VERSION_OVERRIDE=0.0c10 -- --skip preinstall,scripts,postinstall,scalepoint
check_rc "C5 no flag→proceeds exit code" 0 "$TR_RC"
check_not_contains "C5 no flag does not skip" "Already installed" "$TR_OUT"
# ═══════════════════════════════════════════════════════════════
# Case 6: Dry-run + match → "Would skip" (not "Already installed")
# ═══════════════════════════════════════════════════════════════
rm -f "$flags_dir/installed_version"
set_flag "installed_version" "0.0c10"
run_install INSTALL_VERSION_OVERRIDE=0.0c10 -- --dry-run
check_rc "C6 dry-run match exit code" 0 "$TR_RC"
check_contains "C6 dry-run shows Would skip" "Would skip install" "$TR_OUT"
check_not_contains "C6 dry-run not Already installed" "Already installed" "$TR_OUT"
# ═══════════════════════════════════════════════════════════════
# Case 7: Flag written after successful run
# All phases skipped; sudo/install/mkdir stubs on PATH so
# flag_set's `run sudo install` actually creates the flag file.
# ═══════════════════════════════════════════════════════════════
rm -f "$flags_dir/installed_version"
set_flag "installed_version" "0.0c10"
run_install INSTALL_VERSION_OVERRIDE=0.0c11 -- --skip preinstall,scripts,postinstall,scalepoint
check_rc "C7 all-skip exit code" 0 "$TR_RC"
local flag_val
flag_val="$(get_flag installed_version)"
check_eq "C7 flag written with current version" "0.0c11" "$flag_val"
# ═══════════════════════════════════════════════════════════════
# Case 8: Numeric comparison — 0.0c10 > 0.0c9 (not blocked)
# ═══════════════════════════════════════════════════════════════
rm -f "$flags_dir/installed_version"
set_flag "installed_version" "0.0c9"
run_install INSTALL_VERSION_OVERRIDE=0.0c10 -- --skip preinstall,scripts,postinstall,scalepoint
check_rc "C8 numeric 10>9 proceeds exit code" 0 "$TR_RC"
check_not_contains "C8 numeric 10>9 does not skip" "Already installed" "$TR_OUT"
# Also test: 0.0c9 with installed 0.0c10 → NOT a match
rm -f "$flags_dir/installed_version"
set_flag "installed_version" "0.0c10"
run_install INSTALL_VERSION_OVERRIDE=0.0c9 -- --skip preinstall,scripts,postinstall,scalepoint
check_rc "C8 numeric 9≠10 proceeds exit code" 0 "$TR_RC"
check_not_contains "C8 numeric 9≠10 does not skip" "Already installed" "$TR_OUT"
# ═══════════════════════════════════════════════════════════════
# Case 9: Force + match → still writes flag
# ═══════════════════════════════════════════════════════════════
rm -f "$flags_dir/installed_version"
set_flag "installed_version" "0.0c10"
run_install INSTALL_VERSION_OVERRIDE=0.0c10 -- --skip preinstall,scripts,postinstall,scalepoint --force
check_rc "C9 force+match exit code" 0 "$TR_RC"
local flag_val9
flag_val9="$(get_flag installed_version)"
check_eq "C9 force writes flag" "0.0c10" "$flag_val9"
}