fix(javascript): isolate source analysis and retain bounded partial evidence (#1651)
* refactor(javascript): validate transferable file projections * test(javascript): consume decoded module and static facts * fix(javascript): isolate source analysis and retain bounded partial evidence * test(javascript): verify cleanup retention and repeated source paths
This commit is contained in:
1 parent
92cc0edd8e
commit
4999e4be2c
41 files changed
+3748
-176
No files matched your search
@@ -0,0 +1,93 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { expect, it } from "vitest";
|
||||
import { JavaScriptAnalysisWorker } from "../../../src/javascript/analysis/JavaScriptAnalysisWorker.js";
|
||||
import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js";
|
||||
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 },
|
||||
});
|
||||
|
||||
it("reuses one real owned analyzer while retaining distinct source identities and exported returns", async () => {
|
||||
const launches: number[] = [];
|
||||
const worker = new JavaScriptAnalysisWorker(
|
||||
{ heapMb: 128, timeoutMs: 30_000 },
|
||||
async (request) => {
|
||||
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()) {
|
||||
const result = await worker.analyze(file, 100);
|
||||
if (!result.ok) throw result.error.error;
|
||||
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);
|
||||
} finally {
|
||||
await worker.close();
|
||||
}
|
||||
});
|
||||
|
||||
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 {
|
||||
const result = await expired.analyze(file, 100);
|
||||
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 {
|
||||
const result = await next.analyze(file, 100);
|
||||
if (!result.ok) throw result.error.error;
|
||||
expect(result.value.javascript.parse_status).toBe("complete");
|
||||
} finally {
|
||||
await next.close();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user