diff --git a/scripts/LocalDev.psm1 b/scripts/LocalDev.psm1 index 07d1707..beafa3a 100644 --- a/scripts/LocalDev.psm1 +++ b/scripts/LocalDev.psm1 @@ -90,11 +90,27 @@ function Get-RecordedProcesses { function Test-RecordedProcess { param($Record) - $process = Get-Process -Id $Record.Pid -ErrorAction SilentlyContinue + + try { + $recordedProcessId = [int]$Record.Pid + if ($recordedProcessId -le 0) { return $false } + $recordedStartTime = if ($Record.StartTimeUtc -is [datetime]) { + $Record.StartTimeUtc.ToUniversalTime() + } + else { + [datetime]::Parse( + [string]$Record.StartTimeUtc, + [System.Globalization.CultureInfo]::InvariantCulture, + [System.Globalization.DateTimeStyles]::RoundtripKind + ).ToUniversalTime() + } + } + catch { return $false } + + $process = Get-Process -Id $recordedProcessId -ErrorAction SilentlyContinue if (-not $process) { return $false } try { - $actual = $process.StartTime.ToUniversalTime().ToString('o') - return $actual -eq $Record.StartTimeUtc + return $process.StartTime.ToUniversalTime().Ticks -eq $recordedStartTime.Ticks } catch { return $false } } diff --git a/tests/local-dev.tests.ps1 b/tests/local-dev.tests.ps1 index 02836f8..5329385 100644 --- a/tests/local-dev.tests.ps1 +++ b/tests/local-dev.tests.ps1 @@ -63,6 +63,43 @@ try { Assert-Equal './cmd/worker' $commands.Worker.Arguments[1] 'Worker command should follow the backend contract' Assert-Equal 'run' $commands.Frontend.Arguments[0] 'Frontend should use its checked-in dev script' + $stateDirectory = Join-Path $fixture '.local' + New-Item -ItemType Directory -Path $stateDirectory -Force | Out-Null + $currentProcess = Get-Process -Id $PID + @([pscustomobject]@{ + Name = 'test-host' + Pid = $currentProcess.Id + StartTimeUtc = $currentProcess.StartTime.ToUniversalTime().ToString('o') + }) | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $stateDirectory 'processes.json') -Encoding utf8 + $status = @(Get-LocalDevStatus -DeploymentPath $fixture) + Assert-Equal $true $status[0].Running 'Status should recognize a recorded live process after JSON date conversion' + + $childTreeRoot = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', 'powershell.exe -NoProfile -Command "Start-Sleep -Seconds 60"') -WindowStyle Hidden -PassThru + try { + $childTreeRoot.Refresh() + @([pscustomobject]@{ + Name = 'test-tree' + Pid = $childTreeRoot.Id + StartTimeUtc = $childTreeRoot.StartTime.ToUniversalTime().ToString('o') + }) | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $stateDirectory 'processes.json') -Encoding utf8 + + $deadline = [DateTime]::UtcNow.AddSeconds(5) + do { + $child = Get-CimInstance Win32_Process -Filter "ParentProcessId=$($childTreeRoot.Id)" -ErrorAction SilentlyContinue | Select-Object -First 1 + if (-not $child) { Start-Sleep -Milliseconds 50 } + } while (-not $child -and [DateTime]::UtcNow -lt $deadline) + if (-not $child) { throw 'Test child process did not start' } + + Stop-LocalDev -DeploymentPath $fixture + Assert-Equal $null (Get-Process -Id $childTreeRoot.Id -ErrorAction SilentlyContinue) 'Stop should terminate the recorded wrapper process' + Assert-Equal $null (Get-Process -Id $child.ProcessId -ErrorAction SilentlyContinue) 'Stop should terminate descendants of the recorded wrapper process' + } + finally { + if (Get-Process -Id $childTreeRoot.Id -ErrorAction SilentlyContinue) { + & taskkill.exe /PID $childTreeRoot.Id /T /F | Out-Null + } + } + Remove-Item -LiteralPath (Join-Path $backend 'cmd\worker') -Recurse Assert-Throws { Resolve-LocalDevCommands -BackendPath $backend -FrontendPath $frontend } 'cmd/worker' 'A missing worker entrypoint must be actionable'