import fs from 'fs';
import os from 'os';
import path from 'path';
import { snapshotOwnedBrowserProcesses, survivingOwnedBrowserProcesses, profilePathsFromProcessSnapshot } from '../../lib/process-ownership.js';
function proc(root, pid, ppid, cmdline, startTime = '10', comm = 'test') {
const dir = path.join(root, String(pid));
fs.mkdirSync(dir);
fs.writeFileSync(path.join(dir, 'status'), `Name:\ttest\nPPid:\t${ppid}\n`);
fs.writeFileSync(path.join(dir, 'stat'), `${pid} (${comm}) S ${ppid} 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ${startTime}`);
fs.writeFileSync(path.join(dir, 'cmdline'), cmdline);
}
test('profile path extraction accepts separate and equals-form profile arguments', () => {
const profiles = profilePathsFromProcessSnapshot([
{ cmdline: '/cache/camoufox-bin\0-profile\0/tmp/playwright_firefoxdev_profile-live\0-foreground' },
{ cmdline: '/cache/camoufox-bin\0--profile=/tmp/camoufox-live' },
{ cmdline: '/cache/camoufox-bin\0-foreground' },
]);
expect([...profiles]).toEqual([
path.resolve('/tmp/playwright_firefoxdev_profile-live'),
path.resolve('/tmp/camoufox-live'),
]);
});
test('cleanup snapshot never adopts another scoped server browser', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'camofox-proc-'));
proc(root, 100, 1, 'node\0server.js');
proc(root, 101, 100, '/usr/bin/Xvfb\0:10');
proc(root, 102, 100, '/cache/camoufox-bin\0-foreground');
proc(root, 200, 1, 'node\0server.js');
proc(root, 201, 200, '/usr/bin/Xvfb\0:20');
proc(root, 202, 200, '/cache/camoufox-bin\0-foreground');
expect(snapshotOwnedBrowserProcesses(100, root).map(p => p.pid)).toEqual([101, 102]);
expect(survivingOwnedBrowserProcesses(snapshotOwnedBrowserProcesses(100, root), root).map(p => p.pid))
.toEqual([101, 102]);
fs.rmSync(root, { recursive: true, force: true });
});
test('only captured descendants survive reparenting; unrelated PID 1 browsers do not', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'camofox-proc-'));
proc(root, 100, 1, 'node\0server.js');
proc(root, 101, 100, '/cache/camoufox-bin', '10');
proc(root, 202, 1, '/cache/camoufox-bin', '20');
const snapshot = snapshotOwnedBrowserProcesses(100, root);
fs.writeFileSync(path.join(root, '101', 'status'), 'Name:\ttest\nPPid:\t1\n');
expect(snapshot.map(p => p.pid)).toEqual([101]);
expect(survivingOwnedBrowserProcesses(snapshot, root).map(p => p.pid)).toEqual([101]);
fs.rmSync(root, { recursive: true, force: true });
});
test('pid reuse is not mistaken for an owned survivor', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'camofox-proc-'));
proc(root, 100, 1, 'node\0server.js');
proc(root, 101, 100, '/cache/camoufox-bin', '10');
const snapshot = snapshotOwnedBrowserProcesses(100, root);
fs.writeFileSync(path.join(root, '101', 'stat'), '101 (test) S 100 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 99');
expect(survivingOwnedBrowserProcesses(snapshot, root)).toEqual([]);
fs.rmSync(root, { recursive: true, force: true });
});
test('process start time parsing tolerates spaces and parentheses in comm', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'camofox-proc-'));
proc(root, 100, 1, 'node\0server.js');
proc(root, 101, 100, '/cache/camoufox-bin', '42', 'Camoufox Worker (GPU)');
const snapshot = snapshotOwnedBrowserProcesses(100, root);
expect(snapshot.map(p => [p.pid, p.startTime])).toEqual([[101, '42']]);
fs.rmSync(root, { recursive: true, force: true });
});