9a164462e7
gates / consistency-and-conventions (push) Successful in 33s
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)
175 lines
9.8 KiB
Bash
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"
|
|
}
|