feat(installer,web): one canonical bundle per target; robust in-place upgrades; update-check feedback (#142)
Co-authored-by: Yaowei Zheng <hiyouga@buaa.edu.cn> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Wrap each platform-specific Release archive with the matching installer and checksum.
|
||||
# Relative artifact paths are resolved from the repository root.
|
||||
set -eu
|
||||
|
||||
ROOT_DIR="$(CDPATH= cd "$(dirname "$0")/.." && pwd)"
|
||||
ARTIFACT_INPUT="${1:-dist-artifacts}"
|
||||
|
||||
case "$ARTIFACT_INPUT" in
|
||||
/*) ARTIFACT_DIR="$ARTIFACT_INPUT" ;;
|
||||
*) ARTIFACT_DIR="$ROOT_DIR/$ARTIFACT_INPUT" ;;
|
||||
esac
|
||||
|
||||
[ -d "$ARTIFACT_DIR" ] || {
|
||||
echo "error: artifact directory not found: $ARTIFACT_DIR" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
command -v tar >/dev/null 2>&1 || {
|
||||
echo "error: tar is required" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v zip >/dev/null 2>&1 || {
|
||||
echo "error: zip is required" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
write_sha256() {
|
||||
file="$1"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
(cd "$(dirname "$file")" && sha256sum "$(basename "$file")" > "$(basename "$file").sha256")
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
(cd "$(dirname "$file")" && shasum -a 256 "$(basename "$file")" > "$(basename "$file").sha256")
|
||||
else
|
||||
echo "error: sha256sum or shasum is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_payload() {
|
||||
payload="$1"
|
||||
[ -f "$ARTIFACT_DIR/$payload" ] || {
|
||||
echo "error: missing payload: $ARTIFACT_DIR/$payload" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -f "$ARTIFACT_DIR/$payload.sha256" ] || {
|
||||
echo "error: missing payload checksum: $ARTIFACT_DIR/$payload.sha256" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
|
||||
for target in linux-x64 linux-arm64 darwin-x64 darwin-arm64; do
|
||||
payload="penguin-$target.tar.gz"
|
||||
output="$ARTIFACT_DIR/penguin-$target-offline.tar.gz"
|
||||
bundle="$WORK_DIR/$target"
|
||||
require_payload "$payload"
|
||||
mkdir -p "$bundle"
|
||||
cp "$ARTIFACT_DIR/$payload" "$ARTIFACT_DIR/$payload.sha256" "$bundle/"
|
||||
cp "$ROOT_DIR/install.sh" "$bundle/penguin-installer.sh"
|
||||
{
|
||||
printf '%s\n' '#!/bin/sh'
|
||||
printf '%s\n' '# Offline bundle entry point. The payload path is explicit so the online installer never scans its directory.'
|
||||
printf '%s\n' 'set -eu'
|
||||
printf '%s\n' 'SCRIPT_DIR="$(CDPATH= cd "$(dirname "$0")" && pwd)"'
|
||||
printf 'exec sh "$SCRIPT_DIR/penguin-installer.sh" --archive "$SCRIPT_DIR/%s" "$@"\n' "$payload"
|
||||
} > "$bundle/install.sh"
|
||||
chmod +x "$bundle/install.sh" "$bundle/penguin-installer.sh"
|
||||
rm -f "$output" "$output.sha256"
|
||||
tar -czf "$output" -C "$bundle" .
|
||||
write_sha256 "$output"
|
||||
echo "Created $(basename "$output")"
|
||||
done
|
||||
|
||||
payload="penguin-win32-x64.zip"
|
||||
output="$ARTIFACT_DIR/penguin-win32-x64-offline.zip"
|
||||
bundle="$WORK_DIR/win32-x64"
|
||||
require_payload "$payload"
|
||||
mkdir -p "$bundle"
|
||||
cp "$ARTIFACT_DIR/$payload" "$ARTIFACT_DIR/$payload.sha256" \
|
||||
"$ROOT_DIR/install.ps1" "$ROOT_DIR/install.cmd" "$bundle/"
|
||||
rm -f "$output" "$output.sha256"
|
||||
(cd "$bundle" && zip -qr "$output" .)
|
||||
write_sha256 "$output"
|
||||
echo "Created $(basename "$output")"
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/bin/sh
|
||||
# Wrap each program payload with its checksum and native installer into the canonical Release
|
||||
# artifact — the only package shape a Release publishes:
|
||||
#
|
||||
# penguin-<target>.tar.gz = install.sh + payload.tar.gz + payload.tar.gz.sha256
|
||||
# penguin-win32-x64.zip = install.cmd + install.ps1 + payload.zip + payload.zip.sha256
|
||||
#
|
||||
# The same bundle serves online installs (downloaded, outer-verified and opened by install.sh /
|
||||
# install.ps1) and offline installs (transfer one file, extract once, run the bundled
|
||||
# installer, which verifies and installs the sibling payload with no network). The outer layer
|
||||
# stays flat so extracting it never creates deep paths; only the installer expands the payload,
|
||||
# inside its short staging directory.
|
||||
#
|
||||
# Usage: package-release-bundles.sh <payload-dir> [output-dir]
|
||||
# <payload-dir> must contain <target>.tar.gz for linux-x64, linux-arm64, darwin-x64,
|
||||
# darwin-arm64 and universal, plus win32-x64.zip; relative paths resolve from the repo root.
|
||||
set -eu
|
||||
|
||||
ROOT_DIR="$(CDPATH= cd "$(dirname "$0")/.." && pwd)"
|
||||
PAYLOAD_INPUT="${1:?usage: package-release-bundles.sh <payload-dir> [output-dir]}"
|
||||
OUTPUT_INPUT="${2:-dist-artifacts}"
|
||||
|
||||
resolve_dir() {
|
||||
case "$1" in
|
||||
/*) printf '%s\n' "$1" ;;
|
||||
*) printf '%s\n' "$ROOT_DIR/$1" ;;
|
||||
esac
|
||||
}
|
||||
PAYLOAD_DIR="$(resolve_dir "$PAYLOAD_INPUT")"
|
||||
OUTPUT_DIR="$(resolve_dir "$OUTPUT_INPUT")"
|
||||
|
||||
[ -d "$PAYLOAD_DIR" ] || {
|
||||
echo "error: payload directory not found: $PAYLOAD_DIR" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v tar >/dev/null 2>&1 || {
|
||||
echo "error: tar is required" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v zip >/dev/null 2>&1 || {
|
||||
echo "error: zip is required" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
write_sha256() {
|
||||
file="$1"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
(cd "$(dirname "$file")" && sha256sum "$(basename "$file")" > "$(basename "$file").sha256")
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
(cd "$(dirname "$file")" && shasum -a 256 "$(basename "$file")" > "$(basename "$file").sha256")
|
||||
else
|
||||
echo "error: sha256sum or shasum is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_payload() {
|
||||
[ -f "$PAYLOAD_DIR/$1" ] || {
|
||||
echo "error: missing payload: $PAYLOAD_DIR/$1" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
for target in linux-x64 linux-arm64 darwin-x64 darwin-arm64 universal; do
|
||||
payload="$target.tar.gz"
|
||||
output="$OUTPUT_DIR/penguin-$target.tar.gz"
|
||||
bundle="$WORK_DIR/$target"
|
||||
require_payload "$payload"
|
||||
mkdir -p "$bundle"
|
||||
cp "$PAYLOAD_DIR/$payload" "$bundle/payload.tar.gz"
|
||||
write_sha256 "$bundle/payload.tar.gz"
|
||||
cp "$ROOT_DIR/install.sh" "$bundle/install.sh"
|
||||
chmod +x "$bundle/install.sh"
|
||||
rm -f "$output" "$output.sha256"
|
||||
tar -czf "$output" -C "$bundle" .
|
||||
write_sha256 "$output"
|
||||
echo "Created $(basename "$output")"
|
||||
done
|
||||
|
||||
payload="win32-x64.zip"
|
||||
output="$OUTPUT_DIR/penguin-win32-x64.zip"
|
||||
bundle="$WORK_DIR/win32-x64"
|
||||
require_payload "$payload"
|
||||
mkdir -p "$bundle"
|
||||
cp "$PAYLOAD_DIR/$payload" "$bundle/payload.zip"
|
||||
write_sha256 "$bundle/payload.zip"
|
||||
cp "$ROOT_DIR/install.ps1" "$ROOT_DIR/install.cmd" "$bundle/"
|
||||
rm -f "$output" "$output.sha256"
|
||||
(cd "$bundle" && zip -qr "$output" .)
|
||||
write_sha256 "$output"
|
||||
echo "Created $(basename "$output")"
|
||||
@@ -0,0 +1,189 @@
|
||||
# Hermetic Windows installer tests with tiny fixtures: offline upgrade rollback, canonical
|
||||
# bundle installs (local, sibling and online), both checksum layers, no-fallback failures, and
|
||||
# pre-0.1.6 legacy archives from pinned versions.
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$Installer = Join-Path $RepoRoot "install.ps1"
|
||||
$WorkDir = Join-Path ([IO.Path]::GetTempPath()) "penguin-installer-tests-$PID"
|
||||
$OriginalPath = $env:Path
|
||||
$OriginalOs = $env:OS
|
||||
$Fixture = @{
|
||||
Requests = [Collections.Generic.List[string]]::new()
|
||||
Mode = "canonical"
|
||||
GoodBundle = $null
|
||||
BadInnerBundle = $null
|
||||
LegacyArchive = $null
|
||||
}
|
||||
$global:PenguinInstallerFixture = $Fixture
|
||||
|
||||
function Assert-True([bool]$Condition, [string]$Message) {
|
||||
if (-not $Condition) { throw "test failure: $Message" }
|
||||
}
|
||||
|
||||
function New-FixtureArchive([string]$Name, [bool]$Fails = $false) {
|
||||
$SourceDir = Join-Path $WorkDir "$Name-source"
|
||||
$PenguinDir = Join-Path $SourceDir "penguin"
|
||||
New-Item -ItemType Directory -Path (Join-Path $PenguinDir "bin") -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path (Join-Path $PenguinDir "lib") -Force | Out-Null
|
||||
$VersionLines = if ($Fails) {
|
||||
@("echo fixture runtime failure 1>&2", "exit /b 42")
|
||||
} else {
|
||||
@("echo fixture-old", "exit /b 0")
|
||||
}
|
||||
@("@echo off", "if `"%~1`"==`"--version`" (") + $VersionLines + @(")", "exit /b 0") |
|
||||
Set-Content -LiteralPath (Join-Path $PenguinDir "bin\penguin.cmd") -Encoding ascii
|
||||
"fixture" | Set-Content -LiteralPath (Join-Path $PenguinDir "lib\fixture.txt") -Encoding ascii
|
||||
@{ schemaVersion = 1; target = "win32-x64" } | ConvertTo-Json -Compress |
|
||||
Set-Content -LiteralPath (Join-Path $PenguinDir "package-manifest.json") -Encoding ascii
|
||||
$Archive = Join-Path $WorkDir "$Name.zip"
|
||||
Compress-Archive -Path $PenguinDir -DestinationPath $Archive -CompressionLevel Fastest
|
||||
$Hash = (Get-FileHash -LiteralPath $Archive -Algorithm SHA256).Hash
|
||||
"$Hash $([IO.Path]::GetFileName($Archive))" |
|
||||
Set-Content -LiteralPath "$Archive.sha256" -Encoding ascii
|
||||
return $Archive
|
||||
}
|
||||
|
||||
# Serves release assets for the online cases. The new installer never inspects HTTP status
|
||||
# codes, so failure modes are plain throws.
|
||||
function global:Invoke-WebRequest {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Uri,
|
||||
[Parameter(Mandatory = $true)][string]$OutFile,
|
||||
[switch]$UseBasicParsing
|
||||
)
|
||||
$f = $global:PenguinInstallerFixture
|
||||
$f.Requests.Add($Uri)
|
||||
if ($f.Mode -eq "404") { throw "fixture 404: $Uri" }
|
||||
if ($f.Mode -eq "network") { throw "fixture network failure: $Uri" }
|
||||
switch -Wildcard ($Uri) {
|
||||
"*/penguin-win32-x64.zip.sha256" {
|
||||
switch ($f.Mode) {
|
||||
"outer-sha-mismatch" {
|
||||
("0" * 64) + " penguin-win32-x64.zip" | Set-Content -LiteralPath $OutFile -Encoding ascii
|
||||
}
|
||||
"inner-sha-mismatch" { Copy-Item -LiteralPath "$($f.BadInnerBundle).sha256" -Destination $OutFile }
|
||||
"legacy" { Copy-Item -LiteralPath "$($f.LegacyArchive).sha256" -Destination $OutFile }
|
||||
default { Copy-Item -LiteralPath "$($f.GoodBundle).sha256" -Destination $OutFile }
|
||||
}
|
||||
}
|
||||
"*/penguin-win32-x64.zip" {
|
||||
switch ($f.Mode) {
|
||||
"inner-sha-mismatch" { Copy-Item -LiteralPath $f.BadInnerBundle -Destination $OutFile }
|
||||
"legacy" { Copy-Item -LiteralPath $f.LegacyArchive -Destination $OutFile }
|
||||
default { Copy-Item -LiteralPath $f.GoodBundle -Destination $OutFile }
|
||||
}
|
||||
}
|
||||
default { throw "unexpected fixture request: $Uri" }
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-OnlineCase(
|
||||
[string]$Name,
|
||||
[string]$Mode,
|
||||
[string]$Version,
|
||||
[bool]$ShouldSucceed,
|
||||
[int]$ExpectedRequests
|
||||
) {
|
||||
$Fixture.Mode = $Mode
|
||||
$Fixture.Requests.Clear()
|
||||
$InstallDir = Join-Path $WorkDir "$Name-install"
|
||||
$Arguments = @{ InstallDir = $InstallDir }
|
||||
if ($Version) { $Arguments.Version = $Version }
|
||||
$Succeeded = $true
|
||||
try { & $Installer @Arguments *>&1 | Out-Null } catch { $Succeeded = $false }
|
||||
Assert-True ($Succeeded -eq $ShouldSucceed) "$Name returned an unexpected result"
|
||||
Assert-True ($Fixture.Requests.Count -eq $ExpectedRequests) `
|
||||
"$Name made $($Fixture.Requests.Count) requests, expected $ExpectedRequests"
|
||||
[PSCustomObject]@{ InstallDir = $InstallDir; Requests = @($Fixture.Requests) }
|
||||
}
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null
|
||||
# Keep the fixture tests away from the runner's user registry Path.
|
||||
$env:OS = "PenguinInstallerFixtureTest"
|
||||
|
||||
# --- Offline program archive: good install, then a failing upgrade must roll back. ---
|
||||
$InstallDir = Join-Path $WorkDir "offline-installed"
|
||||
$GoodArchive = New-FixtureArchive "valid"
|
||||
& $Installer -InstallDir $InstallDir -ArchivePath $GoodArchive *>&1 | Out-Null
|
||||
|
||||
$FailedArchive = New-FixtureArchive "failure" $true
|
||||
$Failed = $false
|
||||
try {
|
||||
& $Installer -InstallDir $InstallDir -ArchivePath $FailedArchive *>&1 | Out-Null
|
||||
} catch {
|
||||
$Failed = $true
|
||||
}
|
||||
Assert-True $Failed "failing Windows upgrade unexpectedly succeeded"
|
||||
$Version = & (Join-Path $InstallDir "bin\penguin.cmd") --version
|
||||
Assert-True ($Version -eq "fixture-old") "previous Windows installation was not restored"
|
||||
|
||||
# --- Canonical bundle fixtures: flat outer layer sealing payload.zip + checksum + installers. ---
|
||||
$BundleDir = Join-Path $WorkDir "bundle"
|
||||
New-Item -ItemType Directory -Path $BundleDir | Out-Null
|
||||
Copy-Item $GoodArchive (Join-Path $BundleDir "payload.zip")
|
||||
$PayloadHash = (Get-FileHash -LiteralPath (Join-Path $BundleDir "payload.zip") -Algorithm SHA256).Hash
|
||||
"$PayloadHash payload.zip" |
|
||||
Set-Content -LiteralPath (Join-Path $BundleDir "payload.zip.sha256") -Encoding ascii
|
||||
Copy-Item (Join-Path $RepoRoot "install.ps1"), (Join-Path $RepoRoot "install.cmd") $BundleDir
|
||||
$Fixture.GoodBundle = Join-Path $WorkDir "penguin-win32-x64.zip"
|
||||
Compress-Archive -Path (Join-Path $BundleDir "*") -DestinationPath $Fixture.GoodBundle -CompressionLevel Fastest
|
||||
$GoodHash = (Get-FileHash $Fixture.GoodBundle -Algorithm SHA256).Hash
|
||||
"$GoodHash penguin-win32-x64.zip" |
|
||||
Set-Content -LiteralPath "$($Fixture.GoodBundle).sha256" -Encoding ascii
|
||||
|
||||
$BadBundleDir = Join-Path $WorkDir "bad-bundle"
|
||||
Copy-Item $BundleDir $BadBundleDir -Recurse
|
||||
(("0" * 64) + " payload.zip") |
|
||||
Set-Content (Join-Path $BadBundleDir "payload.zip.sha256") -Encoding ascii
|
||||
$Fixture.BadInnerBundle = Join-Path $WorkDir "bad-inner.zip"
|
||||
Compress-Archive -Path (Join-Path $BadBundleDir "*") -DestinationPath $Fixture.BadInnerBundle
|
||||
$BadHash = (Get-FileHash $Fixture.BadInnerBundle -Algorithm SHA256).Hash
|
||||
"$BadHash penguin-win32-x64.zip" |
|
||||
Set-Content -LiteralPath "$($Fixture.BadInnerBundle).sha256" -Encoding ascii
|
||||
|
||||
$Fixture.LegacyArchive = $GoodArchive
|
||||
|
||||
# --- Local bundle via -ArchivePath: opened flat, sealed payload checksum verified. ---
|
||||
$BundleInstall = Join-Path $WorkDir "bundle-install"
|
||||
& $Installer -InstallDir $BundleInstall -ArchivePath $Fixture.GoodBundle *>&1 | Out-Null
|
||||
$Version = & (Join-Path $BundleInstall "bin\penguin.cmd") --version
|
||||
Assert-True ($Version -eq "fixture-old") "local bundle install did not produce a working command"
|
||||
|
||||
# --- Extracted bundle: install.ps1 next to payload.zip installs it with no network. ---
|
||||
$SiblingDir = Join-Path $WorkDir "sibling"
|
||||
New-Item -ItemType Directory -Path $SiblingDir | Out-Null
|
||||
Expand-Archive -LiteralPath $Fixture.GoodBundle -DestinationPath $SiblingDir
|
||||
$SiblingInstall = Join-Path $WorkDir "sibling-install"
|
||||
$Fixture.Requests.Clear()
|
||||
& (Join-Path $SiblingDir "install.ps1") -InstallDir $SiblingInstall *>&1 | Out-Null
|
||||
Assert-True ($Fixture.Requests.Count -eq 0) "sibling install unexpectedly touched the network"
|
||||
$Version = & (Join-Path $SiblingInstall "bin\penguin.cmd") --version
|
||||
Assert-True ($Version -eq "fixture-old") "sibling install did not produce a working command"
|
||||
|
||||
# --- Online cases. ---
|
||||
$canonical = Invoke-OnlineCase "canonical" "canonical" "" $true 2
|
||||
Assert-True ($canonical.Requests[0] -like "*/releases/latest/download/penguin-win32-x64.zip") `
|
||||
"canonical did not request the canonical bundle"
|
||||
$Version = & (Join-Path $canonical.InstallDir "bin\penguin.cmd") --version
|
||||
Assert-True ($Version -eq "fixture-old") "canonical bundle was not installed"
|
||||
Invoke-OnlineCase "outer-mismatch" "outer-sha-mismatch" "" $false 2 | Out-Null
|
||||
Invoke-OnlineCase "inner-mismatch" "inner-sha-mismatch" "" $false 2 | Out-Null
|
||||
Invoke-OnlineCase "latest-404" "404" "" $false 1 | Out-Null
|
||||
Invoke-OnlineCase "pinned-network" "network" "v0.1.4" $false 1 | Out-Null
|
||||
$pinned = Invoke-OnlineCase "pinned-legacy" "legacy" "v0.1.4" $true 2
|
||||
Assert-True ($pinned.Requests[0] -like "*/releases/download/v0.1.4/penguin-win32-x64.zip") `
|
||||
"pinned legacy did not request the pinned asset"
|
||||
|
||||
Write-Host "Windows installer bundle, offline, rollback and online tests passed."
|
||||
} finally {
|
||||
$env:Path = $OriginalPath
|
||||
$env:OS = $OriginalOs
|
||||
Remove-Item Function:\Invoke-WebRequest -ErrorAction SilentlyContinue
|
||||
Remove-Variable PenguinInstallerFixture -Scope Global -ErrorAction SilentlyContinue
|
||||
if (Test-Path -LiteralPath $WorkDir) { Remove-Item -LiteralPath $WorkDir -Recurse -Force }
|
||||
}
|
||||
Executable
+305
@@ -0,0 +1,305 @@
|
||||
#!/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."
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Smoke-test five offline wrappers and POSIX upgrade rollback with tiny fixtures.
|
||||
set -eu
|
||||
|
||||
ROOT_DIR="$(CDPATH= cd "$(dirname "$0")/.." && pwd)"
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
ARTIFACT_DIR="$WORK_DIR/artifacts"
|
||||
TEST_HOME="$WORK_DIR/home"
|
||||
INSTALL_DIR="$TEST_HOME/.penguin"
|
||||
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"
|
||||
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 '%s\n' 'echo fixture-old'
|
||||
} > "$payload/penguin/bin/penguin"
|
||||
fi
|
||||
chmod +x "$payload/penguin/bin/penguin"
|
||||
printf '%s\n' fixture > "$payload/penguin/lib/fixture.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
|
||||
write_sha256 "$output"
|
||||
}
|
||||
|
||||
verify_bundle() {
|
||||
bundle="$1"
|
||||
shift
|
||||
[ -f "$bundle" ] || fail_test "missing $(basename "$bundle")"
|
||||
[ -f "$bundle.sha256" ] || fail_test "missing $(basename "$bundle").sha256"
|
||||
(cd "$(dirname "$bundle")" && sha256sum -c "$(basename "$bundle").sha256" >/dev/null)
|
||||
members="$("$@" | sed 's#^\./##' | sed '/^$/d')"
|
||||
count="$(printf '%s\n' "$members" | wc -l | tr -d ' ')"
|
||||
[ "$count" -eq 4 ] || fail_test "$(basename "$bundle") should contain exactly four files"
|
||||
}
|
||||
|
||||
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" "$TEST_HOME"
|
||||
|
||||
for target in linux-x64 linux-arm64 darwin-x64 darwin-arm64; do
|
||||
make_posix_payload "$target" "$ARTIFACT_DIR/penguin-$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 "$ARTIFACT_DIR/penguin-win32-x64.zip" penguin)
|
||||
write_sha256 "$ARTIFACT_DIR/penguin-win32-x64.zip"
|
||||
|
||||
sh "$ROOT_DIR/scripts/package-offline-bundles.sh" "$ARTIFACT_DIR"
|
||||
|
||||
for target in linux-x64 linux-arm64 darwin-x64 darwin-arm64; do
|
||||
bundle="$ARTIFACT_DIR/penguin-$target-offline.tar.gz"
|
||||
verify_bundle "$bundle" tar -tzf "$bundle"
|
||||
tar -tzf "$bundle" | grep -q "penguin-$target.tar.gz.sha256" \
|
||||
|| fail_test "$(basename "$bundle") is missing its payload checksum"
|
||||
done
|
||||
|
||||
windows_bundle="$ARTIFACT_DIR/penguin-win32-x64-offline.zip"
|
||||
verify_bundle "$windows_bundle" unzip -Z1 "$windows_bundle"
|
||||
unzip -Z1 "$windows_bundle" | grep -q "penguin-win32-x64.zip.sha256" \
|
||||
|| fail_test "Windows bundle is missing its payload checksum"
|
||||
|
||||
HOME="$TEST_HOME" PENGUIN_INSTALL_DIR="$INSTALL_DIR" \
|
||||
sh "$ROOT_DIR/install.sh" --archive "$ARTIFACT_DIR/penguin-linux-x64.tar.gz" >/dev/null
|
||||
|
||||
failure_archive="$WORK_DIR/final-failure.tar.gz"
|
||||
make_posix_payload linux-x64 "$failure_archive" final-failure
|
||||
set +e
|
||||
HOME="$TEST_HOME" PENGUIN_INSTALL_DIR="$INSTALL_DIR" \
|
||||
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"
|
||||
[ "$("$INSTALL_DIR/bin/penguin" --version)" = "fixture-old" ] \
|
||||
|| fail_test "previous POSIX installation was not restored"
|
||||
|
||||
echo "Offline bundle and POSIX rollback smoke tests passed."
|
||||
@@ -1,65 +0,0 @@
|
||||
# Smoke-test Windows offline upgrade rollback with tiny local archives.
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$Installer = Join-Path $RepoRoot "install.ps1"
|
||||
$WorkDir = Join-Path ([IO.Path]::GetTempPath()) "penguin-offline-install-tests-$PID"
|
||||
$OriginalPath = $env:Path
|
||||
$OriginalOs = $env:OS
|
||||
|
||||
function Assert-True([bool]$Condition, [string]$Message) {
|
||||
if (-not $Condition) { throw "test failure: $Message" }
|
||||
}
|
||||
|
||||
function New-FixtureArchive([string]$Name, [bool]$Fails = $false) {
|
||||
$SourceDir = Join-Path $WorkDir "$Name-source"
|
||||
$PenguinDir = Join-Path $SourceDir "penguin"
|
||||
New-Item -ItemType Directory -Path (Join-Path $PenguinDir "bin") -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path (Join-Path $PenguinDir "lib") -Force | Out-Null
|
||||
$VersionLines = if ($Fails) {
|
||||
@("echo fixture runtime failure 1>&2", "exit /b 42")
|
||||
} else {
|
||||
@("echo fixture-old", "exit /b 0")
|
||||
}
|
||||
@("@echo off", "if `"%~1`"==`"--version`" (") + $VersionLines + @(")", "exit /b 0") |
|
||||
Set-Content -LiteralPath (Join-Path $PenguinDir "bin\penguin.cmd") -Encoding ascii
|
||||
"fixture" | Set-Content -LiteralPath (Join-Path $PenguinDir "lib\fixture.txt") -Encoding ascii
|
||||
@{ schemaVersion = 1; target = "win32-x64" } | ConvertTo-Json -Compress |
|
||||
Set-Content -LiteralPath (Join-Path $PenguinDir "package-manifest.json") -Encoding ascii
|
||||
$Archive = Join-Path $WorkDir "$Name.zip"
|
||||
Compress-Archive -Path $PenguinDir -DestinationPath $Archive -CompressionLevel Fastest
|
||||
$Hash = (Get-FileHash -LiteralPath $Archive -Algorithm SHA256).Hash
|
||||
"$Hash $([IO.Path]::GetFileName($Archive))" |
|
||||
Set-Content -LiteralPath "$Archive.sha256" -Encoding ascii
|
||||
return $Archive
|
||||
}
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null
|
||||
# Keep the fixture test away from the runner's user registry Path.
|
||||
$env:OS = "PenguinInstallerFixtureTest"
|
||||
$InstallDir = Join-Path $WorkDir "installed"
|
||||
$GoodArchive = New-FixtureArchive "valid"
|
||||
& $Installer -InstallDir $InstallDir -ArchivePath $GoodArchive *>&1 | Out-Null
|
||||
|
||||
$FailedArchive = New-FixtureArchive "failure" $true
|
||||
$Failed = $false
|
||||
try {
|
||||
& $Installer -InstallDir $InstallDir -ArchivePath $FailedArchive *>&1 | Out-Null
|
||||
} catch {
|
||||
$Failed = $true
|
||||
}
|
||||
Assert-True $Failed "failing Windows upgrade unexpectedly succeeded"
|
||||
$Version = & (Join-Path $InstallDir "bin\penguin.cmd") --version
|
||||
Assert-True ($Version -eq "fixture-old") "previous Windows installation was not restored"
|
||||
Write-Host "Windows offline rollback smoke test passed."
|
||||
} finally {
|
||||
$env:Path = $OriginalPath
|
||||
$env:OS = $OriginalOs
|
||||
if (Test-Path -LiteralPath $WorkDir) {
|
||||
Remove-Item -LiteralPath $WorkDir -Recurse -Force
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user