172 lines
7.9 KiB
PowerShell
172 lines
7.9 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string]$Python = ".venv-build\Scripts\python.exe",
|
|
[switch]$SkipFrontendInstall
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$ProjectRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
|
$CompanionRoot = Join-Path $ProjectRoot "video_companion"
|
|
$Spec = Join-Path $ProjectRoot "packaging\doctor_workstation.spec"
|
|
|
|
function Invoke-FrozenSmokeTest {
|
|
param([Parameter(Mandatory = $true)][string]$Executable)
|
|
|
|
$TempRoot = [System.IO.Path]::GetFullPath([System.IO.Path]::GetTempPath())
|
|
$SmokeRoot = Join-Path $TempRoot ("doctor-workstation-smoke-" + [guid]::NewGuid().ToString("N"))
|
|
$SmokeRoot = [System.IO.Path]::GetFullPath($SmokeRoot)
|
|
if (-not ($SmokeRoot.StartsWith($TempRoot, [System.StringComparison]::OrdinalIgnoreCase)) -or
|
|
-not ([System.IO.Path]::GetFileName($SmokeRoot)).StartsWith("doctor-workstation-smoke-")) {
|
|
throw "Refusing to use an unsafe smoke-test directory: $SmokeRoot"
|
|
}
|
|
|
|
$Environment = @{
|
|
"APPDATA" = (Join-Path $SmokeRoot "AppData\Roaming")
|
|
"LOCALAPPDATA" = (Join-Path $SmokeRoot "AppData\Local")
|
|
"XDG_CONFIG_HOME" = (Join-Path $SmokeRoot "xdg\config")
|
|
"XDG_STATE_HOME" = (Join-Path $SmokeRoot "xdg\state")
|
|
"XDG_CACHE_HOME" = (Join-Path $SmokeRoot "xdg\cache")
|
|
"DOCTOR_CONFIG_DIR" = (Join-Path $SmokeRoot "doctor\config")
|
|
"DOCTOR_LOG_DIR" = (Join-Path $SmokeRoot "doctor\logs")
|
|
"DOCTOR_API_BASE_URL" = "https://127.0.0.1:9"
|
|
"DOCTOR_DEMO_MODE" = "true"
|
|
"DOCTOR_VIDEO_MODE" = "embedded"
|
|
"DOCTOR_VIDEO_WEB_URL" = ""
|
|
"DOCTOR_VERIFY_SSL" = "true"
|
|
"DOCTOR_LOG_LEVEL" = "INFO"
|
|
"DOCTOR_SMOKE_TEST" = "1"
|
|
"HTTP_PROXY" = "http://127.0.0.1:9"
|
|
"HTTPS_PROXY" = "http://127.0.0.1:9"
|
|
"ALL_PROXY" = "http://127.0.0.1:9"
|
|
"NO_PROXY" = ""
|
|
"QT_QPA_PLATFORM" = "offscreen"
|
|
}
|
|
$PreviousEnvironment = @{}
|
|
$StandardOutput = Join-Path $SmokeRoot "stdout.txt"
|
|
$StandardError = Join-Path $SmokeRoot "stderr.txt"
|
|
|
|
New-Item -ItemType Directory -Path $SmokeRoot -Force | Out-Null
|
|
New-Item -ItemType Directory -Path $Environment["APPDATA"] -Force | Out-Null
|
|
New-Item -ItemType Directory -Path $Environment["LOCALAPPDATA"] -Force | Out-Null
|
|
try {
|
|
foreach ($Name in $Environment.Keys) {
|
|
$PreviousEnvironment[$Name] = [Environment]::GetEnvironmentVariable($Name, "Process")
|
|
[Environment]::SetEnvironmentVariable($Name, $Environment[$Name], "Process")
|
|
}
|
|
|
|
# Windows PowerShell 5 can leave ExitCode unset on the object returned
|
|
# by Start-Process. System.Diagnostics.Process retains the real code
|
|
# and lets us drain redirected streams without risking a pipe deadlock.
|
|
$ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo
|
|
$ProcessInfo.FileName = $Executable
|
|
$ProcessInfo.Arguments = "--smoke-test"
|
|
$ProcessInfo.UseShellExecute = $false
|
|
$ProcessInfo.CreateNoWindow = $true
|
|
$ProcessInfo.RedirectStandardOutput = $true
|
|
$ProcessInfo.RedirectStandardError = $true
|
|
$Process = New-Object System.Diagnostics.Process
|
|
$Process.StartInfo = $ProcessInfo
|
|
if (-not $Process.Start()) {
|
|
throw "Unable to start the frozen application smoke test"
|
|
}
|
|
$OutputTask = $Process.StandardOutput.ReadToEndAsync()
|
|
$ErrorTask = $Process.StandardError.ReadToEndAsync()
|
|
$TimedOut = -not $Process.WaitForExit(30000)
|
|
if ($TimedOut -and -not $Process.HasExited) {
|
|
$Process.Kill()
|
|
}
|
|
$Process.WaitForExit()
|
|
$OutputTask.Wait()
|
|
$ErrorTask.Wait()
|
|
Set-Content -LiteralPath $StandardOutput -Value $OutputTask.Result -Encoding UTF8
|
|
Set-Content -LiteralPath $StandardError -Value $ErrorTask.Result -Encoding UTF8
|
|
|
|
$DiagnosticFiles = @($StandardOutput, $StandardError)
|
|
$DiagnosticFiles += Get-ChildItem -LiteralPath $SmokeRoot -Recurse -Filter "*.log" -File |
|
|
Select-Object -ExpandProperty FullName
|
|
$Diagnostics = ($DiagnosticFiles | Where-Object { Test-Path -LiteralPath $_ } |
|
|
ForEach-Object { Get-Content -LiteralPath $_ -Raw -ErrorAction SilentlyContinue }) -join "`n"
|
|
if ($TimedOut) {
|
|
throw "Frozen application smoke test timed out after 30 seconds`n$Diagnostics"
|
|
}
|
|
if ($Process.ExitCode -ne 0) {
|
|
throw "Frozen application smoke test failed with exit code $($Process.ExitCode)`n$Diagnostics"
|
|
}
|
|
if ($Diagnostics -match "(?im)traceback \(most recent call last\)|unhandled exception|uncaught exception|fatal python error") {
|
|
throw "Frozen application smoke-test logs contain an unhandled exception"
|
|
}
|
|
Write-Host "Frozen entry smoke test passed (--smoke-test, isolated demo mode)."
|
|
}
|
|
finally {
|
|
foreach ($Name in $Environment.Keys) {
|
|
[Environment]::SetEnvironmentVariable($Name, $PreviousEnvironment[$Name], "Process")
|
|
}
|
|
if (Test-Path -LiteralPath $SmokeRoot) {
|
|
Remove-Item -LiteralPath $SmokeRoot -Recurse -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
if (-not [System.IO.Path]::IsPathRooted($Python)) {
|
|
$Python = Join-Path $ProjectRoot $Python
|
|
}
|
|
if (-not (Test-Path -LiteralPath $Python -PathType Leaf)) {
|
|
throw "Build Python was not found: $Python"
|
|
}
|
|
|
|
$Npm = (Get-Command npm.cmd -ErrorAction Stop).Source
|
|
Push-Location $ProjectRoot
|
|
try {
|
|
if (-not $SkipFrontendInstall) {
|
|
& $Npm ci --prefix $CompanionRoot --no-audit --no-fund
|
|
if ($LASTEXITCODE -ne 0) { throw "npm ci failed" }
|
|
}
|
|
|
|
& $Npm run build --prefix $CompanionRoot
|
|
if ($LASTEXITCODE -ne 0) { throw "video companion build failed" }
|
|
|
|
& $Python -m PyInstaller --noconfirm --clean $Spec
|
|
if ($LASTEXITCODE -ne 0) { throw "PyInstaller build failed" }
|
|
|
|
$Artifact = Join-Path $ProjectRoot "dist\DoctorWorkstation"
|
|
$Helper = Get-ChildItem -LiteralPath $Artifact -Recurse -Filter "QtWebEngineProcess.exe" -File | Select-Object -First 1
|
|
$Resources = Get-ChildItem -LiteralPath $Artifact -Recurse -Filter "qtwebengine_resources*.pak" -File | Select-Object -First 1
|
|
$Companion = Get-ChildItem -LiteralPath $Artifact -Recurse -Filter "index.html" -File |
|
|
Where-Object { $_.FullName -like "*video_companion_dist*" } |
|
|
Select-Object -First 1
|
|
if (-not $Helper) { throw "QtWebEngineProcess.exe is missing from the artifact" }
|
|
if (-not $Resources) { throw "QtWebEngine Chromium resources are missing from the artifact" }
|
|
if (-not $Companion) { throw "video_companion_dist is missing from the artifact" }
|
|
|
|
$PythonBase = (& $Python -c "import sys; print(sys.base_prefix)").Trim()
|
|
if ($LASTEXITCODE -ne 0 -or -not $PythonBase) {
|
|
throw "Unable to resolve the build Python runtime directory"
|
|
}
|
|
foreach ($RuntimeDllName in @("libssl-3-x64.dll", "libcrypto-3-x64.dll")) {
|
|
$SourceRuntimeDll = Join-Path $PythonBase "DLLs\$RuntimeDllName"
|
|
$FrozenRuntimeDll = Join-Path $Artifact "_internal\$RuntimeDllName"
|
|
if (-not (Test-Path -LiteralPath $SourceRuntimeDll -PathType Leaf)) {
|
|
throw "Build Python runtime dependency is missing: $SourceRuntimeDll"
|
|
}
|
|
if (-not (Test-Path -LiteralPath $FrozenRuntimeDll -PathType Leaf)) {
|
|
throw "Frozen Python runtime dependency is missing: $FrozenRuntimeDll"
|
|
}
|
|
$SourceHash = (Get-FileHash -LiteralPath $SourceRuntimeDll -Algorithm SHA256).Hash
|
|
$FrozenHash = (Get-FileHash -LiteralPath $FrozenRuntimeDll -Algorithm SHA256).Hash
|
|
if ($SourceHash -ne $FrozenHash) {
|
|
throw "Frozen $RuntimeDllName does not match the build Python runtime"
|
|
}
|
|
}
|
|
|
|
$Executable = Join-Path $Artifact "DoctorWorkstation.exe"
|
|
if (-not (Test-Path -LiteralPath $Executable -PathType Leaf)) {
|
|
throw "Frozen application entry point is missing: $Executable"
|
|
}
|
|
Invoke-FrozenSmokeTest -Executable $Executable
|
|
|
|
Write-Host "Build complete: $Artifact"
|
|
}
|
|
finally {
|
|
Pop-Location
|
|
}
|