Repository navigation
Fix close-window shutdown #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 ', ')" | |
| } |