mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
100 lines
4.0 KiB
TypeScript
100 lines
4.0 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import { promisify } from "node:util";
|
|
|
|
import { expect, it } from "vitest";
|
|
|
|
it("retains completed worker facts when cancellation arrives during final job cleanup", async () => {
|
|
const loaderUrl = new URL(
|
|
"../../../scripts/lib/verifier-source-loader.mjs",
|
|
import.meta.url,
|
|
).href;
|
|
const workerUrl = new URL(
|
|
"../../../src/javascript/analysis/JavaScriptAnalysisWorker.ts",
|
|
import.meta.url,
|
|
).href;
|
|
const processUrl = new URL(
|
|
"../../../src/process/ProviderProcess.ts",
|
|
import.meta.url,
|
|
).href;
|
|
// Isolate the filesystem seam in a Node process. Both analyses use the real
|
|
// analyzer and remove their actual files before the cancellation is delivered.
|
|
const script = `
|
|
import fs from 'node:fs';
|
|
import { createHash } from 'node:crypto';
|
|
import { syncBuiltinESMExports } from 'node:module';
|
|
const { importVerifierSource } = await import(${JSON.stringify(loaderUrl)});
|
|
const controller = new AbortController();
|
|
const reason = new Error('cancel during final worker cleanup');
|
|
const nativeRm = fs.promises.rm;
|
|
let cancelOnCleanup = false;
|
|
let completedRemovals = 0;
|
|
fs.promises.rm = async (path, options) => {
|
|
await nativeRm(path, options);
|
|
if (String(path).includes('rea-javascript-analysis-') && String(path).endsWith('.records')) {
|
|
completedRemovals += 1;
|
|
if (cancelOnCleanup) controller.abort(reason);
|
|
}
|
|
};
|
|
syncBuiltinESMExports();
|
|
const { JavaScriptAnalysisWorker } = await importVerifierSource(new URL(${JSON.stringify(workerUrl)}));
|
|
const { spawnOwnedProviderProcess } = await importVerifierSource(new URL(${JSON.stringify(processUrl)}));
|
|
const value = 'export const observed = 7;';
|
|
const source = path => ({ path, sha256: createHash('sha256').update(value).digest('hex'),
|
|
container_sha256: '1'.repeat(64), bytes: Buffer.byteLength(value), inventory_artifact_id: 'fixture-source',
|
|
kind: 'javascript', unpacked: false, text: { included: true, value } });
|
|
let root;
|
|
let child;
|
|
let launches = 0;
|
|
const worker = new JavaScriptAnalysisWorker({ heapMb: 128, timeoutMs: 5000 }, async request => {
|
|
root = request.cwd;
|
|
const spawned = await spawnOwnedProviderProcess(request);
|
|
child = spawned.process;
|
|
launches += 1;
|
|
return spawned;
|
|
});
|
|
let observed;
|
|
try {
|
|
const control = await worker.analyze(source('control.mjs'), { nodes: 100, unknowns: 100 });
|
|
const controlReusable = worker.canReuse();
|
|
cancelOnCleanup = true;
|
|
const cancelled = await worker.analyze(source('cancelled.mjs'), { nodes: 100, unknowns: 100 }, controller.signal);
|
|
const facts = cancelled.ok ? cancelled.value : cancelled.error;
|
|
observed = { controlOk: control.ok, controlReusable, completedRemovals, launches,
|
|
cancelled: !cancelled.ok && cancelled.error.error._tag === 'AnalysisCancelledError',
|
|
exactReason: !cancelled.ok && cancelled.error.error.cause === reason,
|
|
parseStatus: facts.javascript?.parse_status, hasModule: facts.module !== null,
|
|
hasProjection: facts.projection !== null, reusable: worker.canReuse() };
|
|
} finally {
|
|
fs.promises.rm = nativeRm;
|
|
syncBuiltinESMExports();
|
|
await worker.close();
|
|
}
|
|
const rootRemoved = await fs.promises.stat(root).then(() => false, cause => {
|
|
if (cause.code !== 'ENOENT') throw cause;
|
|
return true;
|
|
});
|
|
process.stdout.write(JSON.stringify({ ...observed, rootRemoved,
|
|
childExited: child.exitCode !== null || child.signalCode !== null }));
|
|
`;
|
|
const { stdout } = await promisify(execFile)(
|
|
process.execPath,
|
|
["--input-type=module", "--eval", script],
|
|
{ timeout: 20_000 },
|
|
);
|
|
const observed: unknown = JSON.parse(stdout);
|
|
expect(observed).toEqual({
|
|
controlOk: true,
|
|
controlReusable: true,
|
|
completedRemovals: 2,
|
|
launches: 1,
|
|
cancelled: true,
|
|
exactReason: true,
|
|
parseStatus: "complete",
|
|
hasModule: true,
|
|
hasProjection: true,
|
|
reusable: false,
|
|
rootRemoved: true,
|
|
childExited: true,
|
|
});
|
|
});
|