84949882ab
Co-authored-by: Yaowei Zheng <hiyouga@buaa.edu.cn> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
306 lines
13 KiB
Bash
Executable File
306 lines
13 KiB
Bash
Executable File
#!/bin/sh
|
|
# Hermetic installer tests with tiny fixtures: canonical bundle layout, offline install with no
|
|
# network, POSIX upgrade rollback, and the online download flow through a stubbed curl
|
|
# (checksum layers, no-fallback failures, pre-0.1.6 legacy archives from pinned versions).
|
|
set -eu
|
|
|
|
ROOT_DIR="$(CDPATH= cd "$(dirname "$0")/.." && pwd)"
|
|
WORK_DIR="$(mktemp -d)"
|
|
ARTIFACT_DIR="$WORK_DIR/artifacts"
|
|
PAYLOAD_DIR="$WORK_DIR/payloads"
|
|
STUB_BIN="$WORK_DIR/bin"
|
|
TEST_HOME="$WORK_DIR/home"
|
|
trap 'rm -rf "$WORK_DIR"' EXIT HUP INT TERM
|
|
|
|
fail_test() {
|
|
echo "test failure: $1" >&2
|
|
exit 1
|
|
}
|
|
|
|
write_sha256() {
|
|
file="$1"
|
|
(cd "$(dirname "$file")" && sha256sum "$(basename "$file")" > "$(basename "$file").sha256")
|
|
}
|
|
|
|
make_posix_payload() {
|
|
target="$1"
|
|
output="$2"
|
|
behavior="${3:-success}"
|
|
payload="$WORK_DIR/payload-src"
|
|
rm -rf "$payload"
|
|
mkdir -p "$payload/penguin/bin" "$payload/penguin/lib" "$payload/penguin/web"
|
|
if [ "$behavior" = "final-failure" ]; then
|
|
{
|
|
printf '%s\n' '#!/bin/sh'
|
|
printf '%s\n' 'case "$0" in'
|
|
printf '%s\n' ' */.staging.*/bin/penguin) echo fixture-new; exit 0 ;;'
|
|
printf '%s\n' ' *) echo "fixture final-path failure" >&2; exit 42 ;;'
|
|
printf '%s\n' 'esac'
|
|
} > "$payload/penguin/bin/penguin"
|
|
else
|
|
{
|
|
printf '%s\n' '#!/bin/sh'
|
|
printf 'echo %s\n' "${4:-fixture-old}"
|
|
} > "$payload/penguin/bin/penguin"
|
|
fi
|
|
chmod +x "$payload/penguin/bin/penguin"
|
|
printf '%s\n' fixture > "$payload/penguin/lib/fixture.txt"
|
|
mkdir -p "$payload/penguin/lib/vendor"
|
|
printf '%s\n' vendored > "$payload/penguin/lib/vendor/data.txt"
|
|
printf '%s\n' fixture > "$payload/penguin/web/index.html"
|
|
printf '{"schemaVersion":1,"target":"%s"}\n' "$target" > "$payload/penguin/package-manifest.json"
|
|
tar -czf "$output" -C "$payload" penguin
|
|
}
|
|
|
|
command -v sha256sum >/dev/null 2>&1 || fail_test "sha256sum is required"
|
|
command -v unzip >/dev/null 2>&1 || fail_test "unzip is required"
|
|
mkdir -p "$ARTIFACT_DIR" "$PAYLOAD_DIR" "$STUB_BIN" "$TEST_HOME"
|
|
|
|
case "$(uname -s):$(uname -m)" in
|
|
Linux:x86_64) HOST_TARGET="linux-x64" ;;
|
|
Linux:aarch64) HOST_TARGET="linux-arm64" ;;
|
|
Darwin:x86_64) HOST_TARGET="darwin-x64" ;;
|
|
Darwin:arm64) HOST_TARGET="darwin-arm64" ;;
|
|
*) fail_test "unsupported fixture platform" ;;
|
|
esac
|
|
HOST_ASSET="penguin-$HOST_TARGET.tar.gz"
|
|
|
|
# --- Build fixture payloads and package them exactly like the release workflow. ---
|
|
for target in linux-x64 linux-arm64 darwin-x64 darwin-arm64 universal; do
|
|
make_posix_payload "$target" "$PAYLOAD_DIR/$target.tar.gz"
|
|
done
|
|
windows_payload="$WORK_DIR/windows/penguin"
|
|
mkdir -p "$windows_payload/bin"
|
|
printf '%s\r\n' '@echo off' 'echo fixture-old' > "$windows_payload/bin/penguin.cmd"
|
|
printf '%s\n' '{"schemaVersion":1,"target":"win32-x64"}' > "$windows_payload/package-manifest.json"
|
|
(cd "$WORK_DIR/windows" && zip -qr "$PAYLOAD_DIR/win32-x64.zip" penguin)
|
|
|
|
sh "$ROOT_DIR/scripts/package-release-bundles.sh" "$PAYLOAD_DIR" "$ARTIFACT_DIR"
|
|
|
|
# --- Canonical layout: flat bundles, exact member set, byte-identical installers, both
|
|
# checksum layers valid. ---
|
|
for target in linux-x64 linux-arm64 darwin-x64 darwin-arm64 universal; do
|
|
bundle="$ARTIFACT_DIR/penguin-$target.tar.gz"
|
|
[ -f "$bundle" ] || fail_test "missing $(basename "$bundle")"
|
|
(cd "$ARTIFACT_DIR" && sha256sum -c "$(basename "$bundle").sha256" >/dev/null) \
|
|
|| fail_test "outer checksum failed for $(basename "$bundle")"
|
|
members="$(tar -tzf "$bundle" | sed 's#^\./##' | sed '/^$/d' | LC_ALL=C sort)"
|
|
expected="$(printf '%s\n' install.sh payload.tar.gz payload.tar.gz.sha256 | LC_ALL=C sort)"
|
|
[ "$members" = "$expected" ] || fail_test "$(basename "$bundle") has an unexpected layout"
|
|
extracted="$WORK_DIR/layout-$target"
|
|
mkdir -p "$extracted"
|
|
tar -xzf "$bundle" -C "$extracted"
|
|
[ -x "$extracted/install.sh" ] || fail_test "$(basename "$bundle") installer is not executable"
|
|
cmp -s "$ROOT_DIR/install.sh" "$extracted/install.sh" \
|
|
|| fail_test "$(basename "$bundle") installer differs from the repository installer"
|
|
(cd "$extracted" && sha256sum -c payload.tar.gz.sha256 >/dev/null) \
|
|
|| fail_test "$(basename "$bundle") payload checksum failed"
|
|
cmp -s "$PAYLOAD_DIR/$target.tar.gz" "$extracted/payload.tar.gz" \
|
|
|| fail_test "$(basename "$bundle") payload differs from its input"
|
|
done
|
|
|
|
windows_bundle="$ARTIFACT_DIR/penguin-win32-x64.zip"
|
|
[ -f "$windows_bundle" ] || fail_test "missing penguin-win32-x64.zip"
|
|
(cd "$ARTIFACT_DIR" && sha256sum -c penguin-win32-x64.zip.sha256 >/dev/null) \
|
|
|| fail_test "outer checksum failed for penguin-win32-x64.zip"
|
|
members="$(unzip -Z1 "$windows_bundle" | LC_ALL=C sort)"
|
|
expected="$(printf '%s\n' install.cmd install.ps1 payload.zip payload.zip.sha256 | LC_ALL=C sort)"
|
|
[ "$members" = "$expected" ] || fail_test "penguin-win32-x64.zip has an unexpected layout"
|
|
extracted="$WORK_DIR/layout-win32-x64"
|
|
mkdir -p "$extracted"
|
|
(cd "$extracted" && unzip -q "$windows_bundle")
|
|
cmp -s "$ROOT_DIR/install.ps1" "$extracted/install.ps1" \
|
|
|| fail_test "Windows bundle installer differs from the repository installer"
|
|
cmp -s "$ROOT_DIR/install.cmd" "$extracted/install.cmd" \
|
|
|| fail_test "Windows bundle install.cmd differs from the repository entry point"
|
|
(cd "$extracted" && sha256sum -c payload.zip.sha256 >/dev/null) \
|
|
|| fail_test "Windows bundle payload checksum failed"
|
|
cmp -s "$PAYLOAD_DIR/win32-x64.zip" "$extracted/payload.zip" \
|
|
|| fail_test "Windows bundle payload differs from its input"
|
|
|
|
# --- Offline install: extract the bundle once and run its installer, with a curl that always
|
|
# fails first on PATH — the offline path must never touch the network. ---
|
|
cat > "$STUB_BIN/curl" <<'EOF'
|
|
#!/bin/sh
|
|
echo "unexpected network access: curl $*" >&2
|
|
exit 7
|
|
EOF
|
|
chmod +x "$STUB_BIN/curl"
|
|
|
|
OFFLINE_DIR="$WORK_DIR/offline"
|
|
OFFLINE_INSTALL="$WORK_DIR/offline-install"
|
|
mkdir -p "$OFFLINE_DIR"
|
|
tar -xzf "$ARTIFACT_DIR/$HOST_ASSET" -C "$OFFLINE_DIR"
|
|
HOME="$TEST_HOME" PENGUIN_INSTALL_DIR="$OFFLINE_INSTALL" PATH="$STUB_BIN:$PATH" \
|
|
sh "$OFFLINE_DIR/install.sh" >/dev/null \
|
|
|| fail_test "offline install from the extracted bundle failed"
|
|
[ "$("$OFFLINE_INSTALL/bin/penguin" --version)" = "fixture-old" ] \
|
|
|| fail_test "offline install did not produce a working command"
|
|
|
|
# A corrupted extracted payload must be rejected by the sealed checksum.
|
|
CORRUPT_DIR="$WORK_DIR/offline-corrupt"
|
|
mkdir -p "$CORRUPT_DIR"
|
|
tar -xzf "$ARTIFACT_DIR/$HOST_ASSET" -C "$CORRUPT_DIR"
|
|
printf 'corruption' >> "$CORRUPT_DIR/payload.tar.gz"
|
|
set +e
|
|
HOME="$TEST_HOME" PENGUIN_INSTALL_DIR="$WORK_DIR/offline-corrupt-install" PATH="$STUB_BIN:$PATH" \
|
|
sh "$CORRUPT_DIR/install.sh" >/dev/null 2>&1
|
|
status=$?
|
|
set -e
|
|
[ "$status" -ne 0 ] || fail_test "corrupted offline payload was not rejected"
|
|
|
|
# --- Local archives: the canonical bundle and a bare payload both install; a failing upgrade
|
|
# rolls back to the previous installation. ---
|
|
LOCAL_INSTALL="$TEST_HOME/.penguin"
|
|
HOME="$TEST_HOME" PENGUIN_INSTALL_DIR="$LOCAL_INSTALL" \
|
|
sh "$ROOT_DIR/install.sh" --archive "$ARTIFACT_DIR/$HOST_ASSET" >/dev/null \
|
|
|| fail_test "--archive with the canonical bundle failed"
|
|
|
|
payload_archive="$WORK_DIR/payload.tar.gz"
|
|
cp "$PAYLOAD_DIR/$HOST_TARGET.tar.gz" "$payload_archive"
|
|
write_sha256 "$payload_archive"
|
|
HOME="$TEST_HOME" PENGUIN_INSTALL_DIR="$LOCAL_INSTALL" \
|
|
sh "$ROOT_DIR/install.sh" --archive "$payload_archive" >/dev/null \
|
|
|| fail_test "--archive with a bare payload failed"
|
|
|
|
failure_archive="$WORK_DIR/final-failure.tar.gz"
|
|
make_posix_payload "$HOST_TARGET" "$failure_archive" final-failure
|
|
write_sha256 "$failure_archive"
|
|
set +e
|
|
HOME="$TEST_HOME" PENGUIN_INSTALL_DIR="$LOCAL_INSTALL" \
|
|
sh "$ROOT_DIR/install.sh" --archive "$failure_archive" >/dev/null 2>&1
|
|
status=$?
|
|
set -e
|
|
[ "$status" -ne 0 ] || fail_test "failing POSIX upgrade unexpectedly succeeded"
|
|
[ "$("$LOCAL_INSTALL/bin/penguin" --version)" = "fixture-old" ] \
|
|
|| fail_test "previous POSIX installation was not restored"
|
|
|
|
# --- Pinned-directory upgrade: emulate a filesystem that refuses to rename in-use directories
|
|
# (overlayfs reports EBUSY when `penguin update` replaces the very lib/ its own process runs
|
|
# from). mv/rmdir stubs refuse directory renames that touch the installed lib, forcing
|
|
# relocate_dir through its per-entry and copy fallbacks and the husk-reuse path. ---
|
|
PINNED_LIB="$LOCAL_INSTALL/lib"
|
|
export PINNED_LIB
|
|
cat > "$STUB_BIN/mv" <<'EOF'
|
|
#!/bin/sh
|
|
if [ -d "$1" ]; then
|
|
case "$1" in
|
|
"$PINNED_LIB" | "$PINNED_LIB"/*)
|
|
echo "mv: cannot move '$1': Device or resource busy" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
exec /bin/mv "$@"
|
|
EOF
|
|
cat > "$STUB_BIN/rmdir" <<'EOF'
|
|
#!/bin/sh
|
|
case "$1" in
|
|
"$PINNED_LIB")
|
|
echo "rmdir: failed to remove '$1': Device or resource busy" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
exec /bin/rmdir "$@"
|
|
EOF
|
|
chmod +x "$STUB_BIN/mv" "$STUB_BIN/rmdir"
|
|
|
|
pinned_archive="$WORK_DIR/pinned-upgrade.tar.gz"
|
|
make_posix_payload "$HOST_TARGET" "$pinned_archive" success fixture-upgraded
|
|
write_sha256 "$pinned_archive"
|
|
HOME="$TEST_HOME" PENGUIN_INSTALL_DIR="$LOCAL_INSTALL" PATH="$STUB_BIN:$PATH" \
|
|
sh "$ROOT_DIR/install.sh" --archive "$pinned_archive" >/dev/null \
|
|
|| fail_test "upgrade with a pinned lib directory failed"
|
|
rm -f "$STUB_BIN/mv" "$STUB_BIN/rmdir"
|
|
[ "$("$LOCAL_INSTALL/bin/penguin" --version)" = "fixture-upgraded" ] \
|
|
|| fail_test "pinned-lib upgrade did not install the new version"
|
|
[ -f "$LOCAL_INSTALL/lib/vendor/data.txt" ] \
|
|
|| fail_test "pinned-lib upgrade lost the copied lib subdirectory"
|
|
[ -z "$(ls -A "$LOCAL_INSTALL" | grep -E '^\.(old|staging)\.' || :)" ] \
|
|
|| fail_test "pinned-lib upgrade left staging or backup directories behind"
|
|
|
|
# --- Online flow through a stubbed curl. The canonical bundle is served for current releases;
|
|
# MODE=legacy serves a pre-0.1.6 program archive, which must still install from a pinned
|
|
# version. Checksum failures and download failures must fail without any fallback. ---
|
|
LEGACY_ARCHIVE="$WORK_DIR/legacy.tar.gz"
|
|
make_posix_payload "$HOST_TARGET" "$LEGACY_ARCHIVE"
|
|
write_sha256 "$LEGACY_ARCHIVE"
|
|
|
|
BAD_DIR="$WORK_DIR/bad-bundle"
|
|
mkdir -p "$BAD_DIR"
|
|
tar -xzf "$ARTIFACT_DIR/$HOST_ASSET" -C "$BAD_DIR"
|
|
printf '%064d payload.tar.gz\n' 0 > "$BAD_DIR/payload.tar.gz.sha256"
|
|
BAD_BUNDLE="$WORK_DIR/bad-bundle.tar.gz"
|
|
tar -czf "$BAD_BUNDLE" -C "$BAD_DIR" .
|
|
write_sha256 "$BAD_BUNDLE"
|
|
|
|
cat > "$STUB_BIN/curl" <<'EOF'
|
|
#!/bin/sh
|
|
set -eu
|
|
output=""
|
|
url=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-o) output="$2"; shift 2 ;;
|
|
-*) shift ;;
|
|
*) url="$1"; shift ;;
|
|
esac
|
|
done
|
|
printf '%s\n' "$url" >> "$REQUEST_LOG"
|
|
base="${url##*/}"
|
|
case "$MODE:$base" in
|
|
404:penguin-*) exit 22 ;;
|
|
network:penguin-*) exit 7 ;;
|
|
outer-sha-mismatch:penguin-*.sha256) printf '%064d %s\n' 0 "${base%.sha256}" > "$output" ;;
|
|
outer-sha-mismatch:penguin-*) cp "$ARTIFACT_DIR/$base" "$output" ;;
|
|
inner-sha-mismatch:penguin-*.sha256) cp "$BAD_BUNDLE.sha256" "$output" ;;
|
|
inner-sha-mismatch:penguin-*) cp "$BAD_BUNDLE" "$output" ;;
|
|
legacy:penguin-*.sha256) cp "$LEGACY_ARCHIVE.sha256" "$output" ;;
|
|
legacy:penguin-*) cp "$LEGACY_ARCHIVE" "$output" ;;
|
|
canonical:penguin-*.sha256) cp "$ARTIFACT_DIR/$base" "$output" ;;
|
|
canonical:penguin-*) cp "$ARTIFACT_DIR/$base" "$output" ;;
|
|
*) echo "unexpected fixture request: $url" >&2; exit 2 ;;
|
|
esac
|
|
EOF
|
|
chmod +x "$STUB_BIN/curl"
|
|
export ARTIFACT_DIR BAD_BUNDLE LEGACY_ARCHIVE
|
|
|
|
run_online_case() {
|
|
name="$1"
|
|
mode="$2"
|
|
version="$3"
|
|
expected="$4"
|
|
expected_requests="$5"
|
|
CASE_LOG="$WORK_DIR/$name.log"
|
|
CASE_INSTALL="$WORK_DIR/$name-install"
|
|
: > "$CASE_LOG"
|
|
set +e
|
|
REQUEST_LOG="$CASE_LOG" MODE="$mode" PATH="$STUB_BIN:$PATH" \
|
|
HOME="$WORK_DIR/$name-home" PENGUIN_INSTALL_DIR="$CASE_INSTALL" \
|
|
PENGUIN_VERSION="$version" sh "$ROOT_DIR/install.sh" >/dev/null 2>&1
|
|
status=$?
|
|
set -e
|
|
if [ "$expected" = "success" ]; then
|
|
[ "$status" -eq 0 ] || fail_test "$name unexpectedly failed"
|
|
else
|
|
[ "$status" -ne 0 ] || fail_test "$name unexpectedly succeeded"
|
|
fi
|
|
[ "$(wc -l < "$CASE_LOG" | tr -d ' ')" -eq "$expected_requests" ] \
|
|
|| fail_test "$name made an unexpected number of requests"
|
|
}
|
|
|
|
run_online_case canonical canonical "" success 2
|
|
[ "$("$WORK_DIR/canonical-install/bin/penguin" --version)" = "fixture-old" ] \
|
|
|| fail_test "canonical online install did not produce a working command"
|
|
grep -q "/releases/latest/download/$HOST_ASSET\$" "$WORK_DIR/canonical.log" \
|
|
|| fail_test "canonical did not request the canonical bundle"
|
|
run_online_case outer-mismatch outer-sha-mismatch "" failure 2
|
|
run_online_case inner-mismatch inner-sha-mismatch "" failure 2
|
|
run_online_case latest-404 404 "" failure 1
|
|
run_online_case pinned-network network v0.1.4 failure 1
|
|
run_online_case pinned-legacy legacy v0.1.4 success 2
|
|
grep -q "/releases/download/v0.1.4/$HOST_ASSET\$" "$WORK_DIR/pinned-legacy.log" \
|
|
|| fail_test "pinned legacy did not request the pinned asset"
|
|
|
|
echo "Installer bundle, offline, rollback and online tests passed."
|