mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
test(electron): reap isolated macOS crash reporters on teardown
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import path from 'node:path'
|
||||
|
||||
function ownsCrashpad(command: string, userDataDir: string): boolean {
|
||||
return (
|
||||
command.includes('/chrome_crashpad_handler ') &&
|
||||
command.includes(` --database=${path.join(userDataDir, 'Crashpad')} `)
|
||||
)
|
||||
}
|
||||
|
||||
export function cleanupE2ECrashpad(userDataDir: string): void {
|
||||
if (process.platform !== 'darwin') {
|
||||
return
|
||||
}
|
||||
|
||||
// macOS reparents Crashpad before app exit; its inherited stderr can keep Playwright open.
|
||||
try {
|
||||
const table = execFileSync('ps', ['-axo', 'pid=,command='], {
|
||||
encoding: 'utf8',
|
||||
timeout: 5_000
|
||||
})
|
||||
for (const row of table.split('\n')) {
|
||||
const match = row.match(/^\s*(\d+)\s+(.+)$/)
|
||||
if (!match || !ownsCrashpad(match[2], userDataDir)) {
|
||||
continue
|
||||
}
|
||||
const pid = Number(match[1])
|
||||
if (!Number.isSafeInteger(pid) || pid <= 1) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
const command = execFileSync('ps', ['-p', String(pid), '-o', 'command='], {
|
||||
encoding: 'utf8',
|
||||
timeout: 5_000
|
||||
})
|
||||
if (ownsCrashpad(command, userDataDir)) {
|
||||
process.kill(pid, 'SIGTERM')
|
||||
}
|
||||
} catch {
|
||||
// The test-owned reporter may already have exited.
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Cleanup remains best-effort when process enumeration is unavailable.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import path from 'node:path'
|
||||
import { cleanupE2ECrashpad } from './electron-crashpad-cleanup'
|
||||
|
||||
vi.mock('node:child_process', () => ({ execFileSync: vi.fn() }))
|
||||
|
||||
const profile = '/tmp/test profile'
|
||||
const database = path.join(profile, 'Crashpad')
|
||||
const reporter = `/Electron Framework/Helpers/chrome_crashpad_handler --database=${database} --annotation=prod=Electron`
|
||||
|
||||
afterEach(() => vi.restoreAllMocks())
|
||||
|
||||
describe('test-owned macOS Crashpad cleanup', () => {
|
||||
it('terminates only the reporter for the exact temporary profile after rechecking ownership', () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('darwin')
|
||||
const kill = vi.spyOn(process, 'kill').mockReturnValue(true)
|
||||
vi.mocked(execFileSync)
|
||||
.mockReturnValueOnce(
|
||||
`111 ${reporter}\n222 ${reporter.replace('Crashpad ', 'Crashpad-old ')}\n333 ${reporter.replace('test profile', 'another profile')}\n444 /bin/echo --database=${database} \n`
|
||||
)
|
||||
.mockReturnValueOnce(reporter)
|
||||
cleanupE2ECrashpad(profile)
|
||||
expect(kill).toHaveBeenCalledExactlyOnceWith(111, 'SIGTERM')
|
||||
expect(execFileSync).toHaveBeenLastCalledWith('ps', ['-p', '111', '-o', 'command='], {
|
||||
encoding: 'utf8',
|
||||
timeout: 5_000
|
||||
})
|
||||
})
|
||||
|
||||
it('does not signal a PID whose ownership changed after enumeration', () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('darwin')
|
||||
const kill = vi.spyOn(process, 'kill').mockReturnValue(true)
|
||||
vi.mocked(execFileSync).mockReturnValueOnce(`111 ${reporter}`).mockReturnValueOnce('/bin/sh')
|
||||
cleanupE2ECrashpad(profile)
|
||||
expect(kill).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each(['win32', 'linux'] as const)('does not enumerate processes on %s', (platform) => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue(platform)
|
||||
vi.mocked(execFileSync).mockClear()
|
||||
cleanupE2ECrashpad(profile)
|
||||
expect(execFileSync).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -2,6 +2,7 @@ import type { ChildProcess } from 'node:child_process'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { cleanupE2ECrashpad } from './electron-crashpad-cleanup'
|
||||
import type { ElectronApplication } from '@stablyai/playwright-test'
|
||||
|
||||
const GRACEFUL_CLOSE_TIMEOUT_MS = 10_000
|
||||
@@ -221,4 +222,5 @@ export async function cleanupE2EDaemons(userDataDir: string): Promise<void> {
|
||||
for (const pid of readDaemonPidFiles(userDataDir)) {
|
||||
await forceKillPidTree(pid)
|
||||
}
|
||||
cleanupE2ECrashpad(userDataDir)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user