429 lines
19 KiB
Bash
Executable File
429 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Red-first acceptance checks for #1050. This file is committed before the
|
|
# installer implementation. Do not weaken these properties to make it green.
|
|
|
|
# pass_case always returns zero and fail_case records the aggregate failure;
|
|
# the compact A&&pass||fail assertions are intentional.
|
|
# shellcheck disable=SC2015
|
|
set -uo pipefail
|
|
|
|
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' "$*"; }
|
|
|
|
fingerprint() {
|
|
local dir="$1"
|
|
if [[ ! -d "$dir" ]]; then printf 'ABSENT\n'; return; fi
|
|
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() {
|
|
local bin="$1"
|
|
mkdir -p "$bin"
|
|
cat > "$bin/npm" <<'FAKE'
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
if [[ "${1:-}" == "--version" ]]; then echo '10.6.2'; exit 0; fi
|
|
case "${1:-} ${2:-} ${3:-}" in
|
|
'view @mosaicstack/mosaic@next version') echo '0.0.50-next.999' ;;
|
|
'view @mosaicstack/gateway@next version') echo '0.0.7-next.999' ;;
|
|
'view @mosaicstack/mosaic version') echo '0.0.49' ;;
|
|
'ls -g --depth=0'|'ls -g --json') echo '{"dependencies":{"@mosaicstack/mosaic":{"version":"0.0.50-next.999"},"@mosaicstack/gateway":{"version":"0.0.7-next.999"}}}' ;;
|
|
ls*) echo '{"dependencies":{"@mosaicstack/mosaic":{"version":"0.0.50-next.999"},"@mosaicstack/gateway":{"version":"0.0.7-next.999"}}}' ;;
|
|
*) echo "unexpected fake npm command: $*" >&2; exit 1 ;;
|
|
esac
|
|
FAKE
|
|
chmod 0755 "$bin/npm"
|
|
}
|
|
|
|
printf '[test] case: --check enumerates exactly P0-P8, discriminates, and mutates nothing\n'
|
|
check_home="$TMP/check-home"
|
|
check_bin="$TMP/check-bin"
|
|
mkdir -p "$check_home/.config/mosaic/skills/alpha" "$check_home/.npm-global/bin" "$check_bin"
|
|
printf '# framework\n' > "$check_home/.config/mosaic/AGENTS.md"
|
|
printf '# skill\n' > "$check_home/.config/mosaic/skills/alpha/SKILL.md"
|
|
cat > "$check_home/.npm-global/bin/mosaic" <<'CLI'
|
|
#!/usr/bin/env bash
|
|
printf '0.0.50-next.999\n'
|
|
CLI
|
|
chmod 0755 "$check_home/.npm-global/bin/mosaic"
|
|
make_fake_npm "$check_bin"
|
|
before="$(fingerprint "$check_home")"
|
|
set +e
|
|
HOME="$check_home" MOSAIC_HOME="$check_home/.config/mosaic" MOSAIC_PREFIX="$check_home/.npm-global" \
|
|
MOSAIC_NO_COLOR=1 PATH="$check_bin:/usr/local/bin:/usr/bin:/bin" \
|
|
bash "$ROOT/tools/install.sh" --check --next >"$TMP/check.log" 2>&1
|
|
check_status=$?
|
|
set -e
|
|
after="$(fingerprint "$check_home")"
|
|
|
|
[[ "$before" == "$after" ]] && pass_case '--check left the complete HOME fingerprint unchanged' \
|
|
|| fail_case "--check mutated HOME (before=$before after=$after)"
|
|
[[ "$check_status" -ne 0 ]] && pass_case '--check exited non-zero for failed P4/P5/P8 predicates' \
|
|
|| fail_case '--check returned zero on the deliberately broken host'
|
|
|
|
phase_rows=0
|
|
for phase in P0 P1 P2 P3 P4 P5 P6 P7 P8; do
|
|
count="$(grep -Ec "^\[$phase\] (PASS|FAIL):" "$TMP/check.log" || true)"
|
|
[[ "$count" -eq 1 ]] || fail_case "$phase expected exactly one PASS/FAIL row, got $count"
|
|
phase_rows=$((phase_rows + count))
|
|
done
|
|
[[ "$phase_rows" -eq 9 ]] && pass_case '--check emitted exactly nine P0-P8 result rows' \
|
|
|| fail_case "--check emitted $phase_rows canonical rows instead of 9"
|
|
grep -q '^\[P3\] PASS:.*0\.0\.50-next\.999' "$TMP/check.log" \
|
|
&& pass_case 'P3 preserves the absolute-path exact-version discriminator' \
|
|
|| fail_case 'P3 did not PASS with the exact resolved next-lane version'
|
|
grep -q '^\[P4\] FAIL: NOT-MEASURED / UNDECLARED:' "$TMP/check.log" \
|
|
&& pass_case 'P4 refuses fabricated precision when no shipped-set declaration exists' \
|
|
|| fail_case 'P4 did not report the declared-set population as NOT-MEASURED / UNDECLARED'
|
|
for phase in P5 P8; do
|
|
grep -q "^\[$phase\] FAIL:" "$TMP/check.log" \
|
|
&& pass_case "$phase remains an attributable expected RED" \
|
|
|| fail_case "$phase did not report its own expected failure"
|
|
done
|
|
|
|
printf '[test] case: --check discriminates a constructed good host without mutation\n'
|
|
good_home="$TMP/good-home"
|
|
good_bin="$TMP/good-bin"
|
|
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
|
|
uid="${MOSAIC_TEST_UID:-1001}"
|
|
gid="${MOSAIC_TEST_GID:-1001}"
|
|
user="${MOSAIC_TEST_USER:-fixture-user}"
|
|
case "${1:-}" in
|
|
-u) echo "$uid" ;;
|
|
-g) echo "$gid" ;;
|
|
-un) echo "$user" ;;
|
|
*) exec /bin/id "$@" ;;
|
|
esac
|
|
ID
|
|
cat > "$good_bin/stat" <<'STAT'
|
|
#!/bin/bash
|
|
if [[ "${1:-} ${2:-}" == '-c %u' ]]; then
|
|
[[ "${3:-}" == "${MOSAIC_TEST_WRONG_OWNER_PATH:-__none__}" ]] && echo 9999 || echo "${MOSAIC_TEST_UID:-1001}"
|
|
exit 0
|
|
fi
|
|
if [[ "${1:-} ${2:-}" == '-c %g' ]]; then
|
|
[[ "${3:-}" == "${MOSAIC_TEST_WRONG_GROUP_PATH:-__none__}" ]] && echo 9999 || echo "${MOSAIC_TEST_GID:-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'
|
|
CLI
|
|
chmod 0755 "$good_prefix/bin/mosaic"
|
|
cat > "$good_bin/getent" <<GETENT
|
|
#!/bin/bash
|
|
printf '%s:x:%s:%s::%s:%s\\n' "\${MOSAIC_TEST_USER:-fixture-user}" "\${MOSAIC_TEST_UID:-1001}" "\${MOSAIC_TEST_GID:-1001}" "\${MOSAIC_TEST_PASSWD_HOME:-$good_home}" '$good_bin/bash'
|
|
GETENT
|
|
cat > "$good_bin/bash" <<SHELL
|
|
#!/bin/bash
|
|
if [[ "\${*: -1}" == 'command -v mosaic' ]]; then
|
|
printf '%s\\n' '$good_prefix/bin/mosaic'
|
|
exit 0
|
|
fi
|
|
exec /bin/bash "\$@"
|
|
SHELL
|
|
chmod 0755 "$good_bin/getent" "$good_bin/bash"
|
|
printf '# Soul\n\nConfigured.\n' > "$good_mosaic/SOUL.md"
|
|
printf '# User\n\nConfigured.\n' > "$good_mosaic/USER.md"
|
|
chmod 0600 "$good_mosaic/SOUL.md" "$good_mosaic/USER.md"
|
|
cat > "$good_mosaic/skills/declared-skill/SKILL.md" <<'SKILL'
|
|
---
|
|
name: declared-skill
|
|
description: Constructed loadable acceptance skill.
|
|
---
|
|
|
|
# Declared skill
|
|
SKILL
|
|
printf '{"lane":"next","version":"0.0.50-next.999","skills":["declared-skill"]}\n' > "$good_mosaic/.install-shipped-skills.json"
|
|
printf '{\n "lane": "next",\n "cliVersion": "0.0.50-next.999"\n}\n' > "$good_mosaic/.install-manifest.json"
|
|
before="$(fingerprint "$good_home")"
|
|
set +e
|
|
HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" \
|
|
MOSAIC_NO_COLOR=1 PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" \
|
|
bash "$ROOT/tools/install.sh" --check --next >"$TMP/good-check.log" 2>&1
|
|
status=$?
|
|
set -e
|
|
after="$(fingerprint "$good_home")"
|
|
[[ "$status" -eq 0 ]] && pass_case 'good-host --check exited zero' || fail_case "good-host --check exited $status"
|
|
[[ "$before" == "$after" ]] && pass_case 'good-host --check left HOME unchanged' || fail_case 'good-host --check mutated HOME'
|
|
good_rows="$(grep -Ec '^\[P[0-8]\] PASS:' "$TMP/good-check.log" || true)"
|
|
[[ "$good_rows" -eq 9 ]] && pass_case 'good-host --check emitted nine PASS rows' \
|
|
|| { cat "$TMP/good-check.log" >&2; fail_case "good-host --check emitted $good_rows PASS rows"; }
|
|
|
|
printf '[test] case: P0 binds uid, username, passwd HOME, shell, and privilege mode\n'
|
|
passwd_home="$TMP/passwd-authoritative-home"
|
|
mkdir -p "$passwd_home"
|
|
set +e
|
|
HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" \
|
|
MOSAIC_TEST_PASSWD_HOME="$passwd_home" MOSAIC_NO_COLOR=1 \
|
|
PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" \
|
|
bash "$ROOT/tools/install.sh" --check --next >"$TMP/wrong-home.log" 2>&1
|
|
wrong_home_status=$?
|
|
set -e
|
|
[[ "$wrong_home_status" -ne 0 ]] || fail_case 'P0 accepted ambient HOME that disagrees with passwd HOME'
|
|
grep -q '^\[P0\] FAIL:.*HOME mismatch' "$TMP/wrong-home.log" \
|
|
&& pass_case 'P0 rejects ambient HOME that disagrees with passwd HOME' \
|
|
|| fail_case 'P0 did not attribute the passwd HOME mismatch'
|
|
|
|
for privilege_case in root-with-home sudo-with-inherited-home; do
|
|
extra_env=()
|
|
[[ "$privilege_case" == sudo-with-inherited-home ]] && extra_env+=(SUDO_USER=fixture-user SUDO_UID=1001)
|
|
set +e
|
|
env HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" \
|
|
MOSAIC_TEST_UID=0 MOSAIC_TEST_GID=0 MOSAIC_TEST_USER=root MOSAIC_TEST_PASSWD_HOME=/root \
|
|
MOSAIC_NO_COLOR=1 PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" "${extra_env[@]}" \
|
|
bash "$ROOT/tools/install.sh" --check --next >"$TMP/$privilege_case.log" 2>&1
|
|
privilege_status=$?
|
|
set -e
|
|
[[ "$privilege_status" -ne 0 ]] || fail_case "P0 accepted unsafe $privilege_case context"
|
|
grep -q '^\[P0\] FAIL:.*privilege=' "$TMP/$privilege_case.log" \
|
|
&& pass_case "P0 states and rejects $privilege_case privilege context" \
|
|
|| fail_case "P0 did not state $privilege_case privilege mode"
|
|
done
|
|
|
|
printf '[test] case: P3/P5 reject unsafe owner, group, and mode\n'
|
|
chmod 0777 "$good_prefix/bin/mosaic"
|
|
set +e
|
|
HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" MOSAIC_NO_COLOR=1 \
|
|
PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" bash "$ROOT/tools/install.sh" --check --next >"$TMP/p3-mode.log" 2>&1
|
|
p3_mode_status=$?
|
|
set -e
|
|
[[ "$p3_mode_status" -ne 0 ]] || fail_case 'P3 accepted mode-0777 CLI'
|
|
grep -q '^\[P3\] FAIL:.*unsafe owner/group/mode' "$TMP/p3-mode.log" \
|
|
&& pass_case 'P3 rejects group/world-writable CLI' || fail_case 'P3 did not attribute unsafe CLI mode'
|
|
chmod 0755 "$good_prefix/bin/mosaic"
|
|
|
|
for ownership_case in owner group; do
|
|
wrong_env=()
|
|
[[ "$ownership_case" == owner ]] && wrong_env+=(MOSAIC_TEST_WRONG_OWNER_PATH="$good_prefix/bin/mosaic")
|
|
[[ "$ownership_case" == group ]] && wrong_env+=(MOSAIC_TEST_WRONG_GROUP_PATH="$good_prefix/bin/mosaic")
|
|
set +e
|
|
env HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" MOSAIC_NO_COLOR=1 \
|
|
PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" "${wrong_env[@]}" \
|
|
bash "$ROOT/tools/install.sh" --check --next >"$TMP/p3-$ownership_case.log" 2>&1
|
|
owner_status=$?
|
|
set -e
|
|
[[ "$owner_status" -ne 0 ]] || fail_case "P3 accepted wrong CLI $ownership_case"
|
|
grep -q '^\[P3\] FAIL:.*unsafe owner/group/mode' "$TMP/p3-$ownership_case.log" \
|
|
&& pass_case "P3 rejects wrong CLI $ownership_case" || fail_case "P3 did not attribute wrong CLI $ownership_case"
|
|
done
|
|
|
|
chmod 0644 "$good_mosaic/SOUL.md" "$good_mosaic/USER.md"
|
|
set +e
|
|
HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" MOSAIC_NO_COLOR=1 \
|
|
PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" bash "$ROOT/tools/install.sh" --check --next >"$TMP/p5-mode.log" 2>&1
|
|
p5_mode_status=$?
|
|
set -e
|
|
[[ "$p5_mode_status" -ne 0 ]] || fail_case 'P5 accepted world-readable identity files'
|
|
grep -q '^\[P5\] FAIL:' "$TMP/p5-mode.log" \
|
|
&& pass_case 'P5 rejects world-readable identity files' || fail_case 'P5 did not reject identity mode 0644'
|
|
chmod 0600 "$good_mosaic/SOUL.md" "$good_mosaic/USER.md"
|
|
|
|
mkdir -p "$good_mosaic/credentials"
|
|
chmod 0755 "$good_mosaic/credentials"
|
|
set +e
|
|
HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" MOSAIC_NO_COLOR=1 \
|
|
PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" bash "$ROOT/tools/install.sh" --check --next >"$TMP/p5-credentials.log" 2>&1
|
|
credential_status=$?
|
|
set -e
|
|
[[ "$credential_status" -ne 0 ]] || fail_case 'P5 accepted mode-0755 credentials directory'
|
|
grep -q '^\[P5\] FAIL:.*credentials' "$TMP/p5-credentials.log" \
|
|
&& pass_case 'P5 rejects group/world-readable credential storage' \
|
|
|| fail_case 'P5 did not attribute unsafe credential directory mode'
|
|
chmod 0700 "$good_mosaic/credentials"
|
|
|
|
printf '# framework\n' > "$good_mosaic/AGENTS.md"
|
|
chmod 0666 "$good_mosaic/AGENTS.md"
|
|
set +e
|
|
HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" MOSAIC_NO_COLOR=1 \
|
|
PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" bash "$ROOT/tools/install.sh" --check --next >"$TMP/p4-tree-mode.log" 2>&1
|
|
framework_mode_status=$?
|
|
set -e
|
|
[[ "$framework_mode_status" -ne 0 ]] || fail_case 'P4 accepted group/world-writable framework path'
|
|
grep -q '^\[P4\] FAIL:.*owner/mode policy' "$TMP/p4-tree-mode.log" \
|
|
&& pass_case 'P4 inventories and rejects unsafe created framework paths' \
|
|
|| fail_case 'P4 did not attribute unsafe created-path mode'
|
|
chmod 0644 "$good_mosaic/AGENTS.md"
|
|
|
|
printf '[test] case: P4 fails closed when created-path enumeration is incomplete\n'
|
|
real_find="$(command -v find)"
|
|
cat > "$good_bin/find" <<FIND
|
|
#!/bin/bash
|
|
if [[ "\${1:-}" == '$good_mosaic' && "\${2:-}" == '-xdev' && "\${3:-}" == '-print0' ]]; then
|
|
printf '%s\\0' '$good_mosaic'
|
|
exit 73
|
|
fi
|
|
exec '$real_find' "\$@"
|
|
FIND
|
|
chmod 0755 "$good_bin/find"
|
|
printf '# hidden unsafe child\n' > "$good_mosaic/AGENTS.md"
|
|
chmod 0666 "$good_mosaic/AGENTS.md"
|
|
set +e
|
|
HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" MOSAIC_NO_COLOR=1 \
|
|
PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" bash "$ROOT/tools/install.sh" --check --next \
|
|
>"$TMP/p4-enumeration-failure.log" 2>&1
|
|
p4_enumeration_status=$?
|
|
set -e
|
|
rm -f "$good_bin/find"
|
|
[[ "$p4_enumeration_status" -ne 0 ]] \
|
|
&& pass_case 'P4 rejects an incomplete created-path inventory' \
|
|
|| fail_case 'P4 accepted a partial created-path inventory after find failed'
|
|
grep -q '^\[P4\] FAIL:.*enumeration failed' "$TMP/p4-enumeration-failure.log" \
|
|
&& pass_case 'P4 attributes the failed created-path enumeration' \
|
|
|| fail_case 'P4 did not report failed created-path enumeration'
|
|
chmod 0644 "$good_mosaic/AGENTS.md"
|
|
|
|
printf '[test] case: persisted required-action failures remain blocking\n'
|
|
for blocked_phase in P4 P6; do
|
|
node -e '
|
|
const fs=require("fs"); const p=process.argv[1]; const phase=process.argv[2];
|
|
const m=JSON.parse(fs.readFileSync(p,"utf8")); m.phaseOutcomes={P4:"committed",P6:"committed"};
|
|
m.phaseOutcomes[phase]="failed"; fs.writeFileSync(p,JSON.stringify(m)+"\n");
|
|
' "$good_mosaic/.install-manifest.json" "$blocked_phase"
|
|
set +e
|
|
HOME="$good_home" MOSAIC_HOME="$good_mosaic" MOSAIC_PREFIX="$good_prefix" \
|
|
MOSAIC_NO_COLOR=1 PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" \
|
|
bash "$ROOT/tools/install.sh" --check --next >"$TMP/action-$blocked_phase.log" 2>&1
|
|
status=$?
|
|
set -e
|
|
[[ "$status" -ne 0 ]] || fail_case "$blocked_phase action failure returned zero"
|
|
grep -q "^\[$blocked_phase\] FAIL:.*action reported a required $blocked_phase failure" "$TMP/action-$blocked_phase.log" \
|
|
&& pass_case "$blocked_phase action failure remained blocking in a later --check" \
|
|
|| fail_case "$blocked_phase persisted action failure was not attributed"
|
|
done
|
|
printf '{\n "lane": "next",\n "cliVersion": "0.0.50-next.999",\n "phaseOutcomes": {"P4":"committed","P6":"committed"}\n}\n' > "$good_mosaic/.install-manifest.json"
|
|
|
|
printf '[test] case: fault injection has no synthetic mutation implementation\n'
|
|
if grep -q '\.selftest-' "$ROOT/tools/install.sh"; then
|
|
fail_case 'synthetic .selftest mutation path remains in the production fault seam'
|
|
else
|
|
pass_case 'fault seam is attached only to real P2-P8 action flow (exercised by install-next-lane.test.sh)'
|
|
fi
|
|
|
|
printf '[test] case: unsafe and overlapping rollback roots fail before mutation\n'
|
|
unsafe_home="$TMP/unsafe-home"
|
|
mkdir -p "$unsafe_home"
|
|
for case_name in root-target home-target overlap-target; do
|
|
case "$case_name" in
|
|
root-target) unsafe_mosaic=/; unsafe_prefix="$unsafe_home/.npm-global" ;;
|
|
home-target) unsafe_mosaic="$unsafe_home"; unsafe_prefix="$unsafe_home/.npm-global" ;;
|
|
overlap-target) unsafe_mosaic="$unsafe_home/.config"; unsafe_prefix="$unsafe_home/.config/mosaic/prefix" ;;
|
|
esac
|
|
before="$(fingerprint "$unsafe_home")"
|
|
set +e
|
|
HOME="$unsafe_home" MOSAIC_HOME="$unsafe_mosaic" MOSAIC_PREFIX="$unsafe_prefix" \
|
|
MOSAIC_TEST_PASSWD_HOME="$unsafe_home" 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
|
|
after="$(fingerprint "$unsafe_home")"
|
|
[[ "$status" -ne 0 ]] || fail_case "$case_name unsafe path returned zero"
|
|
grep -q '^\[P0\] FAIL:.*unsafe context' "$TMP/$case_name.log" \
|
|
&& pass_case "$case_name was rejected by P0" || fail_case "$case_name lacked an attributable P0 failure"
|
|
[[ "$before" == "$after" ]] || fail_case "$case_name mutated HOME"
|
|
done
|
|
|
|
symlink_home="$TMP/symlink-home"
|
|
symlink_outside="$TMP/symlink-outside"
|
|
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_TEST_PASSWD_HOME="$symlink_home" 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
|
|
[[ "$status" -ne 0 ]] || fail_case 'symlink-parent unsafe path returned zero'
|
|
grep -q '^\[P0\] FAIL:.*unsafe context' "$TMP/symlink-target.log" \
|
|
&& pass_case 'symlinked rollback parent was rejected by P0' \
|
|
|| fail_case 'symlinked rollback parent lacked an attributable P0 failure'
|
|
[[ -z "$(find "$symlink_outside" -mindepth 1 -print -quit)" ]] || fail_case 'symlink target was mutated'
|
|
|
|
printf '[test] case: journal initialization failure is fatal before mutation\n'
|
|
journal_home="$TMP/journal-failure/home"
|
|
mkdir -p "$journal_home/.config/mosaic"
|
|
printf 'journal-sentinel\n' > "$journal_home/.config/mosaic/operator.txt"
|
|
before="$(fingerprint "$journal_home")"
|
|
set +e
|
|
HOME="$journal_home" MOSAIC_HOME="$journal_home/.config/mosaic" MOSAIC_PREFIX="$journal_home/.npm-global" \
|
|
MOSAIC_TEST_PASSWD_HOME="$journal_home" MOSAIC_INSTALL_STATE_DIR="/proc/mosaic-journal-denied-$$" \
|
|
MOSAIC_NO_COLOR=1 PATH="$good_bin:/usr/local/bin:/usr/bin:/bin" \
|
|
bash "$ROOT/tools/install.sh" --cli --next --yes --no-auto-launch \
|
|
>"$TMP/journal-failure.log" 2>&1
|
|
status=$?
|
|
set -e
|
|
after="$(fingerprint "$journal_home")"
|
|
[[ "$status" -ne 0 ]] && pass_case 'unwritable journal directory failed non-zero' \
|
|
|| fail_case 'unwritable journal directory returned zero'
|
|
grep -q 'cannot create private journal directory' "$TMP/journal-failure.log" \
|
|
&& pass_case 'journal initialization failure was named' \
|
|
|| fail_case 'journal initialization failure lacked a named diagnostic'
|
|
[[ "$before" == "$after" ]] && pass_case 'journal failure occurred before target mutation' \
|
|
|| fail_case "journal failure mutated target HOME (before=$before after=$after)"
|
|
|
|
if [[ "$failures" -ne 0 ]]; then
|
|
printf '[test] install state-machine acceptance RED: %d failed assertion(s)\n' "$failures" >&2
|
|
printf '[test] --check transcript: %s\n' "$TMP/check.log" >&2
|
|
exit 1
|
|
fi
|
|
printf '[test] installer state-machine acceptance passed\n'
|