mirror of
https://github.com/morluto/rea
synced 2026-10-27 14:42:18 -04:00
perf(javascript): defer ambiguity expansion in version comparisons (#2962)
perf(javascript): avoid quadratic temporary matching state Repeated source groups can allocate candidates that later identity matches discard. Retain the original ambiguous groups until all tiers finish, expand only unmatched survivors, and append to locally owned key buckets in place. Constraint: Preserve matching priority, full ambiguity, and public output. Directive: Do not re-match residual groups that become unique after later tiers. Tested: 59 relevant tests, npm run check, and test discovery on current main. Tested: CLI benchmarks and full-suite failure comparison on the recorded base. Not-tested: Full suite on the updated main; native Linux and Windows workflows.
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
artifactEvidence,
|
||||
completeCoverage,
|
||||
fixtureNode,
|
||||
} from "./javascriptApplicationGraph.fixture.js";
|
||||
import {
|
||||
createJavaScriptApplicationGraph,
|
||||
type JavaScriptApplicationGraph,
|
||||
} from "./javascriptApplicationGraph.js";
|
||||
import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js";
|
||||
import { compareJavaScriptApplicationVersions } from "./javascriptApplicationVersionComparison.js";
|
||||
|
||||
const ARTIFACT_SHA = "a".repeat(64);
|
||||
|
||||
const moduleNode = (name: string, source: string, structure?: string) =>
|
||||
fixtureNode(
|
||||
"javascript-module",
|
||||
{
|
||||
strategy: "canonical-path",
|
||||
stability: "artifact-version",
|
||||
artifact_sha256: ARTIFACT_SHA,
|
||||
path: `${name}.js`,
|
||||
},
|
||||
name,
|
||||
{
|
||||
source_sha256: source.repeat(64),
|
||||
...(structure === undefined
|
||||
? {}
|
||||
: {
|
||||
structural_fingerprint_sha256: structure.repeat(64),
|
||||
structural_fingerprint_algorithm: "fixture",
|
||||
}),
|
||||
},
|
||||
artifactEvidence(ARTIFACT_SHA, `${name}.js`),
|
||||
);
|
||||
|
||||
const graph = (nodes: ApplicationNode[]): JavaScriptApplicationGraph =>
|
||||
createJavaScriptApplicationGraph({
|
||||
schema: "JavaScriptApplicationGraph",
|
||||
root_node_ids: nodes.map(({ node_id }) => node_id),
|
||||
nodes,
|
||||
edges: [],
|
||||
coverage: completeCoverage,
|
||||
limitations: [],
|
||||
});
|
||||
|
||||
const compare = (
|
||||
left: JavaScriptApplicationGraph,
|
||||
right: JavaScriptApplicationGraph,
|
||||
) =>
|
||||
compareJavaScriptApplicationVersions({
|
||||
left: {
|
||||
evidenceId: `ev_${"1".repeat(64)}`,
|
||||
rootArtifactSha256: ARTIFACT_SHA,
|
||||
graph: left,
|
||||
},
|
||||
right: {
|
||||
evidenceId: `ev_${"2".repeat(64)}`,
|
||||
rootArtifactSha256: ARTIFACT_SHA,
|
||||
graph: right,
|
||||
},
|
||||
leftNativeEvidence: [],
|
||||
rightNativeEvidence: [],
|
||||
});
|
||||
|
||||
describe("application version matching", () => {
|
||||
it("does not revisit a source group that becomes unique after identity matching", () => {
|
||||
const shared = moduleNode("shared", "1");
|
||||
const leftOnly = moduleNode("left", "1");
|
||||
const rightOnly = moduleNode("right", "1");
|
||||
const result = compare(
|
||||
graph([shared, leftOnly]),
|
||||
graph([shared, rightOnly]),
|
||||
);
|
||||
|
||||
expect(result.matching).toMatchObject({
|
||||
exact_node_identity: 1,
|
||||
exact_module_source_digest: 0,
|
||||
ambiguous: 2,
|
||||
unmatched: 0,
|
||||
});
|
||||
expect(
|
||||
result.items.find(({ left_node_id }) => left_node_id === shared.node_id),
|
||||
).toMatchObject({
|
||||
right_node_id: shared.node_id,
|
||||
match: { status: "matched", basis: "exact-node-identity" },
|
||||
});
|
||||
expect(
|
||||
result.items.find(
|
||||
({ left_node_id }) => left_node_id === leftOnly.node_id,
|
||||
),
|
||||
).toMatchObject({
|
||||
status: "unknown",
|
||||
right_node_id: null,
|
||||
match: {
|
||||
status: "ambiguous",
|
||||
candidate_right_node_ids: [rightOnly.node_id],
|
||||
},
|
||||
});
|
||||
expect(
|
||||
result.items.find(
|
||||
({ right_node_id }) => right_node_id === rightOnly.node_id,
|
||||
),
|
||||
).toMatchObject({
|
||||
status: "unknown",
|
||||
left_node_id: null,
|
||||
match: {
|
||||
status: "ambiguous",
|
||||
candidate_left_node_ids: [leftOnly.node_id],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("unions all unresolved source and structural candidates without forcing pairs", () => {
|
||||
const a = moduleNode("a", "1", "a");
|
||||
const b = moduleNode("b", "1", "a");
|
||||
const c = moduleNode("c", "2", "a");
|
||||
const x = moduleNode("x", "1", "a");
|
||||
const y = moduleNode("y", "1", "b");
|
||||
const z = moduleNode("z", "3", "a");
|
||||
const left = graph([a, b, c]);
|
||||
const right = graph([x, y, z]);
|
||||
const result = compare(left, right);
|
||||
|
||||
expect(result.summary).toEqual({
|
||||
unchanged: 0,
|
||||
added: 0,
|
||||
removed: 0,
|
||||
changed: 0,
|
||||
unknown: 6,
|
||||
});
|
||||
expect(result.matching).toMatchObject({ ambiguous: 6, unmatched: 0 });
|
||||
for (const [node, candidates] of [
|
||||
[a, [x, y, z]],
|
||||
[b, [x, y, z]],
|
||||
[c, [x, z]],
|
||||
] as const) {
|
||||
expect(
|
||||
result.items.find(({ left_node_id }) => left_node_id === node.node_id)
|
||||
?.match,
|
||||
).toEqual({
|
||||
status: "ambiguous",
|
||||
basis: "none",
|
||||
confidence: "unknown",
|
||||
candidate_left_node_ids: [],
|
||||
candidate_right_node_ids: candidates
|
||||
.map(({ node_id }) => node_id)
|
||||
.sort(),
|
||||
});
|
||||
}
|
||||
for (const [node, candidates] of [
|
||||
[x, [a, b, c]],
|
||||
[y, [a, b]],
|
||||
[z, [a, b, c]],
|
||||
] as const) {
|
||||
expect(
|
||||
result.items.find(({ right_node_id }) => right_node_id === node.node_id)
|
||||
?.match,
|
||||
).toEqual({
|
||||
status: "ambiguous",
|
||||
basis: "none",
|
||||
confidence: "unknown",
|
||||
candidate_left_node_ids: candidates
|
||||
.map(({ node_id }) => node_id)
|
||||
.sort(),
|
||||
candidate_right_node_ids: [],
|
||||
});
|
||||
}
|
||||
|
||||
// Permute the already validated graph so matching sees the changed order.
|
||||
// Compare the full result, including item, comparison and change-graph IDs.
|
||||
expect(
|
||||
compare(
|
||||
{ ...left, nodes: [...left.nodes].reverse() },
|
||||
{ ...right, nodes: [...right.nodes].reverse() },
|
||||
),
|
||||
).toEqual(result);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user