mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-10-02 07:34:35 +08:00
Merge pull request #208 from Tencent/fix/windows-installer-architecture
fix(installer): resolve Windows installation and upgrade failures
This commit is contained in:
+57
-34
@@ -38,11 +38,17 @@ function Write-Die {
|
||||
# ── Platform / architecture detection ─────────────────────────────────────────
|
||||
|
||||
function Get-PlatformTriple {
|
||||
$arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture
|
||||
# RuntimeInformation.ProcessArchitecture can be unavailable in Windows PowerShell 5.1.
|
||||
# WOW64 exposes the native architecture separately from the 32-bit process.
|
||||
$arch = $env:PROCESSOR_ARCHITEW6432
|
||||
if (-not $arch) { $arch = $env:PROCESSOR_ARCHITECTURE }
|
||||
if (-not $arch) {
|
||||
Write-Die "could not detect Windows architecture: PROCESSOR_ARCHITEW6432 and PROCESSOR_ARCHITECTURE are empty"
|
||||
}
|
||||
|
||||
switch ($arch) {
|
||||
"X64" { $archId = "x64" }
|
||||
"Arm64" { $archId = "arm64" }
|
||||
"AMD64" { $archId = "x64" }
|
||||
"ARM64" { $archId = "arm64" }
|
||||
default { Write-Die "unsupported architecture: $arch (x64 and ARM64 only)" }
|
||||
}
|
||||
|
||||
@@ -67,25 +73,21 @@ function Add-ToUserPath {
|
||||
|
||||
$currentUserPath = @([Environment]::GetEnvironmentVariable("PATH", "User") -split ";" | Where-Object { $_ })
|
||||
|
||||
if ($currentUserPath -contains $Dir) {
|
||||
Write-Log "$Dir is already in your user PATH"
|
||||
$newUserPath = (@($Dir) + @($currentUserPath | Where-Object { $_ -ine $Dir })) -join ";"
|
||||
if (($currentUserPath -join ";") -ceq $newUserPath) {
|
||||
Write-Log "$Dir is already first in your user PATH"
|
||||
return
|
||||
}
|
||||
|
||||
$newUserPath = ($currentUserPath + $Dir) -join ";"
|
||||
[Environment]::SetEnvironmentVariable("PATH", $newUserPath, "User")
|
||||
Write-Log "added ${Dir} to user PATH"
|
||||
Write-Log "placed ${Dir} first in user PATH"
|
||||
}
|
||||
|
||||
function Add-ToSessionPath {
|
||||
param([string]$Dir)
|
||||
|
||||
$pathEntries = $env:PATH -split ";" | Where-Object { $_ }
|
||||
if ($pathEntries -contains $Dir) {
|
||||
return
|
||||
}
|
||||
|
||||
$env:PATH = "$Dir;$env:PATH"
|
||||
$pathEntries = @($env:PATH -split ";" | Where-Object { $_ -and $_ -ine $Dir })
|
||||
$env:PATH = (@($Dir) + $pathEntries) -join ";"
|
||||
}
|
||||
|
||||
# ── Git Bash (bash environment) PATH helper ──────────────────────────────────
|
||||
@@ -126,14 +128,19 @@ function Install-Binary {
|
||||
$staged = "$Target.install-$([Guid]::NewGuid().ToString('N'))"
|
||||
try {
|
||||
[System.IO.File]::Copy($Source, $staged)
|
||||
# The running daemon may come from another installation directory.
|
||||
# Use the downloaded CLI: older versions have broken Windows liveness checks.
|
||||
# Stop verifies daemon identity and uses the current BSK_HOME.
|
||||
# Fail closed for both new installs and replacements; never discard daemon metadata here.
|
||||
& $Source daemon stop
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
$daemonHome = if ($env:BSK_HOME) { $env:BSK_HOME } else { Join-Path $HOME ".bsk" }
|
||||
$daemonInfoPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath((Join-Path $daemonHome "daemon.json"))
|
||||
throw "could not stop bsk daemon; installation was not changed. Stop any running bsk daemon, remove '$daemonInfoPath', then retry the installer."
|
||||
}
|
||||
if ([System.IO.File]::Exists($Target)) {
|
||||
# Use the downloaded CLI: older versions have broken Windows
|
||||
# liveness checks. Stop verifies daemon identity before terminating it.
|
||||
& $Source daemon stop
|
||||
if ($LASTEXITCODE -ne 0) { throw "could not stop bsk daemon; existing installation was not replaced" }
|
||||
# PowerShell 5.1 converts $null to an empty path for string parameters.
|
||||
[System.IO.File]::Replace($staged, $Target, [NullString]::Value)
|
||||
Write-Log "daemon will restart automatically on the next browser command"
|
||||
}
|
||||
else {
|
||||
[System.IO.File]::Move($staged, $Target)
|
||||
@@ -166,14 +173,20 @@ function Main {
|
||||
Write-Log "latest version is ${version}"
|
||||
}
|
||||
|
||||
$platformKey = $platform.PlatformKey
|
||||
$asset = $null
|
||||
if ($manifest -and $manifest.assets) {
|
||||
$asset = $manifest.assets.$platformKey
|
||||
}
|
||||
# ARM64 is not in the current release matrix. Require a published entry
|
||||
# before attempting it, while preserving legacy x64 installs without a manifest.
|
||||
if ($platform.ArchId -eq "arm64" -and -not $asset) {
|
||||
Write-Die "version.json does not list a Windows ARM64 package for bsk $version"
|
||||
}
|
||||
|
||||
$archiveName = "bsk-v${version}-$($platform.TargetTriple).zip"
|
||||
$downloadUrl = "${GitHub}/releases/download/${tag}/${archiveName}"
|
||||
|
||||
$expectedSha = $null
|
||||
$platformKey = $platform.PlatformKey
|
||||
if ($manifest -and $manifest.assets) {
|
||||
$expectedSha = $manifest.assets.$platformKey.sha256
|
||||
}
|
||||
$expectedSha = if ($asset) { $asset.sha256 } else { $null }
|
||||
if (-not $expectedSha) {
|
||||
if (-not $manifest) {
|
||||
Write-Log "warning: could not fetch version.json; skipping checksum verification"
|
||||
@@ -184,17 +197,21 @@ function Main {
|
||||
}
|
||||
|
||||
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
|
||||
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
|
||||
[System.IO.Directory]::CreateDirectory($tempDir) | Out-Null
|
||||
|
||||
try {
|
||||
$archivePath = Join-Path $tempDir $archiveName
|
||||
|
||||
Write-Log "downloading ${downloadUrl}"
|
||||
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath -UseBasicParsing -ErrorAction Stop
|
||||
# PowerShell 5.1's -OutFile treats brackets as wildcards. Write raw HTTP bytes literally.
|
||||
$response = Invoke-WebRequest -Uri $downloadUrl -UseBasicParsing -ErrorAction Stop
|
||||
try {
|
||||
[System.IO.File]::WriteAllBytes($archivePath, $response.RawContentStream.ToArray())
|
||||
} finally { $response.RawContentStream.Dispose() }
|
||||
|
||||
if ($expectedSha) {
|
||||
Write-Log "verifying checksum"
|
||||
$actualSha = (Get-FileHash -Algorithm SHA256 -Path $archivePath).Hash
|
||||
$actualSha = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash
|
||||
if ($actualSha -ieq $expectedSha) {
|
||||
Write-Log "checksum OK"
|
||||
}
|
||||
@@ -204,15 +221,15 @@ function Main {
|
||||
}
|
||||
|
||||
Write-Log "extracting ${archiveName}"
|
||||
Expand-Archive -Path $archivePath -DestinationPath $tempDir -Force
|
||||
# PowerShell 5.1's Expand-Archive treats the destination as a wildcard path.
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($archivePath, $tempDir)
|
||||
|
||||
if (-not (Test-Path (Join-Path $tempDir "bsk.exe"))) {
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $tempDir "bsk.exe"))) {
|
||||
Write-Die "bsk.exe not found in archive"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $InstallDir)) {
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||
}
|
||||
[System.IO.Directory]::CreateDirectory($InstallDir) | Out-Null
|
||||
|
||||
Install-Binary -Source (Join-Path $tempDir "bsk.exe") -Target (Join-Path $InstallDir "bsk.exe")
|
||||
|
||||
@@ -232,12 +249,18 @@ function Main {
|
||||
& $bskPath --version
|
||||
if ($LASTEXITCODE -ne 0) { throw "installed bsk failed verification" }
|
||||
|
||||
$command = Get-Command bsk -ErrorAction SilentlyContinue
|
||||
if (-not $command -or $command.CommandType -ne "Application" -or $command.Source -ine $bskPath) {
|
||||
Write-Log "warning: 'bsk' does not resolve to $bskPath in this session; check Get-Command bsk -All for a conflicting command"
|
||||
}
|
||||
|
||||
Write-Log "done"
|
||||
Write-Host ""
|
||||
Write-Host "Open a new terminal (PowerShell / Git Bash) for PATH changes to take full effect."
|
||||
Write-Host "This PATH check covers the current session only; a new terminal may prefer a Machine PATH entry or alias."
|
||||
Write-Host "In a new PowerShell terminal, run Get-Command bsk -All and confirm the first result is $bskPath."
|
||||
}
|
||||
finally {
|
||||
Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue
|
||||
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#Requires -Version 5.1
|
||||
param([string]$BskPath = (Join-Path $PSScriptRoot "../target/debug/bsk.exe"))
|
||||
param(
|
||||
[string]$BskPath = (Join-Path $PSScriptRoot "../target/debug/bsk.exe"),
|
||||
[switch]$HelpersOnly
|
||||
)
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Load definitions, never Main: no downloads or changes to the real user PATH.
|
||||
# Load definitions without running Main. Registry PATH writes are isolated below.
|
||||
$installer = Join-Path $PSScriptRoot "../install.ps1"
|
||||
$tokens = $null
|
||||
$errors = $null
|
||||
@@ -21,27 +24,88 @@ foreach ($statement in $ast.EndBlock.Statements) {
|
||||
function Assert-Equal($Actual, $Expected) {
|
||||
if ($Actual -cne $Expected) { throw "expected [$Expected], got [$Actual]" }
|
||||
}
|
||||
function Assert-Fails([scriptblock]$Action) {
|
||||
function Assert-Fails([scriptblock]$Action, [string]$MessageContains) {
|
||||
$failed = $false
|
||||
try { & $Action } catch { $failed = $true }
|
||||
try { & $Action } catch {
|
||||
$failed = $true
|
||||
if ($MessageContains -and -not $_.Exception.Message.Contains($MessageContains)) { throw }
|
||||
}
|
||||
if (-not $failed) { throw "expected failure" }
|
||||
}
|
||||
|
||||
# Isolate the fatal-error stub and restore the real process environment.
|
||||
& {
|
||||
function Write-Die([string]$Message) { throw $Message }
|
||||
$oldProcessArch = $env:PROCESSOR_ARCHITECTURE
|
||||
$oldNativeArch = $env:PROCESSOR_ARCHITEW6432
|
||||
try {
|
||||
$cases = @(
|
||||
@{ Process = 'AMD64'; Native = $null; Arch = 'x64'; Triple = 'x86_64-pc-windows-msvc' }
|
||||
@{ Process = 'ARM64'; Native = $null; Arch = 'arm64'; Triple = 'aarch64-pc-windows-msvc' }
|
||||
@{ Process = 'amd64'; Native = $null; Arch = 'x64'; Triple = 'x86_64-pc-windows-msvc' }
|
||||
@{ Process = 'x86'; Native = 'AMD64'; Arch = 'x64'; Triple = 'x86_64-pc-windows-msvc' }
|
||||
@{ Process = 'x86'; Native = 'ARM64'; Arch = 'arm64'; Triple = 'aarch64-pc-windows-msvc' }
|
||||
@{ Process = $null; Native = 'AMD64'; Arch = 'x64'; Triple = 'x86_64-pc-windows-msvc' }
|
||||
@{ Process = 'x86'; Native = $null; Error = 'unsupported architecture: x86 (x64 and ARM64 only)' }
|
||||
@{ Process = 'AMD64'; Native = 'IA64'; Error = 'unsupported architecture: IA64 (x64 and ARM64 only)' }
|
||||
@{ Process = $null; Native = $null; Error = 'could not detect Windows architecture: PROCESSOR_ARCHITEW6432 and PROCESSOR_ARCHITECTURE are empty' }
|
||||
)
|
||||
foreach ($case in $cases) {
|
||||
$env:PROCESSOR_ARCHITECTURE = $case.Process
|
||||
$env:PROCESSOR_ARCHITEW6432 = $case.Native
|
||||
if ($case.Error) {
|
||||
$message = $null
|
||||
try { Get-PlatformTriple | Out-Null } catch { $message = $_.Exception.Message }
|
||||
Assert-Equal $message $case.Error
|
||||
}
|
||||
else {
|
||||
$platform = Get-PlatformTriple
|
||||
Assert-Equal $platform.ArchId $case.Arch
|
||||
Assert-Equal $platform.TargetTriple $case.Triple
|
||||
Assert-Equal $platform.PlatformKey "windows-$($case.Arch)"
|
||||
}
|
||||
}
|
||||
Write-Host "Windows installer architecture regressions passed ($($PSVersionTable.PSVersion))"
|
||||
}
|
||||
finally {
|
||||
$env:PROCESSOR_ARCHITECTURE = $oldProcessArch
|
||||
$env:PROCESSOR_ARCHITEW6432 = $oldNativeArch
|
||||
}
|
||||
}
|
||||
$dir = 'C:\Users\Alice\.local\bin'
|
||||
foreach ($existing in @('', 'C:\WindowsApps', 'C:\A;C:\B')) {
|
||||
$script:UserPath = $existing
|
||||
Add-ToUserPath $dir
|
||||
$expected = if ($existing) { "$existing;$dir" } else { $dir }
|
||||
Assert-Equal $script:UserPath $expected
|
||||
Add-ToUserPath $dir
|
||||
Assert-Equal $script:UserPath $expected
|
||||
$pathCases = @(
|
||||
@{ Existing = ''; Expected = $dir }
|
||||
@{ Existing = 'C:\WindowsApps'; Expected = "$dir;C:\WindowsApps" }
|
||||
@{ Existing = 'C:\A;C:\B'; Expected = "$dir;C:\A;C:\B" }
|
||||
@{ Existing = "C:\old-bsk;$dir;C:\B;$($dir.ToUpperInvariant())"; Expected = "$dir;C:\old-bsk;C:\B" }
|
||||
)
|
||||
$oldPath = $env:PATH
|
||||
try {
|
||||
foreach ($case in $pathCases) {
|
||||
$script:UserPath = $case.Existing
|
||||
$env:PATH = $case.Existing
|
||||
foreach ($attempt in 1..2) {
|
||||
Add-ToUserPath $dir
|
||||
Add-ToSessionPath $dir
|
||||
Assert-Equal $script:UserPath $case.Expected
|
||||
Assert-Equal $env:PATH $case.Expected
|
||||
}
|
||||
}
|
||||
} finally { $env:PATH = $oldPath }
|
||||
if ($HelpersOnly) {
|
||||
Write-Host "Windows installer helper regressions passed ($($PSVersionTable.PSVersion))"
|
||||
return
|
||||
}
|
||||
|
||||
$root = Join-Path ([IO.Path]::GetTempPath()) ("bsk-install-test-" + [Guid]::NewGuid().ToString('N'))
|
||||
[IO.Directory]::CreateDirectory($root) | Out-Null
|
||||
$oldBskHome = $env:BSK_HOME
|
||||
$oldAutoUpdate = $env:BSK_AUTO_UPDATE
|
||||
$oldVersion = $env:BSK_VERSION
|
||||
$oldProcessArch = $env:PROCESSOR_ARCHITECTURE
|
||||
$oldNativeArch = $env:PROCESSOR_ARCHITEW6432
|
||||
$daemon = $null
|
||||
$script:DownloadServer = $null
|
||||
try {
|
||||
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
||||
$bashRc = Join-Path $root ".bashrc"
|
||||
@@ -66,7 +130,7 @@ try {
|
||||
if ($LASTEXITCODE -ne 0) { throw "Git Bash failed" }
|
||||
Assert-Equal $actual ('/c' + $special.Substring(2).Replace('\', '/'))
|
||||
|
||||
$env:BSK_HOME = Join-Path $root "home"
|
||||
$env:BSK_HOME = Join-Path $root "home [state]"
|
||||
$env:BSK_AUTO_UPDATE = "off"
|
||||
[IO.Directory]::CreateDirectory($env:BSK_HOME) | Out-Null
|
||||
$sourceDir = Join-Path $root "download"
|
||||
@@ -76,7 +140,8 @@ try {
|
||||
$targetDir = Join-Path $root "中文 space [x] & install"
|
||||
[IO.Directory]::CreateDirectory($targetDir) | Out-Null
|
||||
$target = Join-Path $targetDir "bsk.exe"
|
||||
Install-Binary $source $target
|
||||
$firstInstallOutput = Install-Binary $source $target 6>&1 | Out-String
|
||||
if ($firstInstallOutput -match 'daemon will restart') { throw 'fresh install reported a daemon restart' }
|
||||
Assert-Equal (Get-FileHash -LiteralPath $target).Hash (Get-FileHash -LiteralPath $source).Hash
|
||||
|
||||
$daemon = Start-Process -FilePath $target -ArgumentList @('daemon', 'start', '--foreground', '--port', '0') -WindowStyle Hidden -PassThru
|
||||
@@ -99,18 +164,165 @@ try {
|
||||
& $target --version
|
||||
if ($LASTEXITCODE -ne 0) { throw "replacement is not executable" }
|
||||
|
||||
# Installing into a new directory must also stop a daemon from the old one.
|
||||
$newTargetDir = Join-Path $root "new install"
|
||||
[IO.Directory]::CreateDirectory($newTargetDir) | Out-Null
|
||||
$newTarget = Join-Path $newTargetDir "bsk.exe"
|
||||
$before = (Get-FileHash -LiteralPath $target).Hash
|
||||
$daemon = Start-Process -FilePath $target -ArgumentList @('daemon', 'start', '--foreground', '--port', '0') -WindowStyle Hidden -PassThru
|
||||
$deadline = [DateTime]::UtcNow.AddSeconds(15)
|
||||
while (-not [IO.File]::Exists($infoPath)) {
|
||||
if ($daemon.HasExited -or [DateTime]::UtcNow -gt $deadline) { throw "old-directory daemon failed to start" }
|
||||
Start-Sleep -Milliseconds 50
|
||||
}
|
||||
Install-Binary $source $newTarget
|
||||
if (-not $daemon.WaitForExit(5000)) { throw "new-directory install left the old daemon running" }
|
||||
Assert-Equal (Get-FileHash -LiteralPath $target).Hash $before
|
||||
Assert-Equal (Get-FileHash -LiteralPath $newTarget).Hash (Get-FileHash -LiteralPath $source).Hash
|
||||
& $newTarget --version
|
||||
if ($LASTEXITCODE -ne 0) { throw "new-directory installation is not executable" }
|
||||
if (@(Get-ChildItem -LiteralPath $newTargetDir -Filter "*.install-*").Count) { throw "new-directory staging files leaked" }
|
||||
|
||||
# Refuse replacement when daemon identity cannot be verified.
|
||||
[IO.File]::WriteAllText($infoPath, 'invalid daemon metadata')
|
||||
$before = (Get-FileHash -LiteralPath $target).Hash
|
||||
Assert-Fails { Install-Binary $source $target }
|
||||
Assert-Fails { Install-Binary $source $target } -MessageContains $infoPath
|
||||
Assert-Equal (Get-FileHash -LiteralPath $target).Hash $before
|
||||
|
||||
# A failed stop must also prevent installation to a previously empty target.
|
||||
$blockedTarget = Join-Path $newTargetDir "blocked.exe"
|
||||
Assert-Fails { Install-Binary $source $blockedTarget } -MessageContains $infoPath
|
||||
if (Test-Path -LiteralPath $blockedTarget) { throw "failed stop created an installation" }
|
||||
if (@(Get-ChildItem -LiteralPath $newTargetDir -Filter "*.install-*").Count) { throw "failed stop leaked staging files" }
|
||||
Assert-Equal ([IO.File]::ReadAllText($infoPath)) 'invalid daemon metadata'
|
||||
# Following the reported recovery path must allow the same installation to succeed.
|
||||
[IO.File]::Delete($infoPath)
|
||||
Install-Binary $source $blockedTarget
|
||||
Assert-Equal (Get-FileHash -LiteralPath $blockedTarget).Hash (Get-FileHash -LiteralPath $source).Hash
|
||||
|
||||
# A remaining lock must fail without truncation or staging debris.
|
||||
$lock = [IO.File]::Open($target, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::None)
|
||||
try { Assert-Fails { Install-Binary $source $target } } finally { $lock.Dispose() }
|
||||
Assert-Equal (Get-FileHash -LiteralPath $target).Hash $before
|
||||
if (@(Get-ChildItem -LiteralPath $targetDir -Filter "*.install-*").Count) { throw "staging files leaked" }
|
||||
|
||||
# Exercise Main with real ZIPs/executables, isolating network and user state.
|
||||
& {
|
||||
$fixtureTemp = Join-Path $root 'temp [literal]'
|
||||
[IO.Directory]::CreateDirectory($fixtureTemp) | Out-Null
|
||||
$mainDefinition = ($ast.EndBlock.Statements | Where-Object {
|
||||
$_ -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $_.Name -eq 'Main'
|
||||
}).Extent.Text.Replace('[System.IO.Path]::GetTempPath()', '$fixtureTemp')
|
||||
Invoke-Expression $mainDefinition
|
||||
function Write-Die([string]$Message) { throw $Message }
|
||||
$bashProfile = (Get-Command Add-ToBashProfile).ScriptBlock
|
||||
function Add-ToBashProfile([string]$Dir) {
|
||||
& $bashProfile $Dir (Join-Path $root 'flow.bashrc')
|
||||
}
|
||||
$fixtureArchive = Join-Path $root 'release.zip'
|
||||
Compress-Archive -LiteralPath $source -DestinationPath $fixtureArchive -CompressionLevel Fastest
|
||||
# Serve the ZIP over loopback HTTP. Only the URL is redirected below;
|
||||
# Invoke-WebRequest and the installer's disk writes remain real.
|
||||
$script:DownloadServer = Start-Job -ArgumentList $fixtureArchive -ScriptBlock {
|
||||
param($Archive)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$body = [IO.File]::ReadAllBytes($Archive)
|
||||
$listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, 0)
|
||||
try {
|
||||
$listener.Start()
|
||||
Write-Output $listener.LocalEndpoint.Port
|
||||
while ($true) {
|
||||
if (-not $listener.Pending()) { Start-Sleep -Milliseconds 50; continue }
|
||||
$client = $listener.AcceptTcpClient()
|
||||
try {
|
||||
$stream = $client.GetStream()
|
||||
$stream.ReadTimeout = 5000
|
||||
$stream.WriteTimeout = 5000
|
||||
$reader = [IO.StreamReader]::new($stream)
|
||||
while (($line = $reader.ReadLine()) -and $line.Length) { }
|
||||
$header = [Text.Encoding]::ASCII.GetBytes("HTTP/1.1 200 OK`r`nContent-Type: application/octet-stream`r`nContent-Length: $($body.Length)`r`nConnection: close`r`n`r`n")
|
||||
$stream.Write($header, 0, $header.Length)
|
||||
$stream.Write($body, 0, $body.Length)
|
||||
} finally { $client.Dispose() }
|
||||
}
|
||||
} finally { $listener.Stop() }
|
||||
}
|
||||
$serverPort = $null
|
||||
$deadline = [DateTime]::UtcNow.AddSeconds(15)
|
||||
while (-not $serverPort) {
|
||||
$serverPort = Receive-Job $script:DownloadServer
|
||||
if ($script:DownloadServer.State -ne 'Running' -or [DateTime]::UtcNow -gt $deadline) {
|
||||
throw 'download fixture failed to start'
|
||||
}
|
||||
if (-not $serverPort) { Start-Sleep -Milliseconds 50 }
|
||||
}
|
||||
$asset = @{ sha256 = (Get-FileHash -LiteralPath $fixtureArchive).Hash }
|
||||
$fixtureManifest = @{ version = '9.8.7'; assets = @{ 'windows-x64' = $asset } }
|
||||
$downloads = New-Object 'System.Collections.Generic.List[string]'
|
||||
function Invoke-RestMethod([string]$Uri) {
|
||||
if (-not $fixtureManifest) { throw 'fixture manifest unavailable' }
|
||||
return $fixtureManifest
|
||||
}
|
||||
function Invoke-WebRequest {
|
||||
[CmdletBinding()]
|
||||
param([string]$Uri, [string]$OutFile, [switch]$UseBasicParsing)
|
||||
$downloads.Add($Uri)
|
||||
$PSBoundParameters['Uri'] = "http://127.0.0.1:$serverPort/release.zip"
|
||||
Microsoft.PowerShell.Utility\Invoke-WebRequest @PSBoundParameters -TimeoutSec 15
|
||||
}
|
||||
$GitHub = 'https://example.invalid/fixture'
|
||||
$InstallDir = Join-Path $root 'installed [literal]'
|
||||
$installed = Join-Path $InstallDir 'bsk.exe'
|
||||
$env:BSK_VERSION = $null
|
||||
$env:PROCESSOR_ARCHITECTURE = 'AMD64'
|
||||
$env:PROCESSOR_ARCHITEW6432 = $null
|
||||
$script:UserPath = "$targetDir;$InstallDir;$($InstallDir.ToUpperInvariant())"
|
||||
$env:PATH = "$targetDir;$InstallDir;$oldPath"
|
||||
Assert-Equal (Get-Command bsk).Source $target
|
||||
$installOutput = Main 6>&1 | Out-String
|
||||
if ($installOutput -notmatch 'current session only' -or
|
||||
$installOutput -notmatch 'Machine PATH' -or $installOutput -notmatch 'Get-Command bsk -All') {
|
||||
throw 'install did not explain PATH verification in a new terminal'
|
||||
}
|
||||
Assert-Equal $downloads.Count 1
|
||||
Assert-Equal (Get-Command bsk).Source $installed
|
||||
Assert-Equal $script:UserPath "$InstallDir;$targetDir"
|
||||
Assert-Equal (Get-FileHash -LiteralPath $installed).Hash (Get-FileHash -LiteralPath $source).Hash
|
||||
Assert-Equal @(Get-ChildItem -LiteralPath $fixtureTemp -Force).Count 0
|
||||
|
||||
# ARM64 must be listed in the manifest before any archive is requested.
|
||||
$env:PROCESSOR_ARCHITECTURE = 'ARM64'
|
||||
$downloads.Clear()
|
||||
$message = $null
|
||||
try { Main } catch { $message = $_.Exception.Message }
|
||||
Assert-Equal $message 'version.json does not list a Windows ARM64 package for bsk 9.8.7'
|
||||
Assert-Equal $downloads.Count 0
|
||||
$env:BSK_VERSION = '9.8.7'
|
||||
$fixtureManifest = $null
|
||||
$message = $null
|
||||
try { Main } catch { $message = $_.Exception.Message }
|
||||
Assert-Equal $message 'version.json does not list a Windows ARM64 package for bsk 9.8.7'
|
||||
Assert-Equal $downloads.Count 0
|
||||
|
||||
# Keep pinned x64 installs compatible with older releases without manifests.
|
||||
$env:PROCESSOR_ARCHITECTURE = 'AMD64'
|
||||
Main
|
||||
Assert-Equal $downloads.Count 1
|
||||
Assert-Equal @(Get-ChildItem -LiteralPath $fixtureTemp -Force).Count 0
|
||||
|
||||
# A listed ARM64 archive remains selectable (the fixture uses the host EXE).
|
||||
$fixtureManifest = @{ version = '9.8.7'; assets = @{ 'windows-arm64' = $asset } }
|
||||
$env:PROCESSOR_ARCHITECTURE = 'ARM64'
|
||||
Main
|
||||
Assert-Equal $downloads[$downloads.Count - 1] "$GitHub/releases/download/cli-v9.8.7/bsk-v9.8.7-aarch64-pc-windows-msvc.zip"
|
||||
|
||||
# A checksum failure must clean the bracketed temp path and preserve the install.
|
||||
$before = (Get-FileHash -LiteralPath $installed).Hash
|
||||
$fixtureManifest.assets['windows-arm64'] = @{ sha256 = '0' * 64 }
|
||||
Assert-Fails { Main }
|
||||
Assert-Equal (Get-FileHash -LiteralPath $installed).Hash $before
|
||||
Assert-Equal @(Get-ChildItem -LiteralPath $fixtureTemp -Force).Count 0
|
||||
}
|
||||
Write-Host "Windows installer regressions passed ($($PSVersionTable.PSVersion))"
|
||||
}
|
||||
catch {
|
||||
@@ -119,12 +331,20 @@ catch {
|
||||
throw
|
||||
}
|
||||
finally {
|
||||
if ($script:DownloadServer) {
|
||||
Stop-Job $script:DownloadServer
|
||||
Remove-Job $script:DownloadServer
|
||||
}
|
||||
if ($daemon -and -not $daemon.HasExited) {
|
||||
Stop-Process -Id $daemon.Id -Force
|
||||
$daemon.WaitForExit(5000) | Out-Null
|
||||
}
|
||||
$env:BSK_HOME = $oldBskHome
|
||||
$env:BSK_AUTO_UPDATE = $oldAutoUpdate
|
||||
$env:BSK_VERSION = $oldVersion
|
||||
$env:PROCESSOR_ARCHITECTURE = $oldProcessArch
|
||||
$env:PROCESSOR_ARCHITEW6432 = $oldNativeArch
|
||||
$env:PATH = $oldPath
|
||||
Remove-Item Env:BSK_TEST_RC -ErrorAction SilentlyContinue
|
||||
# Resolve and verify before recursive cleanup; only this fixture is removed.
|
||||
$resolvedRoot = [IO.Path]::GetFullPath($root)
|
||||
|
||||
Reference in New Issue
Block a user