Skip to content

Fix close-window shutdown #1

Fix close-window shutdown

Fix close-window shutdown #1

name: Native Parroty window close
on:
pull_request:
branches: [main]
permissions:
contents: read
jobs:
native-window-close:
runs-on: windows-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Parse PowerShell launcher
shell: pwsh
run: |
$tokens = $null
$errors = $null
[void][System.Management.Automation.Language.Parser]::ParseFile(
(Resolve-Path "parroty_window.ps1"),
[ref]$tokens,
[ref]$errors
)
if ($errors.Count -gt 0) {
$errors | Format-List | Out-String | Write-Error
exit 1
}
- name: Verify native window close is detected while host remains alive
shell: pwsh
run: |
$tokens = $null
$errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
(Resolve-Path "parroty_window.ps1"),
[ref]$tokens,
[ref]$errors
)
if ($errors.Count -gt 0) { throw "PowerShell parser errors found" }
$functions = $ast.FindAll({
param($node)
$node -is [System.Management.Automation.Language.FunctionDefinitionAst]
}, $true)
foreach ($function in $functions) {
Invoke-Expression $function.Extent.Text
}
Add-Type @"
using System;
using System.Runtime.InteropServices;
public static class ParrotyNativeWindow {
[DllImport("user32.dll")]
public static extern bool IsWindow(IntPtr hWnd);
}
"@
$work = Join-Path $env:RUNNER_TEMP "parroty-window-test"
New-Item -ItemType Directory -Force -Path $work | Out-Null
$handleFile = Join-Path $work "handle.txt"
$closeFile = Join-Path $work "close.txt"
$hostFile = Join-Path $work "host.ps1"
@'
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.Text = "Parroty close regression"
$form.Width = 500
$form.Height = 300
$form.Show()
Set-Content -LiteralPath $args[0] -Value ([int64]$form.Handle)
while (-not (Test-Path -LiteralPath $args[1])) {
[System.Windows.Forms.Application]::DoEvents()
Start-Sleep -Milliseconds 50
}
$form.Close()
# Deliberately keep the host process alive after the window is gone.
Start-Sleep -Seconds 4
'@ | Set-Content -LiteralPath $hostFile -Encoding UTF8
$host = Start-Process powershell.exe -ArgumentList @(
"-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass",
"-File", $hostFile, $handleFile, $closeFile
) -PassThru -WindowStyle Hidden
try {
for ($i = 0; $i -lt 40 -and -not (Test-Path $handleFile); $i++) {
Start-Sleep -Milliseconds 250
}
if (-not (Test-Path $handleFile)) { throw "Native test window did not start" }
$handle = [IntPtr][int64](Get-Content $handleFile -Raw)
if (-not [ParrotyNativeWindow]::IsWindow($handle)) {
throw "Native test handle is not a live window"
}
$closer = Start-Process powershell.exe -ArgumentList @(
"-NoLogo", "-NoProfile", "-Command",
"Start-Sleep -Seconds 1; New-Item -ItemType File -Force -Path '$closeFile' | Out-Null"
) -PassThru -WindowStyle Hidden
$watch = [System.Diagnostics.Stopwatch]::StartNew()
Wait-ParrotyWindowClose -WindowHandle $handle -WindowProcess $host
$watch.Stop()
if ($watch.Elapsed.TotalSeconds -lt 0.5 -or $watch.Elapsed.TotalSeconds -gt 8) {
throw "Window watcher returned at an unexpected time: $($watch.Elapsed.TotalSeconds)s"
}
if (-not (Get-Process -Id $host.Id -ErrorAction SilentlyContinue)) {
throw "Host process exited; test did not reproduce Chromium staying alive"
}
if ([ParrotyNativeWindow]::IsWindow($handle)) {
throw "Window watcher returned while the native window still existed"
}
}
finally {
Stop-Process -Id $host.Id -Force -ErrorAction SilentlyContinue
}
- name: Verify recorded backend PID is stopped
shell: pwsh
run: |
$tokens = $null
$errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
(Resolve-Path "parroty_window.ps1"),
[ref]$tokens,
[ref]$errors
)
$functions = $ast.FindAll({
param($node)
$node -is [System.Management.Automation.Language.FunctionDefinitionAst]
}, $true)
foreach ($function in $functions) { Invoke-Expression $function.Extent.Text }
$listener = Start-Process python -ArgumentList @(
"-m", "http.server", "5000", "--bind", "127.0.0.1"
) -PassThru -WindowStyle Hidden
try {
for ($i = 0; $i -lt 40; $i++) {
Start-Sleep -Milliseconds 250
$captured = @(Get-ParrotyBackendPids -ListenerPort 5000)
if ($captured -contains $listener.Id) { break }
}
if ($captured -notcontains $listener.Id) { throw "Listener PID was not captured" }
Stop-ParrotyBackend -ProcessIds $captured
Start-Sleep -Milliseconds 500
if (Get-Process -Id $listener.Id -ErrorAction SilentlyContinue) {
throw "Recorded backend PID remained alive"
}
}
finally {
Stop-Process -Id $listener.Id -Force -ErrorAction SilentlyContinue
}
- name: Enforce BAT exclusion
shell: pwsh
run: |
$trackedBat = @(git ls-files "*.bat")
if ($trackedBat.Count -gt 0) {
throw "Tracked BAT files found: $($trackedBat -join ', ')"
}