2026-10-11 07:22:50 +08:00
import { createHash } from "node:crypto" ;
2026-10-13 11:34:54 -03:00
import { execFile } from "node:child_process" ;
2026-10-19 13:12:25 +08:00
import { readFile , readdir } from "node:fs/promises" ;
2026-10-13 11:34:54 -03:00
import { join } from "node:path" ;
import { promisify } from "node:util" ;
2026-10-11 07:22:50 +08:00
import { expect , it } from "vitest" ;
import { JavaScriptAnalysisWorker } from "../../../src/javascript/analysis/JavaScriptAnalysisWorker.js" ;
2026-10-21 19:01:20 +08:00
import { createJavaScriptSourceAnalysis } from "../../../src/artifacts/javascript/JavaScriptSourceAnalysis.js" ;
import { ArtifactResourceScope } from "../../../src/artifacts/ArtifactResourceScope.js" ;
2026-10-11 08:05:08 +08:00
import { cleanupOwnedProcessGroup } from "../../../src/process/ProcessOwnership.js" ;
2026-10-13 11:34:54 -03:00
import type { OwnedProcessGroup } from "../../../src/process/ProcessOwnership.js" ;
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js" ;
2026-10-11 08:05:08 +08:00
import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js" ;
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js" ;
2026-10-11 07:22:50 +08:00
import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js" ;
2026-10-11 07:20:00 -04:00
import { javaScriptWorkerMemoryLimitBytes } from "../../../src/process/JavaScriptWorkerMemoryLimit.js" ;
2026-10-11 07:22:50 +08:00
import type { JavaScriptArtifactFile } from "../../../src/domain/javascript/javascriptArtifactFiles.js" ;
const source = ( path : string , value : string ) : JavaScriptArtifactFile => ({
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 },
});
2026-10-11 07:20:00 -04:00
/** The Job Object limit the worker requests for its 128 MiB heap budget, if any. */
const processLimit = javaScriptWorkerMemoryLimitBytes ( 128 );
2026-10-11 07:22:50 +08:00
it ( "reuses one real owned analyzer while retaining distinct source identities and exported returns" , async () => {
const launches : number [] = [];
2026-10-19 13:12:25 +08:00
let runtimeRoot : string | undefined ;
2026-10-11 07:22:50 +08:00
const worker = new JavaScriptAnalysisWorker (
{ heapMb : 128 , timeoutMs : 30_000 },
async ( request ) => {
2026-10-19 13:12:25 +08:00
runtimeRoot = request . cwd ;
2026-10-11 07:22:50 +08:00
const spawned = await spawnOwnedProviderProcess ( request );
const pid = spawned . process . pid ;
if ( pid === undefined )
throw new Error ( "Owned analyzer has no observed PID" );
launches . push ( pid );
return spawned ;
},
);
try {
const first = source (
"first.mjs" ,
"export function create() { return { observed: 7 }; }" ,
);
const second = source (
"second.mjs" ,
"export function next() { return { observed: 8 }; }" ,
);
for ( const [ index , file ] of [ first , second ]. entries ()) {
2026-10-24 02:34:27 +08:00
const phases : string [] = [];
const result = await worker . analyze (
file ,
{ nodes : 100 , unknowns : 100 },
undefined ,
async ( phase ) => {
phases . push ( phase );
},
);
2026-10-11 07:22:50 +08:00
if ( ! result . ok ) throw result . error . error ;
2026-10-24 02:34:27 +08:00
expect ( phases ). toEqual ([
"parse" ,
"static" ,
"semantic" ,
"projection" ,
"transfer" ,
]);
2026-10-11 07:22:50 +08:00
expect ( result . value . javascript . parse_status ). toBe ( "complete" );
expect (
result . value . module ? . callables [ 0 ] ? . returnSites [ 0 ] ? . value ,
). toMatchObject ({
status : "object" ,
properties : [
{ name : "observed" , value : { status : "literal" , value : index + 7 } },
],
});
expect (
result . value . projection ? . nodes . every (
({ identity }) => identity . artifact_sha256 === file . sha256 ,
),
). toBe ( true );
expect (
result . value . projection ? . nodes . some (
({ identity }) => identity . module_path === file . path ,
),
). toBe ( true );
}
expect ( launches ). toHaveLength ( 1 );
2026-10-19 13:12:25 +08:00
if ( runtimeRoot === undefined )
throw new Error (
"Owned analyzer did not expose its runtime root to launcher" ,
);
expect (
( await readdir ( runtimeRoot )). filter (( name ) =>
/\.(?:source|static\.json|records)$/u.test(name),
),
). toEqual ([]);
2026-10-11 07:22:50 +08:00
} finally {
await worker . close ();
}
});
2026-10-21 19:01:20 +08:00
it ( "reuses one owned worker across analyses on the same resource scope" , async () => {
const scope = new ArtifactResourceScope ();
const launches : number [] = [];
const exits = new Map < number , Promise < void >>();
const launcher : typeof spawnOwnedProviderProcess = async ( request ) => {
const previous = launches . at ( - 1 );
if ( previous !== undefined ) await exits . get ( previous );
const spawned = await spawnOwnedProviderProcess ( request );
const pid = spawned . process . pid ;
if ( pid === undefined )
throw new Error ( "Owned analyzer has no observed PID" );
exits . set (
pid ,
new Promise (( resolve ) => {
spawned . process . once ( "exit" , () => {
resolve ();
});
}),
);
launches . push ( pid );
return spawned ;
};
const limits = { heapMb : 128 , timeoutMs : 30_000 };
const first = source ( "first.mjs" , "export const observed = 1;" );
const second = source ( "second.mjs" , "export const observed = 2;" );
const files = [ first , second ];
try {
for ( const file of files ) {
const worker = await createJavaScriptSourceAnalysis ( limits , 1 , {
scope ,
launcher ,
});
try {
const result = await worker . analyze ( file , {
nodes : 100 ,
unknowns : 100 ,
});
if ( ! result . ok ) throw result . error . error ;
expect ( result . value . javascript . parse_status ). toBe ( "complete" );
} finally {
await worker . close ();
}
}
expect ( launches ). toHaveLength ( 1 );
const replaced = await createJavaScriptSourceAnalysis (
{ heapMb : 256 , timeoutMs : 30_000 },
1 ,
{ scope , launcher },
);
try {
const result = await replaced . analyze ( first , {
nodes : 100 ,
unknowns : 100 ,
});
if ( ! result . ok ) throw result . error . error ;
expect ( result . value . javascript . parse_status ). toBe ( "complete" );
} finally {
await replaced . close ();
}
expect ( launches ). toHaveLength ( 2 );
await scope . close ();
const reopened = await createJavaScriptSourceAnalysis ( limits , 1 , {
scope ,
launcher ,
});
try {
const result = await reopened . analyze ( first , {
nodes : 100 ,
unknowns : 100 ,
});
if ( ! result . ok ) throw result . error . error ;
} finally {
await reopened . close ();
}
expect ( launches ). toHaveLength ( 3 );
} finally {
await scope . close ();
}
});
it ( "keeps a worker parked while a limit change closes the previous process" , async () => {
const scope = new ArtifactResourceScope ();
const launches : number [] = [];
const exited = new Set < number >();
const exits = new Map < number , Promise < void >>();
const launcher : typeof spawnOwnedProviderProcess = async ( request ) => {
const spawned = await spawnOwnedProviderProcess ( request );
const pid = spawned . process . pid ;
if ( pid === undefined )
throw new Error ( "Owned analyzer has no observed PID" );
exits . set (
pid ,
new Promise (( resolve ) => {
spawned . process . once ( "exit" , () => {
exited . add ( pid );
resolve ();
});
}),
);
launches . push ( pid );
return spawned ;
};
const limits = { heapMb : 128 , timeoutMs : 30_000 };
const file = source ( "parked.mjs" , "export const observed = 1;" );
const originalClose = JavaScriptAnalysisWorker . prototype . close ;
let releaseClose = () : void => {};
const gate = new Promise < void >(( resolve ) => {
releaseClose = resolve ;
});
let closeCalls = 0 ;
let markCloseStarted = () : void => {};
const closeStarted = new Promise < void >(( resolve ) => {
markCloseStarted = resolve ;
});
let replacement :
| ReturnType < typeof createJavaScriptSourceAnalysis >
| undefined ;
try {
const initial = await createJavaScriptSourceAnalysis ( limits , 1 , {
scope ,
launcher ,
});
const first = await initial . analyze ( file , { nodes : 100 , unknowns : 100 });
if ( ! first . ok ) throw first . error . error ;
await initial . close ();
const replacedPid = launches [ 0 ];
if ( replacedPid === undefined )
throw new Error ( "Replaced analyzer has no observed PID" );
JavaScriptAnalysisWorker . prototype . close = async function (
this : JavaScriptAnalysisWorker ,
) {
closeCalls += 1 ;
if ( closeCalls === 1 ) {
markCloseStarted ();
await gate ;
}
return originalClose . call ( this );
};
replacement = createJavaScriptSourceAnalysis (
{ heapMb : 256 , timeoutMs : 30_000 },
1 ,
{ scope , launcher },
);
await closeStarted ;
const concurrent = await createJavaScriptSourceAnalysis ( limits , 1 , {
scope ,
launcher ,
});
const concurrentResult = await concurrent . analyze ( file , {
nodes : 100 ,
unknowns : 100 ,
});
if ( ! concurrentResult . ok ) throw concurrentResult . error . error ;
await concurrent . close ();
const parkedPid = launches [ 1 ];
if ( parkedPid === undefined )
throw new Error ( "Parked analyzer has no observed PID" );
releaseClose ();
const extra = await replacement ;
const extraResult = await extra . analyze ( file , {
nodes : 100 ,
unknowns : 100 ,
});
if ( ! extraResult . ok ) throw extraResult . error . error ;
const extraPid = launches [ 2 ];
if ( extraPid === undefined )
throw new Error ( "Replacement analyzer has no observed PID" );
await extra . close ();
await exits . get ( replacedPid );
await exits . get ( extraPid );
expect ( exited . has ( replacedPid )). toBe ( true );
expect ( exited . has ( extraPid )). toBe ( true );
expect ( exited . has ( parkedPid )). toBe ( false );
await scope . close ();
await exits . get ( parkedPid );
expect ( exited . has ( parkedPid )). toBe ( true );
} finally {
JavaScriptAnalysisWorker . prototype . close = originalClose ;
releaseClose ();
const port = await replacement ? . catch (() => undefined );
await port ? . close (). catch (() => undefined );
await scope . close ();
}
});
it ( "does not start a replacement when closing the previous worker fails" , async () => {
const scope = new ArtifactResourceScope ();
const launches : number [] = [];
const exited = new Set < number >();
const exits = new Map < number , Promise < void >>();
const launcher : typeof spawnOwnedProviderProcess = async ( request ) => {
const previous = launches . at ( - 1 );
if ( previous !== undefined ) await exits . get ( previous );
const spawned = await spawnOwnedProviderProcess ( request );
const pid = spawned . process . pid ;
if ( pid === undefined )
throw new Error ( "Owned analyzer has no observed PID" );
exits . set (
pid ,
new Promise (( resolve ) => {
spawned . process . once ( "exit" , () => {
exited . add ( pid );
resolve ();
});
}),
);
launches . push ( pid );
return spawned ;
};
const limits = { heapMb : 128 , timeoutMs : 30_000 };
const file = source ( "replaced.mjs" , "export const observed = 1;" );
const originalClose = JavaScriptAnalysisWorker . prototype . close ;
let closeCalls = 0 ;
try {
const initial = await createJavaScriptSourceAnalysis ( limits , 1 , {
scope ,
launcher ,
});
const first = await initial . analyze ( file , { nodes : 100 , unknowns : 100 });
if ( ! first . ok ) throw first . error . error ;
await initial . close ();
const replacedPid = launches [ 0 ];
if ( replacedPid === undefined )
throw new Error ( "Replaced analyzer has no observed PID" );
JavaScriptAnalysisWorker . prototype . close = async function (
this : JavaScriptAnalysisWorker ,
) {
closeCalls += 1 ;
if ( closeCalls === 1 ) throw new Error ( "worker still owned" );
return originalClose . call ( this );
};
await expect (
createJavaScriptSourceAnalysis ({ heapMb : 256 , timeoutMs : 30_000 }, 1 , {
scope ,
launcher ,
}),
). rejects . toThrow ( "worker still owned" );
expect ( launches ). toEqual ([ replacedPid ]);
expect ( exited . has ( replacedPid )). toBe ( false );
JavaScriptAnalysisWorker . prototype . close = originalClose ;
const replacement = await createJavaScriptSourceAnalysis (
{ heapMb : 256 , timeoutMs : 30_000 },
1 ,
{ scope , launcher },
);
try {
const result = await replacement . analyze ( file , {
nodes : 100 ,
unknowns : 100 ,
});
if ( ! result . ok ) throw result . error . error ;
} finally {
await replacement . close ();
}
expect ( launches ). toHaveLength ( 2 );
expect ( exited . has ( replacedPid )). toBe ( true );
const replacementPid = launches [ 1 ];
if ( replacementPid === undefined )
throw new Error ( "Replacement analyzer has no observed PID" );
expect ( exited . has ( replacementPid )). toBe ( false );
} finally {
JavaScriptAnalysisWorker . prototype . close = originalClose ;
await scope . close ();
}
});
2026-10-11 07:22:50 +08:00
it ( "reports an actual startup deadline and closes its owned resources before subsequent analysis" , async () => {
const file = source ( "app.mjs" , "export const observed = 7;" );
const expired = new JavaScriptAnalysisWorker ({ heapMb : 128 , timeoutMs : 1 });
try {
2026-10-17 21:57:31 +08:00
const result = await expired . analyze ( file , { nodes : 100 , unknowns : 100 });
2026-10-11 07:22:50 +08:00
if ( result . ok )
throw new Error (
"A one-millisecond fresh process deadline unexpectedly completed" ,
);
expect ( result . error . error ). toMatchObject ({
_tag : "AnalysisTimeoutError" ,
timeoutMs : 1 ,
});
} finally {
await expired . close ();
}
const next = new JavaScriptAnalysisWorker ({ heapMb : 128 , timeoutMs : 30_000 });
try {
2026-10-17 21:57:31 +08:00
const result = await next . analyze ( file , { nodes : 100 , unknowns : 100 });
2026-10-11 07:22:50 +08:00
if ( ! result . ok ) throw result . error . error ;
expect ( result . value . javascript . parse_status ). toBe ( "complete" );
} finally {
await next . close ();
}
});
2026-10-11 08:05:08 +08:00
it ( "preserves a failed owned cleanup retry when the next caller cancels before acquisition" , async () => {
const controller = new AbortController ();
let releaseAllowed = false ;
let cleanupAttempts = 0 ;
const worker = new JavaScriptAnalysisWorker (
{ heapMb : 128 , timeoutMs : 30_000 },
async ( request ) => {
const launched = await spawnOwnedProviderProcess ( request );
return {
... launched ,
async cleanup() {
cleanupAttempts += 1 ;
if ( ! releaseAllowed ) {
if ( cleanupAttempts > 1 ) controller . abort ();
throw new Error ( "Fixture owned cleanup is unavailable" );
}
return launched . cleanup === undefined
? cleanupOwnedProcessGroup ( launched . ownership )
: launched . cleanup ();
},
};
},
);
const next = new JavaScriptAnalysisWorker ({ heapMb : 128 , timeoutMs : 30_000 });
try {
const file = source ( "app.mjs" , "export const observed = 7;" );
2026-10-17 21:57:31 +08:00
const first = await worker . analyze ( file , { nodes : 100 , unknowns : 100 });
2026-10-11 08:05:08 +08:00
if ( ! first . ok ) throw first . error . error ;
2026-10-10 18:57:38 -07:00
const closing = worker . close ();
await expect ( closing ). rejects . toBeInstanceOf ( ProviderCleanupError );
await expect ( closing ). rejects . toMatchObject ({
cause : { message : "Fixture owned cleanup is unavailable" },
});
2026-10-17 21:57:31 +08:00
const interrupted = await next . analyze (
file ,
{ nodes : 100 , unknowns : 100 },
controller . signal ,
);
2026-10-11 08:05:08 +08:00
if ( interrupted . ok )
throw new Error ( "Unreleased ownership must block subsequent analysis" );
expect ( controller . signal . aborted ). toBe ( true );
expect ( projectAnalysisError ( interrupted . error . error )). toMatchObject ({
code : "cleanup_incomplete" ,
});
releaseAllowed = true ;
await worker . close ();
2026-10-17 21:57:31 +08:00
const recovered = await next . analyze ( file , { nodes : 100 , unknowns : 100 });
2026-10-11 08:05:08 +08:00
if ( ! recovered . ok ) throw recovered . error . error ;
expect ( recovered . value . javascript . parse_status ). toBe ( "complete" );
} finally {
releaseAllowed = true ;
await worker . close ();
await next . close ();
}
});
2026-10-11 07:20:00 -04:00
2026-10-13 11:34:54 -03:00
it ( "ends worker stdin and unreferences process handles when cleanup reports inspection uncertainty" , async () => {
let stdinEnded = false ;
let unreferenced = false ;
let permitCleanup = false ;
let childStdin : typeof process . stdin | undefined ;
2026-10-14 12:21:22 +08:00
let childExit : Promise < void > | undefined ;
2026-10-13 11:34:54 -03:00
const worker = new JavaScriptAnalysisWorker (
{ heapMb : 128 , timeoutMs : 30_000 },
async ( request ) => {
const launched = await spawnOwnedProviderProcess ( request );
2026-10-14 12:21:22 +08:00
childExit = new Promise (( resolve ) => {
launched . process . once ( "exit" , () => resolve ());
});
2026-10-13 11:34:54 -03:00
const originalUnref = launched . process . unref ;
launched . process . unref = () => {
unreferenced = true ;
originalUnref ? . call ( launched . process );
};
childStdin = launched . process . stdin as unknown as typeof process . stdin ;
launched . process . stdin ? . once ( "finish" , () => {
stdinEnded = true ;
});
return {
... launched ,
cleanup : async () =>
permitCleanup
? launched . cleanup === undefined
? cleanupOwnedProcessGroup ( launched . ownership )
: launched . cleanup ()
: {
cleaned : false ,
reason : "process table could not be inspected: spawn EPERM" ,
},
};
},
);
try {
const file = source ( "app.mjs" , "export const observed = 7;" );
2026-10-17 21:57:31 +08:00
const result = await worker . analyze ( file , { nodes : 100 , unknowns : 100 });
2026-10-13 11:34:54 -03:00
if ( ! result . ok ) throw result . error . error ;
const closing = worker . close ();
await expect ( closing ). rejects . toBeInstanceOf ( ProviderCleanupError );
await expect ( closing ). rejects . toMatchObject ({
cause : {
message : "process table could not be inspected: spawn EPERM" ,
},
});
expect ( childStdin ? . writableEnded || stdinEnded ). toBe ( true );
expect ( unreferenced ). toBe ( true );
const errorListeners = childStdin ? . listenerCount ( "error" );
for ( let retry = 0 ; retry < 3 ; retry ++ )
await expect ( worker . close ()). rejects . toBeInstanceOf ( ProviderCleanupError );
expect ( childStdin ? . listenerCount ( "error" )). toBeLessThanOrEqual (
errorListeners ?? 0 ,
);
2026-10-14 12:21:22 +08:00
await childExit ;
2026-10-13 11:34:54 -03:00
permitCleanup = true ;
await worker . close ();
} finally {
permitCleanup = true ;
await worker . close (). catch (() => {});
}
});
it ( "lets a separate host exit while its live worker cleanup remains incomplete" , async () => {
const root = await createTestTempDirectory ( "rea-worker-incomplete-host-" );
const ownershipPath = join ( root , "ownership.json" );
const workerUrl = new URL (
"../../../dist/javascript/analysis/JavaScriptAnalysisWorker.js" ,
import . meta . url ,
). href ;
const launcherUrl = new URL (
"../../../dist/process/ProviderProcess.js" ,
import . meta . url ,
). href ;
const script = `
import { writeFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
import { JavaScriptAnalysisWorker } from ${ JSON . stringify ( workerUrl ) } ;
import { spawnOwnedProviderProcess } from ${ JSON . stringify ( launcherUrl ) } ;
const worker = new JavaScriptAnalysisWorker({ heapMb: 128, timeoutMs: 10000 }, async request => {
const entry = pathToFileURL(request.arguments.at(-1)).href;
const launched = await spawnOwnedProviderProcess({ ...request, arguments: [
...request.arguments.slice(0, -1), "--input-type=module", "-e",
"const interval = setInterval(() => {}, 1000); setTimeout(() => clearInterval(interval), 60000); await import(" + JSON.stringify(entry) + ");"
] });
// The launcher is deliberately orphaned when this host exits; cleanup still
// requires its run token and executable identity, rather than its old parent.
const { expectedParentPid, ...ownership } = launched.ownership;
await writeFile( ${ JSON . stringify ( ownershipPath ) } , JSON.stringify(ownership));
return { ...launched, cleanup: async () => ({ cleaned: false, reason: "fixture inspection unavailable" }) };
});
2026-10-17 21:57:31 +08:00
const analyzed = await worker.analyze( ${ JSON . stringify ( source ( "app.mjs" , "export const value = 1;" )) } , { nodes: 100, unknowns: 100 });
2026-10-13 11:34:54 -03:00
if (!analyzed.ok) throw analyzed.error.error;
try { await worker.close(); throw new Error("cleanup unexpectedly completed"); }
catch (error) {
if (error.cleanupIncomplete !== true) throw error;
process.stdout.write(JSON.stringify({ code: "cleanup_incomplete", reason: error.cause.message }) + "\\n");
}
` ;
try {
const result = await promisify ( execFile )(
process . execPath ,
[ "--input-type=module" , "-e" , script ],
{ timeout : 10_000 },
);
expect ( JSON . parse ( result . stdout )). toMatchObject ({
code : "cleanup_incomplete" ,
reason : "fixture inspection unavailable" ,
});
} finally {
const ownership = JSON . parse (
await readFile ( ownershipPath , "utf8" ),
) as OwnedProcessGroup ;
expect ( await cleanupOwnedProcessGroup ( ownership )). toMatchObject ({
cleaned : true ,
});
}
});
2026-10-14 16:00:20 +08:00
// The real server reports a parent-declined semantic budget through its
// resource frame.
it ( "formats a real worker's declined semantic budget once" , async () => {
const worker = new JavaScriptAnalysisWorker (
{ heapMb : 128 , timeoutMs : 30_000 },
async ( request ) => {
const spawned = await spawnOwnedProviderProcess ( request );
const stdin = spawned . process . stdin ;
if ( stdin === null || stdin === undefined )
throw new Error ( "Owned analyzer has no stdin" );
const write = stdin . write . bind ( stdin ) as (
chunk : string ,
callback : ( error? : Error | null ) => void ,
) => boolean ;
stdin . write = ((
chunk : string ,
callback : ( error? : Error | null ) => void ,
) =>
write (
chunk . replace (
/"semantic_result_budget_bytes":\d+/u,
'"semantic_result_budget_bytes":0' ,
),
callback ,
)) as typeof stdin . write ;
return spawned ;
},
);
try {
const result = await worker . analyze (
source ( "main.js" , "export const observed = 7;" ),
2026-10-17 21:57:31 +08:00
{ nodes : 100 , unknowns : 100 },
2026-10-14 16:00:20 +08:00
);
if ( result . ok ) throw new Error ( "expected a declined semantic budget" );
const { error } = result . error ;
expect ( error ). toMatchObject ({
_tag : "AnalysisResourceConstraintError" ,
reason :
"The parent reserved its remaining result capacity for static observations; semantic analysis of main.js was not started" ,
reportedLimits : { semantic_result_budget_bytes : 0 },
});
expect ( error . message . match ( /Resource constraint during/gu)).toHaveLength(1);
expect ( error . message . match ( /main\.js:/gu)).toBeNull();
expect ( result . error . javascript ? . parse_status ). toBe ( "complete" );
} finally {
await worker . close ();
}
});
2026-10-11 07:20:00 -04:00
// The handshake mirrors the real server: only Node.js reports an enforced V8 heap limit.
const resourceFailureWorker = ( heapLimit : string ) =>
new JavaScriptAnalysisWorker ({ heapMb : 128 , timeoutMs : 30_000 }, ( request ) =>
spawnOwnedProviderProcess ({
... request ,
arguments : [
"-e" ,
`const heap = ${ heapLimit } ;
process.stdout.write(JSON.stringify({ kind: "ready", heap_limit_bytes: heap }) + "\\n");
require("node:readline").createInterface({ input: process.stdin }).once("line", (line) => {
const { id } = JSON.parse(line);
process.stdout.write(JSON.stringify({ kind: "failure", id, reason: "resource", resource: "memory", limits: null, message: "fixture" }) + "\\n");
});` ,
],
}),
);
it ( "reports the worker heap limit only when the runtime enforces V8 heap flags" , async () => {
const worker = resourceFailureWorker (
'process.versions.bun === undefined ? require("node:v8").getHeapStatistics().heap_size_limit : null' ,
);
try {
const result = await worker . analyze (
source ( "app.mjs" , "export const observed = 7;" ),
2026-10-17 21:57:31 +08:00
{ nodes : 100 , unknowns : 100 },
2026-10-11 07:20:00 -04:00
);
if ( result . ok ) throw new Error ( "expected a reported resource failure" );
expect ( result . error . error ). toMatchObject ({
_tag : "AnalysisResourceConstraintError" ,
reportedLimits :
process.versions.bun === undefined
? {
worker_old_space_mb : 128 ,
worker_heap_limit_bytes : expect.any ( Number ),
worker_timeout_ms : 30_000 ,
}
: {
worker_heap_limit_bytes : null ,
...( processLimit === null
? {}
: { worker_process_memory_limit_bytes : processLimit }),
worker_timeout_ms : 30_000 ,
},
});
} finally {
await worker . close ();
}
});
it ( "rejects a worker heap report that contradicts the launching runtime" , async () => {
const worker = resourceFailureWorker (
"process.versions.bun === undefined ? null : 1" ,
);
try {
const result = await worker . analyze (
source ( "app.mjs" , "export const observed = 7;" ),
2026-10-17 21:57:31 +08:00
{ nodes : 100 , unknowns : 100 },
2026-10-11 07:20:00 -04:00
);
if ( result . ok ) throw new Error ( "expected a handshake failure" );
expect ( result . error . error . _tag ). toBe ( "AnalysisOutputError" );
} finally {
await worker . close ();
}
});
// The launcher runs a real child without a job limit, so any artifact can run
// it, and its handle reports the notification an owned Windows job records
// before exit. Bun exits 3 (panic) or 1 (exception) at a reached job limit.
it . each ([ true , false ])(
"classifies a worker exit by the job memory notification (reached: %s)" ,
async ( reached ) => {
const worker = new JavaScriptAnalysisWorker (
{ heapMb : 128 , timeoutMs : 30_000 },
async ({ memoryLimitBytes , ... request }) => {
expect ( memoryLimitBytes ). toBe ( processLimit ?? undefined );
const spawned = await spawnOwnedProviderProcess ({
... request ,
arguments : [
"-e" ,
`const heap = process.versions.bun === undefined ? require("node:v8").getHeapStatistics().heap_size_limit : null;
process.stdout.write(JSON.stringify({ kind: "ready", heap_limit_bytes: heap }) + "\\n");
require("node:readline").createInterface({ input: process.stdin }).once("line", () => {
process.stderr.write("panic: out of memory");
process.exit(3);
});` ,
],
});
let exited = false ;
spawned . process . once ( "exit" , () => {
exited = true ;
});
Object . defineProperty ( spawned . process , "memoryLimitReached" , {
get : () => reached && exited ,
});
return spawned ;
},
);
try {
const result = await worker . analyze (
source ( "app.mjs" , "export const observed = 7;" ),
2026-10-17 21:57:31 +08:00
{ nodes : 100 , unknowns : 100 },
2026-10-11 07:20:00 -04:00
);
if ( result . ok ) throw new Error ( "expected the worker to fail" );
expect ( result . error . error ). toMatchObject (
reached
? {
_tag : "AnalysisResourceConstraintError" ,
resource : "memory" ,
reason : expect.stringContaining ( "process memory limit" ),
reportedLimits :
processLimit === null
? expect . not . objectContaining ({
worker_process_memory_limit_bytes : expect.anything (),
})
: { worker_process_memory_limit_bytes : processLimit },
capturedOutput : { stderr : "panic: out of memory" },
}
: {
_tag : "ProviderAdapterError" ,
diagnostics : { exit_code : 3 },
},
);
} finally {
await worker . close ();
}
},
);
2026-10-22 16:54:14 +08:00
it ( "retains legacy routes and a bounded route-fact prefix inside one checkpoint budget" , async () => {
const count = 12000 ;
const text = Array . from (
{ length : count },
( _ , index ) => `route('/reports/ ${ index } / ${ "x" . repeat ( 96 ) } ');` ,
). join ( "\n" );
const worker = new JavaScriptAnalysisWorker ({
heapMb : 512 ,
timeoutMs : 30000 ,
});
try {
const analyzed = await worker . analyze ( source ( "routes.js" , text ), {
nodes : 1 ,
unknowns : 8 ,
});
const facts = analyzed . ok
? analyzed.value.javascript
: analyzed.error.javascript ;
expect (
facts ,
analyzed . ok ? undefined : analyzed . error . error . message ,
). not . toBeNull ();
expect ( facts ! . endpoints ). toHaveLength ( count );
expect ( facts ! . routes ! . candidates . length ). toBeGreaterThan ( 0 );
expect ( facts ! . routes ! . candidates . length ). toBeLessThan ( count );
expect ( facts ! . routes ! . capacity ). toMatchObject ({ kind : "result-bytes" });
expect ( Buffer . byteLength ( JSON . stringify ( facts ))). toBeLessThanOrEqual (
8 * 1024 * 1024 ,
);
} finally {
await worker . close ();
}
}, 60000 );
it ( "keeps wrapped route ancestry, navigation and unknown named parents in the real worker" , async () => {
const worker = new JavaScriptAnalysisWorker ({
heapMb : 128 ,
timeoutMs : 30000 ,
});
try {
const analyzed = await worker . analyze (
source (
"routes.ts" ,
"const detail={_$:'detail'};defineRoutes({reports:{_$:'reports',detail:(detail as typeof detail)}});router.push(({pathname:'/users'} as const));router.push(({pathname:'/settings'} satisfies {pathname:string}));router.addRoute('admin',({path:'settings',component:View} as const));router.addRoute('admin',{path:'relative',children:[{path:'/public',component:View},{path:'relative'}]});defineRoutes({generated:buildRoutes()});" ,
),
{ nodes : 1 , unknowns : 8 },
);
if ( ! analyzed . ok ) throw analyzed . error . error ;
expect (
analyzed . value . javascript . routes ! . candidates . map (({ path }) => path ),
). toEqual ([
"/users" ,
"/settings" ,
"/reports" ,
"/reports/detail" ,
"/public" ,
]);
expect ( analyzed . value . javascript . routes ! . unresolved ). toHaveLength ( 3 );
expect ( analyzed . value . javascript . routes ! . unresolved [ 0 ] ! . reason ). toContain (
"parent" ,
);
} finally {
await worker . close ();
}
});