diff --git a/install.ps1 b/install.ps1 index 620f205..e2ed669 100644 --- a/install.ps1 +++ b/install.ps1 @@ -96,16 +96,13 @@ function Install-Octopus { try { $null = Invoke-WebRequest -Uri $releaseUrl -Method Head -UseBasicParsing -ErrorAction Stop } catch { - Write-Color "Error: Release v$specificVersion not found." "Red" - Write-Color "Available releases: $baseUrl/$REPO/releases" "DarkGray" - exit 1 + throw "Release v$specificVersion not found. Available releases: $baseUrl/$REPO/releases" } } else { Write-Color "Fetching latest version..." "DarkGray" $specificVersion = Get-LatestVersion if (-not $specificVersion) { - Write-Color "Error: Failed to fetch latest version." "Red" - exit 1 + throw "Failed to fetch latest version." } } @@ -116,7 +113,7 @@ function Install-Octopus { $installedVersion = & $existing --version 2>$null if ($installedVersion -eq $specificVersion) { Write-Color "Version $specificVersion already installed." "DarkGray" - exit 0 + return } else { Write-Color "Installed version: $installedVersion" "DarkGray" } @@ -144,10 +141,8 @@ function Install-Octopus { $ProgressPreference = "SilentlyContinue" Invoke-WebRequest -Uri $downloadUrl -OutFile $zipPath -UseBasicParsing } catch { - Write-Color "Error: Failed to download $downloadUrl" "Red" - Write-Color $_.Exception.Message "Red" Remove-Item -Recurse -Force $tmpDir -ErrorAction SilentlyContinue - exit 1 + throw "Failed to download $downloadUrl : $($_.Exception.Message)" } # Extract @@ -157,9 +152,8 @@ function Install-Octopus { New-Item -ItemType Directory -Path $INSTALL_DIR -Force | Out-Null $exeSrc = Join-Path $tmpDir "$APP.exe" if (-not (Test-Path $exeSrc)) { - Write-Color "Error: $APP.exe not found in archive." "Red" Remove-Item -Recurse -Force $tmpDir -ErrorAction SilentlyContinue - exit 1 + throw "$APP.exe not found in archive." } Copy-Item -Path $exeSrc -Destination (Join-Path $INSTALL_DIR "$APP.exe") -Force Remove-Item -Recurse -Force $tmpDir -ErrorAction SilentlyContinue diff --git a/tests/test.ps1 b/tests/test.ps1 new file mode 100644 index 0000000..e8865ef --- /dev/null +++ b/tests/test.ps1 @@ -0,0 +1,75 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Regression tests for install.ps1. + +.BUG + `irm | iex` closed the terminal window. Cause: the script used + `exit`, which under Invoke-Expression terminates the entire PowerShell + host process (there is no script scope to exit). Fix: use `return` + (success) / `throw` (errors) so the host survives. + +.NOTES + Host-process termination cannot be observed from inside the dying process, + so these tests combine a source-verification guard with a behavioral test + run in a child process (`exit` cannot be caught by try/catch and kills the + child; `throw` is catchable). +#> +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent $PSScriptRoot +$installScript = Join-Path $repoRoot "install.ps1" +$failures = [System.Collections.Generic.List[string]]::new() + +function Assert-True([bool]$Cond, [string]$Message) { + if (-not $Cond) { $failures.Add($Message) | Out-Null } +} + +# --- Test 1 (source verification): no host-terminating `exit` --- +$content = Get-Content -Path $installScript -Raw +$exitCount = ([regex]::Matches($content, '(?m)^\s*exit\b')).Count +Assert-True ($exitCount -eq 0) "install.ps1 must not use bare 'exit' (found $exitCount); 'irm | iex' would kill the host. Use return/throw." + +# --- Test 2: parses cleanly --- +$parseOk = $true +try { [void][scriptblock]::Create($content) } catch { $parseOk = $false } +Assert-True $parseOk "install.ps1 must parse without syntax errors." + +# --- Test 3 (behavioral): iex must NOT kill the host --- +# Force the version lookup to fail fast against an unreachable address so the +# script reaches its early-exit/throw path offline. `exit` terminates the child +# (RESULT never emitted); `throw` is caught and RESULT is emitted. +$inner = @' +$ErrorActionPreference = "Stop" +$env:OCTOPUS_DOWNLOAD_BASE = "http://127.0.0.1:1" +$r = "nothing" +try { + Invoke-Expression (Get-Content -Raw "__INSTALL__") + $r = "completed" +} catch { + $r = "caught-throw" +} +Write-Output ("RESULT=$r") +'@.Replace("__INSTALL__", $installScript) + +$tmp = Join-Path $env:TEMP ("octopus_iex_test_" + $PID + ".ps1") +Set-Content -Path $tmp -Value $inner -Encoding UTF8 +try { + $childOut = (& powershell -NoProfile -File $tmp 2>&1 | Out-String) +} finally { + Remove-Item $tmp -ErrorAction SilentlyContinue +} +Assert-True ($childOut -match 'RESULT=') "iex must not terminate the host (try/catch must observe a RESULT). Child output:`n$childOut" + +# --- Report --- +if ($failures.Count -gt 0) { + Write-Host ("FAIL: " + $failures.Count + " test(s)") -ForegroundColor Red + foreach ($f in $failures) { Write-Host (" - " + $f) -ForegroundColor Red } + exit 1 +} +Write-Host "PASS: all regression tests passed." -ForegroundColor Green +exit 0