mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-28 16:50:57 +02:00
256 lines
9.6 KiB
PowerShell
256 lines
9.6 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string]$InstallerPath
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
if ($env:OS -ne 'Windows_NT') {
|
|
throw 'The Windows installer smoke test can only run on Windows.'
|
|
}
|
|
|
|
if ($env:GITHUB_ACTIONS -ne 'true') {
|
|
throw 'This test changes the machine PATH and is restricted to the disposable GitHub Actions runner.'
|
|
}
|
|
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
|
|
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
|
throw 'The Windows installer smoke test requires an elevated process.'
|
|
}
|
|
|
|
$repoRoot = Split-Path -Parent $PSScriptRoot
|
|
if ([string]::IsNullOrWhiteSpace($InstallerPath)) {
|
|
$installerCandidates = @(
|
|
Get-ChildItem -LiteralPath (Join-Path $repoRoot 'dist') -Filter '*.exe' -File |
|
|
Where-Object { $_.Name -like '*Setup*.exe' }
|
|
)
|
|
if ($installerCandidates.Count -ne 1) {
|
|
throw "Expected one NSIS setup executable in dist, found $($installerCandidates.Count)."
|
|
}
|
|
$InstallerPath = $installerCandidates[0].FullName
|
|
}
|
|
|
|
$InstallerPath = (Resolve-Path -LiteralPath $InstallerPath).Path
|
|
$tempRoot = if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) {
|
|
[IO.Path]::GetTempPath()
|
|
} else {
|
|
$env:RUNNER_TEMP
|
|
}
|
|
$testId = [Guid]::NewGuid().ToString('N')
|
|
$installRoot = Join-Path $tempRoot "Prism-$testId Installer Smoke"
|
|
$tuiDirectory = Join-Path $installRoot 'resources\tui'
|
|
$tuiExecutable = Join-Path $tuiDirectory 'prism-tui.exe'
|
|
$uninstallerPath = Join-Path $installRoot 'Uninstall Prism.exe'
|
|
$legacyEntry = ($tuiDirectory -split '\s', 2)[0]
|
|
$machinePathBefore = [Environment]::GetEnvironmentVariable('Path', 'Machine')
|
|
$uninstalled = $false
|
|
$legacyDirectoryCreated = $false
|
|
|
|
function Get-PathEntries {
|
|
param([AllowNull()][string]$Value)
|
|
|
|
return @($Value -split ';' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
|
}
|
|
|
|
function Assert-PathEntriesEqual {
|
|
param(
|
|
[string[]]$Expected,
|
|
[string[]]$Actual,
|
|
[string]$Context
|
|
)
|
|
|
|
if ($Expected.Count -ne $Actual.Count) {
|
|
throw "$Context entry count mismatch. Expected $($Expected.Count), got $($Actual.Count)."
|
|
}
|
|
|
|
for ($index = 0; $index -lt $Expected.Count; $index += 1) {
|
|
if (-not [string]::Equals($Expected[$index], $Actual[$index], [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "$Context differs at index $index. Expected '$($Expected[$index])', got '$($Actual[$index])'."
|
|
}
|
|
}
|
|
}
|
|
|
|
function Invoke-CheckedProcess {
|
|
param(
|
|
[Parameter(Mandatory)][string]$FilePath,
|
|
[string]$Arguments = '',
|
|
[hashtable]$Environment = @{},
|
|
[switch]$CaptureOutput
|
|
)
|
|
|
|
$startInfo = New-Object Diagnostics.ProcessStartInfo
|
|
$startInfo.FileName = $FilePath
|
|
$startInfo.Arguments = $Arguments
|
|
$startInfo.UseShellExecute = $false
|
|
$startInfo.CreateNoWindow = $true
|
|
$startInfo.WorkingDirectory = $repoRoot
|
|
$startInfo.RedirectStandardOutput = $CaptureOutput
|
|
$startInfo.RedirectStandardError = $CaptureOutput
|
|
foreach ($name in $Environment.Keys) {
|
|
$startInfo.EnvironmentVariables[$name] = [string]$Environment[$name]
|
|
}
|
|
|
|
$process = New-Object Diagnostics.Process
|
|
$process.StartInfo = $startInfo
|
|
if (-not $process.Start()) {
|
|
throw "Could not start $FilePath."
|
|
}
|
|
|
|
$stdout = ''
|
|
$stderr = ''
|
|
if ($CaptureOutput) {
|
|
$stdout = $process.StandardOutput.ReadToEnd()
|
|
$stderr = $process.StandardError.ReadToEnd()
|
|
}
|
|
$process.WaitForExit()
|
|
|
|
if ($process.ExitCode -ne 0) {
|
|
throw "$FilePath exited with code $($process.ExitCode).`n$stdout`n$stderr"
|
|
}
|
|
|
|
return [pscustomobject]@{
|
|
Stdout = $stdout
|
|
Stderr = $stderr
|
|
}
|
|
}
|
|
|
|
function Remove-DirectoryWithRetry {
|
|
param(
|
|
[Parameter(Mandatory)][string]$LiteralPath,
|
|
[int]$MaximumAttempts = 10,
|
|
[int]$DelayMilliseconds = 500
|
|
)
|
|
|
|
for ($attempt = 1; $attempt -le $MaximumAttempts; $attempt += 1) {
|
|
if (-not (Test-Path -LiteralPath $LiteralPath)) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
Remove-Item -LiteralPath $LiteralPath -Recurse -Force -ErrorAction Stop
|
|
return
|
|
} catch {
|
|
if ($attempt -eq $MaximumAttempts) {
|
|
throw
|
|
}
|
|
Start-Sleep -Milliseconds $DelayMilliseconds
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
if ([string]::Equals($legacyEntry, $tuiDirectory, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "The smoke-test installation path must contain whitespace: $installRoot"
|
|
}
|
|
if (Test-Path -LiteralPath $legacyEntry -PathType Container) {
|
|
throw "Refusing to use an existing directory as the legacy PATH fixture: $legacyEntry"
|
|
}
|
|
|
|
$originalEntries = @(Get-PathEntries $machinePathBefore)
|
|
foreach ($entry in $originalEntries) {
|
|
if ([string]::Equals($entry, $tuiDirectory, [StringComparison]::OrdinalIgnoreCase) -or
|
|
[string]::Equals($entry, $legacyEntry, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "The machine PATH already contains a smoke-test entry: $entry"
|
|
}
|
|
}
|
|
|
|
$seededEntries = @($originalEntries) + $legacyEntry
|
|
[Environment]::SetEnvironmentVariable('Path', ($seededEntries -join ';'), 'Machine')
|
|
|
|
# NSIS requires /D to be the final argument and consumes the remainder of the
|
|
# command line as the install directory, including spaces.
|
|
Invoke-CheckedProcess -FilePath $InstallerPath -Arguments "/S /D=$installRoot" | Out-Null
|
|
|
|
if (-not (Test-Path -LiteralPath $tuiExecutable -PathType Leaf)) {
|
|
throw "The installer did not install prism-tui.exe at $tuiExecutable."
|
|
}
|
|
if (-not (Test-Path -LiteralPath $uninstallerPath -PathType Leaf)) {
|
|
throw "The installer did not create the expected uninstaller at $uninstallerPath."
|
|
}
|
|
|
|
$installedEntries = @(Get-PathEntries ([Environment]::GetEnvironmentVariable('Path', 'Machine')))
|
|
$expectedInstalledEntries = @($originalEntries) + $tuiDirectory
|
|
Assert-PathEntriesEqual -Expected $expectedInstalledEntries -Actual $installedEntries -Context 'Installed machine PATH'
|
|
|
|
$matchingTuiEntries = @(
|
|
$installedEntries | Where-Object {
|
|
[string]::Equals($_, $tuiDirectory, [StringComparison]::OrdinalIgnoreCase)
|
|
}
|
|
)
|
|
if ($matchingTuiEntries.Count -ne 1) {
|
|
throw "Expected one Prism TUI PATH entry, found $($matchingTuiEntries.Count)."
|
|
}
|
|
if ($installedEntries -icontains $legacyEntry) {
|
|
throw "The installer left the legacy truncated PATH entry in place: $legacyEntry"
|
|
}
|
|
|
|
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
$launchPath = (@($installedEntries) + @(Get-PathEntries $userPath)) -join ';'
|
|
$launchResult = Invoke-CheckedProcess -FilePath $env:ComSpec `
|
|
-Arguments '/D /C prism-tui.exe --version' `
|
|
-Environment @{ Path = $launchPath } `
|
|
-CaptureOutput
|
|
if ($launchResult.Stdout.Trim() -notmatch '^prism-tui\s+') {
|
|
throw "prism-tui did not return the expected version output: $($launchResult.Stdout.Trim())"
|
|
}
|
|
|
|
# Reinstall with the legacy prefix backed by a real directory. The safe
|
|
# migration must now preserve that entry while continuing to deduplicate the
|
|
# complete Prism TUI path.
|
|
New-Item -ItemType Directory -Path $legacyEntry | Out-Null
|
|
$legacyDirectoryCreated = $true
|
|
[Environment]::SetEnvironmentVariable('Path', ((@($installedEntries) + $legacyEntry) -join ';'), 'Machine')
|
|
Invoke-CheckedProcess -FilePath $InstallerPath -Arguments "/S /D=$installRoot" | Out-Null
|
|
|
|
$reinstalledEntries = @(Get-PathEntries ([Environment]::GetEnvironmentVariable('Path', 'Machine')))
|
|
$expectedReinstalledEntries = @($originalEntries) + $legacyEntry + $tuiDirectory
|
|
Assert-PathEntriesEqual -Expected $expectedReinstalledEntries -Actual $reinstalledEntries -Context 'Reinstalled machine PATH'
|
|
|
|
# _?= prevents NSIS from spawning a temporary uninstaller and exiting early,
|
|
# so WaitForExit observes the process that is actually removing the files.
|
|
# NSIS requires this to be the final argument and forbids quoting its value.
|
|
Invoke-CheckedProcess -FilePath $uninstallerPath -Arguments "/S _?=$installRoot" | Out-Null
|
|
$uninstalled = $true
|
|
|
|
$uninstalledEntries = @(Get-PathEntries ([Environment]::GetEnvironmentVariable('Path', 'Machine')))
|
|
$expectedUninstalledEntries = @($originalEntries) + $legacyEntry
|
|
Assert-PathEntriesEqual -Expected $expectedUninstalledEntries -Actual $uninstalledEntries -Context 'Uninstalled machine PATH'
|
|
if ($uninstalledEntries -icontains $tuiDirectory) {
|
|
throw "The uninstaller left the Prism TUI PATH entry in place: $tuiDirectory"
|
|
}
|
|
|
|
Write-Host "Windows installer PATH smoke test passed for $InstallerPath"
|
|
} finally {
|
|
if (-not $uninstalled -and (Test-Path -LiteralPath $uninstallerPath -PathType Leaf)) {
|
|
try {
|
|
Invoke-CheckedProcess -FilePath $uninstallerPath -Arguments "/S _?=$installRoot" | Out-Null
|
|
} catch {
|
|
Write-Warning "Smoke-test uninstall cleanup failed: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
[Environment]::SetEnvironmentVariable('Path', $machinePathBefore, 'Machine')
|
|
|
|
if (Test-Path -LiteralPath $installRoot) {
|
|
$fullTempRoot = [IO.Path]::GetFullPath($tempRoot).TrimEnd('\') + '\'
|
|
$fullInstallRoot = [IO.Path]::GetFullPath($installRoot)
|
|
if ($fullInstallRoot.StartsWith($fullTempRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
|
Remove-DirectoryWithRetry -LiteralPath $fullInstallRoot
|
|
} else {
|
|
Write-Warning "Refusing to remove unexpected smoke-test directory: $fullInstallRoot"
|
|
}
|
|
}
|
|
|
|
if ($legacyDirectoryCreated -and (Test-Path -LiteralPath $legacyEntry -PathType Container)) {
|
|
$fullTempRoot = [IO.Path]::GetFullPath($tempRoot).TrimEnd('\') + '\'
|
|
$fullLegacyEntry = [IO.Path]::GetFullPath($legacyEntry)
|
|
if ($fullLegacyEntry.StartsWith($fullTempRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
|
Remove-DirectoryWithRetry -LiteralPath $fullLegacyEntry
|
|
} else {
|
|
Write-Warning "Refusing to remove unexpected legacy fixture directory: $fullLegacyEntry"
|
|
}
|
|
}
|
|
}
|