open-standards/wai/quickstart/test/clean-machine.ps1
Transaction Science 783689c6be The release's macOS leg has node on its PATH, each quickstart runs every binary, and the release check reads commands
The aarch64 leg ran npm with only cargo added to launchd's PATH, which omits node. Both clean-machine
scripts said every binary runs and only looked for wai-pack and wai-remote-host: each now runs alone
and says its usage. check-wai-release-quickstart.py read a step's lines for the script's path, so an
echo of the path or a `|| true` after it passed; it now requires the script as the line's command,
the step's exit status to be the script's, and the Windows throw after it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 10:56:00 -05:00

136 lines
6.9 KiB
PowerShell

# The quickstart as a new user runs it on Windows, from the archive alone, timed step by step:
#
# pwsh -File wai/quickstart/test/clean-machine.ps1 <archive.zip>
#
# The twin of clean-machine.sh, step for step. A machine with nothing of WAI on it: a fresh
# profile (USERPROFILE, HOME, APPDATA, LOCALAPPDATA, TEMP and TMP) and working directory, a PATH
# of the system's own directories, no cargo and no repository. The steps are the quickstart's:
# 1. check the archive against its .sha256 beside it, and unpack it;
# 2. the archive holds what `wai dev` and the quickstart name: bin/ with wai, wai-relay,
# wai-room, wai-ingest, wai-remote-host, wai-pack and wai-quantum, each of which runs (the
# last four here, the first three in the steps below), and share/wai/web/ and
# share/wai/studio/ (Studio's page and its core);
# 3. wai hello; 4. wai verify; 5. wai create my-room --template video-room;
# 6. wai dev --check in the project: the relay, the room service, the page and WAI Studio at
# /_wai/ on one origin, each checked through it.
# It prints each step's time, the total, and a JSON line; it exits non-zero when a step fails,
# naming it. Downloading the archive is not timed: the archive is given. It needs PowerShell 7
# and runs on any system it runs on (a .tar.gz archive elsewhere than Windows), so its steps can
# be checked off Windows too.
param([string]$Archive)
$ErrorActionPreference = 'Stop'
if (-not $Archive) {
Get-Content $PSCommandPath | Select-Object -First 19 | ForEach-Object { $_ -replace '^# ?', '' }
exit 2
}
$Archive = (Resolve-Path $Archive).Path
# The system's temporary directory, read before TEMP, TMP and TMPDIR point into the run's own.
$SystemTmp = [IO.Path]::GetTempPath()
$Root = Join-Path $SystemTmp ("wai-clean-" + [guid]::NewGuid().ToString('N'))
$HomeDir = Join-Path $Root 'home'
$Tmp = Join-Path $Root 'tmp'
$Work = Join-Path $Root 'work'
New-Item -ItemType Directory -Force -Path $HomeDir, $Tmp, $Work | Out-Null
$env:HOME = $HomeDir
$env:USERPROFILE = $HomeDir
$env:APPDATA = Join-Path $HomeDir 'AppData\Roaming'
$env:LOCALAPPDATA = Join-Path $HomeDir 'AppData\Local'
$env:TEMP = $Tmp
$env:TMP = $Tmp
$env:TMPDIR = $Tmp
New-Item -ItemType Directory -Force -Path $env:APPDATA, $env:LOCALAPPDATA | Out-Null
$Sep = [IO.Path]::PathSeparator
if ($IsWindows) {
$System = @("$env:SystemRoot\System32", $env:SystemRoot, "$env:SystemRoot\System32\Wbem", "$env:SystemRoot\System32\WindowsPowerShell\v1.0") -join $Sep
} else {
$System = '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'
}
$env:PATH = $System
Set-Location $Work
$T0 = [Diagnostics.Stopwatch]::StartNew()
$Steps = [ordered]@{}
$Failed = ''
# Run one step, its output to <name>.log: its time, or FAILED and the log's tail.
function Step([string]$Name, [scriptblock]$Do) {
$log = Join-Path $Root "$Name.log"
$w = [Diagnostics.Stopwatch]::StartNew()
$ok = $true
try {
$global:LASTEXITCODE = 0
& $Do *> $log
if ($LASTEXITCODE -ne 0) { $ok = $false }
} catch {
$_ | Out-File -Append $log
$ok = $false
}
if ($ok) {
$script:Steps[$Name] = [math]::Round($w.Elapsed.TotalSeconds, 2).ToString([Globalization.CultureInfo]::InvariantCulture)
} else {
$script:Steps[$Name] = 'FAILED'
$script:Failed = $Name
Write-Output "FAIL ${Name}:"
if (Test-Path $log) { Get-Content $log -Tail 15 }
}
}
Step 'unpack' {
$want = ((Get-Content "$Archive.sha256" -Raw).Trim() -split '\s+')[0].ToLower()
$got = (Get-FileHash $Archive -Algorithm SHA256).Hash.ToLower()
if ($got -ne $want) { throw "SHA-256 $got, not $want" }
if ($Archive.EndsWith('.zip')) {
Expand-Archive -Path $Archive -DestinationPath $Work
} else {
tar -xzf $Archive -C $Work
}
}
$Bin = Get-ChildItem -Path $Work -Directory -Filter 'wai-*' | Select-Object -First 1 | ForEach-Object { Join-Path $_.FullName 'bin' }
if ($Bin) { $env:PATH = "$Bin$Sep$env:PATH" }
if (-not $Failed) {
Step 'contents' {
$exe = if ($IsWindows) { '.exe' } else { '' }
$missing = @()
foreach ($b in 'wai', 'wai-relay', 'wai-room', 'wai-ingest', 'wai-remote-host', 'wai-pack', 'wai-quantum') {
if (-not (Test-Path -PathType Leaf (Join-Path $Bin "$b$exe"))) { $missing += "bin/$b$exe" }
}
foreach ($f in 'share/wai/web/room/index.html', 'share/wai/studio/index.html', 'share/wai/studio/pkg/wai_studio_core_bg.wasm') {
if (-not (Test-Path -PathType Leaf (Join-Path (Split-Path $Bin -Parent) $f))) { $missing += $f }
}
if ($missing) { throw "the archive lacks: $($missing -join ' ')" }
wai-quantum --version
if ($LASTEXITCODE -ne 0) { throw "wai-quantum --version exits $LASTEXITCODE" }
# wai-ingest and wai-pack alone say how they are run and exit 2; wai-remote-host, 1.
foreach ($u in @{ n = 'wai-ingest'; c = 2 }, @{ n = 'wai-pack'; c = 2 }, @{ n = 'wai-remote-host'; c = 1 }) {
$err = Join-Path $Root "$($u.n).err"
$p = Start-Process -FilePath (Join-Path $Bin "$($u.n)$exe") -NoNewWindow -Wait -PassThru -RedirectStandardOutput (Join-Path $Root "$($u.n).out") -RedirectStandardError $err
$said = Get-Content $err -Raw
if ($p.ExitCode -ne $u.c -or $said -notmatch 'usage' -or $said -notmatch [regex]::Escape("$($u.n) ")) {
throw "$($u.n) alone exits $($p.ExitCode), not $($u.c) with its usage"
}
}
}
}
if (-not $Failed) { Step 'hello' { wai hello } }
if (-not $Failed) { Step 'verify' { wai verify wai-hello } }
if (-not $Failed) { Step 'create' { wai create my-room --template video-room } }
# Every piece held, Studio among them: the archive's share/wai/studio, found beside wai.
if (-not $Failed) { Step 'dev-check' { Push-Location my-room; try { wai dev --check --port 0 | Tee-Object -Variable out; if ($LASTEXITCODE -eq 0 -and -not ($out | Select-String -SimpleMatch -Quiet 'ok WAI Studio at /_wai/')) { throw 'wai dev --check did not hold WAI Studio at /_wai/' } } finally { Pop-Location } } }
$Total = [math]::Round($T0.Elapsed.TotalSeconds, 2).ToString([Globalization.CultureInfo]::InvariantCulture)
$Os = [Runtime.InteropServices.RuntimeInformation]::OSDescription
$Arch = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture
Write-Output "the quickstart from $(Split-Path $Archive -Leaf) on $Os $Arch, a fresh profile and directory:"
foreach ($k in $Steps.Keys) {
if ($Steps[$k] -eq 'FAILED') { Write-Output (" {0,-10} FAILED" -f $k) } else { Write-Output (" {0,-10} {1} s" -f $k, $Steps[$k]) }
}
$devLog = Join-Path $Root 'dev-check.log'
if (Test-Path $devLog) { Select-String -Path $devLog -Pattern 'checks held' | ForEach-Object { " $($_.Line)" } }
Write-Output (" total $Total s" + $(if ($Failed) { " (stopped at $Failed)" } else { '' }))
$stepsOut = [ordered]@{}
foreach ($k in $Steps.Keys) { $stepsOut[$k] = $Steps[$k] }
[ordered]@{ archive = (Split-Path $Archive -Leaf); total_s = [double]$Total; steps = $stepsOut; ok = (-not $Failed) } | ConvertTo-Json -Compress
Set-Location $SystemTmp
Remove-Item -Recurse -Force $Root -ErrorAction SilentlyContinue
if ($Failed) { exit 1 }
exit 0