test(process): stress real forkpty cleanup
This commit is contained in:
parent
f762790e30
commit
dc9bdb0175
|
|
@ -0,0 +1,33 @@
|
|||
name: Process cleanup stress
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/process-cleanup-stress.yml"
|
||||
- "scripts/verify-process-cleanup-stress.mjs"
|
||||
- "src/application/ProcessCaptureLifecycle.ts"
|
||||
- "src/application/ProcessHarness.ts"
|
||||
- "src/application/ProcessOwnership.ts"
|
||||
- "tests/fixtures/processFidelity.mjs"
|
||||
- "package.json"
|
||||
- "package-lock.json"
|
||||
schedule:
|
||||
- cron: "17 4 * * 1"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
real-forkpty-cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run verify:process:stress
|
||||
|
|
@ -71,6 +71,7 @@
|
|||
"typecheck": "tsc --noEmit",
|
||||
"verify:hopper": "npm run build && node scripts/verify-real-hopper.mjs",
|
||||
"verify:browser": "npm run build && node scripts/verify-real-browser.mjs",
|
||||
"verify:process:stress": "npm run build && node scripts/verify-process-cleanup-stress.mjs",
|
||||
"verify:hopper:linux": "npm run build && node scripts/verify-real-hopper-linux.mjs",
|
||||
"verify:setup:linux": "npm run build && node scripts/verify-linux-setup-e2e.mjs",
|
||||
"build:fixtures": "node scripts/build-conformance-fixtures.mjs",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { execFile } from "node:child_process";
|
||||
import { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
import {
|
||||
captureProcessScenario,
|
||||
probeProcessCaptureCapability,
|
||||
} from "../dist/application/ProcessHarness.js";
|
||||
import { parseProcessScenario } from "../dist/domain/processScenario.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const fixture = fileURLToPath(
|
||||
new URL("../tests/fixtures/processFidelity.mjs", import.meta.url),
|
||||
);
|
||||
const iterations = Number.parseInt(
|
||||
process.env.REA_PROCESS_STRESS_RUNS ?? "25",
|
||||
10,
|
||||
);
|
||||
|
||||
if (process.platform !== "linux")
|
||||
throw new Error("Real forkpty cleanup stress verification requires Linux");
|
||||
if (!Number.isSafeInteger(iterations) || iterations < 1 || iterations > 250)
|
||||
throw new Error("REA_PROCESS_STRESS_RUNS must be an integer from 1 to 250");
|
||||
const capability = await probeProcessCaptureCapability();
|
||||
if (!capability.available)
|
||||
throw new Error(`Process capture unavailable: ${capability.reason}`);
|
||||
|
||||
for (let iteration = 1; iteration <= iterations; iteration += 1) {
|
||||
const result = await captureProcessScenario(
|
||||
parseProcessScenario({
|
||||
approved: true,
|
||||
executable: process.execPath,
|
||||
arguments: [fixture, "tree"],
|
||||
working_directory: dirname(fixture),
|
||||
timeout_ms: 1_000,
|
||||
idle_timeout_ms: 2_000,
|
||||
settle_ms: 50,
|
||||
}),
|
||||
{
|
||||
enabled: true,
|
||||
executableRoots: [dirname(process.execPath)],
|
||||
workingRoots: [dirname(fixture)],
|
||||
allowedEnvironment: [],
|
||||
allowExternalNetwork: true,
|
||||
},
|
||||
);
|
||||
if (!result.ok)
|
||||
throw new Error(`forkpty cleanup run ${String(iteration)} failed`, {
|
||||
cause: result.error,
|
||||
});
|
||||
if (
|
||||
result.value.cleanup.owned_process_group !== "verified" ||
|
||||
result.value.cleanup.temporary_root !== "removed"
|
||||
)
|
||||
throw new Error(`forkpty cleanup run ${String(iteration)} was incomplete`);
|
||||
const commands = result.value.process_samples.map(({ command }) => command);
|
||||
if (!commands.some((command) => command.includes("tree-child")))
|
||||
throw new Error(
|
||||
`forkpty cleanup run ${String(iteration)} missed its child`,
|
||||
);
|
||||
|
||||
const { stdout } = await execFileAsync("ps", ["-axo", "command="]);
|
||||
if (
|
||||
stdout.includes(`${fixture} tree-child`) ||
|
||||
stdout.includes(`${fixture} tree-grandchild`)
|
||||
)
|
||||
throw new Error(
|
||||
`forkpty cleanup run ${String(iteration)} leaked a process`,
|
||||
);
|
||||
}
|
||||
|
||||
process.stdout.write(
|
||||
`${JSON.stringify({ platform: process.platform, iterations, verified: true })}\n`,
|
||||
);
|
||||
Loading…
Reference in New Issue