diff --git a/tools/install-state-machine.test.sh b/tools/install-state-machine.test.sh index ae5dde26..c0dd5867 100755 --- a/tools/install-state-machine.test.sh +++ b/tools/install-state-machine.test.sh @@ -11,6 +11,22 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" TMP="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-install-state-test.XXXXXX")" trap 'rm -rf "$TMP"' EXIT failures=0 +COMPAT_BIN="$TMP/compat-bin" +mkdir -p "$COMPAT_BIN" +cat > "$COMPAT_BIN/realpath" <<'REALPATH' +#!/usr/bin/env python3 +import os +import sys + +args = sys.argv[1:] +mode = args.pop(0) if args and args[0] in ("-e", "-m") else "-m" +if args and args[0] == "--": + args.pop(0) +if len(args) != 1 or (mode == "-e" and not os.path.exists(args[0])): + raise SystemExit(1) +print(os.path.realpath(args[0])) +REALPATH +chmod 0755 "$COMPAT_BIN/realpath" fail_case() { printf '[test] FAIL: %s\n' "$*" >&2; failures=$((failures + 1)); } pass_case() { printf '[test] PASS: %s\n' "$*"; } @@ -18,11 +34,28 @@ pass_case() { printf '[test] PASS: %s\n' "$*"; } fingerprint() { local dir="$1" if [[ ! -d "$dir" ]]; then printf 'ABSENT\n'; return; fi - ( - cd "$dir" || exit 1 - find . -mindepth 1 -printf '%P|%y|%m|%u|%g|%l\n' | LC_ALL=C sort - find . -type f -print0 | LC_ALL=C sort -z | xargs -0 -r sha256sum - ) | sha256sum | awk '{print $1}' + python3 - "$dir" <<'PY' +import hashlib +import os +import stat +import sys + +root = os.path.abspath(sys.argv[1]) +rows = [] +for current, dirs, files in os.walk(root, topdown=True, followlinks=False): + for name in dirs + files: + path = os.path.join(current, name) + rel = os.path.relpath(path, root) + meta = os.lstat(path) + target = os.readlink(path) if stat.S_ISLNK(meta.st_mode) else "" + digest = "" + if stat.S_ISREG(meta.st_mode): + with open(path, "rb") as handle: + digest = hashlib.sha256(handle.read()).hexdigest() + rows.append((rel, stat.S_IFMT(meta.st_mode), stat.S_IMODE(meta.st_mode), meta.st_uid, meta.st_gid, target, digest)) +payload = "\n".join("|".join(map(str, row)) for row in sorted(rows)).encode() +print(hashlib.sha256(payload).hexdigest()) +PY } make_fake_npm() { @@ -97,6 +130,30 @@ good_prefix="$good_home/.npm-global" good_mosaic="$good_home/.config/mosaic" mkdir -p "$good_bin" "$good_prefix/bin" "$good_mosaic/skills/declared-skill" make_fake_npm "$good_bin" +cp "$COMPAT_BIN/realpath" "$good_bin/realpath" +cat > "$good_bin/id" <<'ID' +#!/bin/bash +case "${1:-}" in + -u) echo 1001 ;; + -g) echo 1001 ;; + -un) echo fixture-user ;; + *) exec /bin/id "$@" ;; +esac +ID +cat > "$good_bin/stat" <<'STAT' +#!/bin/bash +if [[ "${1:-} ${2:-}" == '-c %u' ]]; then echo 1001; exit 0; fi +exec /bin/stat "$@" +STAT +cat > "$good_bin/curl" <<'CURL' +#!/bin/bash +exit 0 +CURL +cat > "$good_bin/ldd" <<'LDD' +#!/bin/bash +echo 'ldd (GNU libc) 2.36' +LDD +chmod 0755 "$good_bin/id" "$good_bin/stat" "$good_bin/curl" "$good_bin/ldd" cat > "$good_prefix/bin/mosaic" <<'CLI' #!/usr/bin/env bash printf '0.0.50-next.999\n' @@ -175,7 +232,7 @@ for phase in P2 P3 P4 P5 P6 P7 P8; do set +e HOME="$home" MOSAIC_HOME="$home/.config/mosaic" MOSAIC_PREFIX="$home/.npm-global" \ MOSAIC_INSTALL_STATE_DIR="$state" MOSAIC_INSTALL_FAULT_AFTER="$phase" \ - MOSAIC_NO_COLOR=1 bash "$ROOT/tools/install.sh" --state-machine-self-test \ + MOSAIC_NO_COLOR=1 PATH="$COMPAT_BIN:$PATH" bash "$ROOT/tools/install.sh" --state-machine-self-test \ >"$TMP/fault-$phase.log" 2>&1 status=$? set -e @@ -205,7 +262,7 @@ for case_name in root-target home-target overlap-target; do before="$(fingerprint "$unsafe_home")" set +e HOME="$unsafe_home" MOSAIC_HOME="$unsafe_mosaic" MOSAIC_PREFIX="$unsafe_prefix" \ - MOSAIC_NO_COLOR=1 PATH="$check_bin:/usr/local/bin:/usr/bin:/bin" \ + MOSAIC_NO_COLOR=1 PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" \ bash "$ROOT/tools/install.sh" --check --next >"$TMP/$case_name.log" 2>&1 status=$? set -e @@ -222,7 +279,7 @@ mkdir -p "$symlink_home" "$symlink_outside" ln -s "$symlink_outside" "$symlink_home/.config" set +e HOME="$symlink_home" MOSAIC_HOME="$symlink_home/.config/mosaic" MOSAIC_PREFIX="$symlink_home/.npm-global" \ - MOSAIC_NO_COLOR=1 PATH="$check_bin:/usr/local/bin:/usr/bin:/bin" \ + MOSAIC_NO_COLOR=1 PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" \ bash "$ROOT/tools/install.sh" --check --next >"$TMP/symlink-target.log" 2>&1 status=$? set -e @@ -240,7 +297,7 @@ printf '{"status":"in-progress","journal":"%s"}\n' "$stale_state/dead-run/journa set +e HOME="$stale_home" MOSAIC_HOME="$stale_home/.config/mosaic" MOSAIC_PREFIX="$stale_home/.npm-global" \ MOSAIC_INSTALL_STATE_DIR="$stale_state" MOSAIC_INSTALL_FAULT_AFTER=P2 MOSAIC_NO_COLOR=1 \ - bash "$ROOT/tools/install.sh" --state-machine-self-test >"$TMP/stale.log" 2>&1 + PATH="$COMPAT_BIN:$PATH" bash "$ROOT/tools/install.sh" --state-machine-self-test >"$TMP/stale.log" 2>&1 status=$? set -e [[ "$status" -eq 97 ]] || fail_case "stale projection recovery expected injected status 97, got $status"