Files
rea/src/application/javascript/JavaScriptArtifactAnalysis.order.test.ts
T

159 lines
5.3 KiB
TypeScript

import { expect, it } from "vitest";
import {
analyzeAndAdoptJavaScriptArtifactFiles,
javaScriptAnalysisOrder,
} from "./JavaScriptArtifactAnalysis.js";
import { createJavaScriptSemanticGraphProjection } from "./JavaScriptSemanticGraphBuilder.js";
import { AnalysisResourceConstraintError } from "../../domain/analysisErrorCore.js";
import { analyzeJavaScriptStaticSource } from "../../../tests/fixtures/javascriptStaticAnalysis.js";
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
import { err, ok } from "../../domain/result.js";
const source = (path: string): JavaScriptArtifactFile => ({
path,
container_sha256: "a".repeat(64),
sha256: "b".repeat(64),
bytes: 20,
inventory_artifact_id: path,
kind: "javascript",
unpacked: false,
text: { included: true, value: "export const x = 1;" },
});
const manifest = (path: string): JavaScriptArtifactFile => ({
...source(path),
kind: "package-json",
text: { included: true, value: "{}" },
});
// Lexical inventory order puts `node_modules/` before an electron-vite `out-*`
// directory, which is the shape that exhausted the result budget in #9321.
const inventory = [
source("node_modules/a/index.js"),
manifest("node_modules/a/package.json"),
source("node_modules/b/lib/yaml.js"),
source("out-global-online/main/index.js"),
source("out-global-online/preload/index.js"),
manifest("package.json"),
];
it("offers application JavaScript before dependency JavaScript", () => {
expect(javaScriptAnalysisOrder(inventory).map(({ path }) => path)).toEqual([
"node_modules/a/package.json",
"out-global-online/main/index.js",
"out-global-online/preload/index.js",
"package.json",
"node_modules/a/index.js",
"node_modules/b/lib/yaml.js",
]);
// A package merely named like a dependency directory is not one.
expect(
javaScriptAnalysisOrder([
source("node_modules.js"),
source("src/node_modules_shim.js"),
]).map(({ path }) => path),
).toEqual(["node_modules.js", "src/node_modules_shim.js"]);
});
it("spends a limited result budget on first-party sources and keeps inventory order", async () => {
const javascript = analyzeJavaScriptStaticSource("export const x = 1;");
const analyzed: string[] = [];
// Admit two sources, then report the budget as spent, as the parent does
// once its remaining result capacity is reserved for static observations.
const worker = {
analyze: async (file: JavaScriptArtifactFile) => {
analyzed.push(file.path);
const value = { javascript, module: null, projection: null };
if (analyzed.length <= 2) return ok(value);
return err({
...value,
error: new AnalysisResourceConstraintError(
"analyze_javascript_application",
"memory",
`semantic analysis of ${file.path} was not started`,
{ semantic_result_budget_bytes: 0 },
),
});
},
};
const outcome = await analyzeAndAdoptJavaScriptArtifactFiles(
{
files: inventory,
unavailable_relevant_files: [],
containers: [],
text_bytes_read: 120,
invalid_utf8_files: 0,
},
worker,
createJavaScriptSemanticGraphProjection(),
async () => {},
);
expect(analyzed).toEqual([
"out-global-online/main/index.js",
"out-global-online/preload/index.js",
"node_modules/a/index.js",
"node_modules/b/lib/yaml.js",
]);
expect(outcome.analysis.files.map(({ file }) => file.path)).toEqual(
inventory.map(({ path }) => path),
);
const failed = outcome.analysis.files
.filter(({ analysis_failure }) => analysis_failure !== undefined)
.map(({ file }) => file.path);
expect(failed).toEqual([
"node_modules/a/index.js",
"node_modules/b/lib/yaml.js",
]);
expect(outcome.interruption).toBeNull();
});
it("spends capacity in the caller's requested order for explicitly selected sources", async () => {
const files = [
source("node_modules/selected/index.js"),
source("src/main.js"),
];
const admitted: string[] = [];
const javascript = analyzeJavaScriptStaticSource("export const x = 1;");
const outcome = await analyzeAndAdoptJavaScriptArtifactFiles(
{
files,
unavailable_relevant_files: [],
containers: [],
text_bytes_read: 40,
invalid_utf8_files: 0,
source_selection: {
requested_paths: files.map((file) => file.path),
unselected_files: 0,
},
},
{
analyze: async (file) => {
const value = { javascript, module: null, projection: null };
admitted.push(file.path);
return admitted.length === 1
? ok(value)
: err({
...value,
error: new AnalysisResourceConstraintError(
"analyze_javascript_application",
"memory",
"result capacity exhausted",
{ semantic_result_budget_bytes: 0 },
),
});
},
},
createJavaScriptSemanticGraphProjection(),
async () => {},
);
expect(admitted).toEqual(files.map((file) => file.path));
expect(
outcome.analysis.files.find(({ file }) => file.path === files[0]!.path)
?.analysis_failure,
).toBeUndefined();
expect(
outcome.analysis.files.find(({ file }) => file.path === files[1]!.path)
?.analysis_failure,
).toBeDefined();
});