mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 20:06:14 +00:00
perf(javascript): index version relationships once per comparison (#1347)
relationshipSignature rebuilt a node map and scanned every graph edge for each side of each matched pair, so comparison cost grew as pairs x (nodes + edges). Build node and adjacency indexes once per graph in ItemContext; signatures, sorting and output are unchanged. At 4000 matched modules, end-to-end compare: 5.7s -> 2.9s (2 edges per node) and 10.4s -> 5.8s (6 edges per node); 8000: 3.3x and 2.5x. Output JSON is byte-identical on the base commit.
This commit is contained in:
1 parent
3f59a84bd1
commit
b1e85ad711
2 files changed
+232
-27
No files matched your search
@@ -0,0 +1,175 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
createJavaScriptApplicationEdge,
|
||||
createJavaScriptApplicationGraph,
|
||||
createJavaScriptApplicationNode,
|
||||
type JavaScriptApplicationGraph,
|
||||
} from "./javascriptApplicationGraph.js";
|
||||
import {
|
||||
artifactEvidence,
|
||||
completeCoverage,
|
||||
} from "./javascriptApplicationGraph.fixture.js";
|
||||
import { compareJavaScriptApplicationVersions } from "./javascriptApplicationVersionComparison.js";
|
||||
|
||||
const LEFT_SHA = "a".repeat(64);
|
||||
const RIGHT_SHA = "b".repeat(64);
|
||||
const NAMES = ["a", "b", "c", "d"] as const;
|
||||
type ModuleName = (typeof NAMES)[number];
|
||||
type Relation = "imports" | "contains";
|
||||
type EdgeSpec = readonly [ModuleName, ModuleName, Relation];
|
||||
|
||||
const sourceDigest = (name: string): string =>
|
||||
name.charCodeAt(0).toString(16).padStart(64, "0");
|
||||
|
||||
const buildGraph = (
|
||||
artifactSha: string,
|
||||
edges: readonly EdgeSpec[],
|
||||
order: "forward" | "reverse" = "forward",
|
||||
): JavaScriptApplicationGraph => {
|
||||
const names = order === "forward" ? [...NAMES] : [...NAMES].reverse();
|
||||
const nodes = names.map((name) =>
|
||||
createJavaScriptApplicationNode({
|
||||
kind: "javascript-module",
|
||||
identity: {
|
||||
strategy: "canonical-path",
|
||||
stability: "artifact-version",
|
||||
artifact_sha256: artifactSha,
|
||||
path: `${name}.js`,
|
||||
},
|
||||
observations: [
|
||||
{
|
||||
label: `${name}.js`,
|
||||
properties: { source_sha256: sourceDigest(name) },
|
||||
evidence: artifactEvidence(artifactSha, `${name}.js`),
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
const idOf = (name: ModuleName): string => {
|
||||
const node = nodes.find(({ identity }) =>
|
||||
identity.strategy === "canonical-path"
|
||||
? identity.path === `${name}.js`
|
||||
: false,
|
||||
);
|
||||
if (node === undefined) throw new Error(`Missing fixture node ${name}`);
|
||||
return node.node_id;
|
||||
};
|
||||
const ordered = order === "forward" ? [...edges] : [...edges].reverse();
|
||||
return createJavaScriptApplicationGraph({
|
||||
schema: "JavaScriptApplicationGraph",
|
||||
root_node_ids: [idOf("a")],
|
||||
nodes,
|
||||
edges: ordered.map(([source, target, relation]) =>
|
||||
createJavaScriptApplicationEdge({
|
||||
source_node_id: idOf(source),
|
||||
target_node_id: idOf(target),
|
||||
relation,
|
||||
properties: {},
|
||||
evidence: artifactEvidence(artifactSha, `${source}.js`),
|
||||
}),
|
||||
),
|
||||
coverage: completeCoverage,
|
||||
limitations: [],
|
||||
});
|
||||
};
|
||||
|
||||
const relationshipChanges = (
|
||||
left: JavaScriptApplicationGraph,
|
||||
right: JavaScriptApplicationGraph,
|
||||
): string[] => {
|
||||
const result = compareJavaScriptApplicationVersions({
|
||||
left: {
|
||||
evidenceId: `ev_${"1".repeat(64)}`,
|
||||
rootArtifactSha256: LEFT_SHA,
|
||||
graph: left,
|
||||
},
|
||||
right: {
|
||||
evidenceId: `ev_${"2".repeat(64)}`,
|
||||
rootArtifactSha256: RIGHT_SHA,
|
||||
graph: right,
|
||||
},
|
||||
leftNativeEvidence: [],
|
||||
rightNativeEvidence: [],
|
||||
});
|
||||
expect(result.summary.added + result.summary.removed).toBe(0);
|
||||
return result.items
|
||||
.filter(({ dimensions }) => dimensions.includes("relationships"))
|
||||
.map(({ left_node_id: id }) => {
|
||||
const node = left.nodes.find(({ node_id: nodeId }) => nodeId === id);
|
||||
const label = node?.observations[0]?.label;
|
||||
if (label === undefined || label === null)
|
||||
throw new Error("Changed item has no node");
|
||||
return label;
|
||||
})
|
||||
.sort();
|
||||
};
|
||||
|
||||
const BASE_EDGES: readonly EdgeSpec[] = [
|
||||
["a", "b", "imports"],
|
||||
["b", "c", "imports"],
|
||||
["a", "c", "contains"],
|
||||
["c", "b", "imports"],
|
||||
];
|
||||
|
||||
describe("application version relationship comparison", () => {
|
||||
it("reports no relationship change for identical topology in any node or edge order", () => {
|
||||
expect(
|
||||
relationshipChanges(
|
||||
buildGraph(LEFT_SHA, BASE_EDGES),
|
||||
buildGraph(RIGHT_SHA, BASE_EDGES, "reverse"),
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("flags both endpoints when an edge relation changes", () => {
|
||||
const changed: readonly EdgeSpec[] = [
|
||||
["a", "b", "imports"],
|
||||
["b", "c", "contains"],
|
||||
["a", "c", "contains"],
|
||||
["c", "b", "imports"],
|
||||
];
|
||||
expect(
|
||||
relationshipChanges(
|
||||
buildGraph(LEFT_SHA, BASE_EDGES),
|
||||
buildGraph(RIGHT_SHA, changed),
|
||||
),
|
||||
).toEqual(["b.js", "c.js"]);
|
||||
});
|
||||
|
||||
it("flags a node whose only change is an incoming edge", () => {
|
||||
const left: readonly EdgeSpec[] = [["a", "b", "imports"]];
|
||||
const right: readonly EdgeSpec[] = [
|
||||
["a", "b", "imports"],
|
||||
["c", "b", "imports"],
|
||||
];
|
||||
expect(
|
||||
relationshipChanges(
|
||||
buildGraph(LEFT_SHA, left),
|
||||
buildGraph(RIGHT_SHA, right),
|
||||
),
|
||||
).toEqual(["b.js", "c.js"]);
|
||||
});
|
||||
|
||||
it("flags a node when an edge is re-pointed at a different matched neighbour", () => {
|
||||
const left: readonly EdgeSpec[] = [["a", "b", "imports"]];
|
||||
const right: readonly EdgeSpec[] = [["a", "c", "imports"]];
|
||||
expect(
|
||||
relationshipChanges(
|
||||
buildGraph(LEFT_SHA, left),
|
||||
buildGraph(RIGHT_SHA, right),
|
||||
),
|
||||
).toEqual(["a.js", "b.js", "c.js"]);
|
||||
});
|
||||
|
||||
it("distinguishes edge direction between the same pair", () => {
|
||||
const left: readonly EdgeSpec[] = [["a", "b", "imports"]];
|
||||
const right: readonly EdgeSpec[] = [["b", "a", "imports"]];
|
||||
expect(
|
||||
relationshipChanges(
|
||||
buildGraph(LEFT_SHA, left),
|
||||
buildGraph(RIGHT_SHA, right),
|
||||
),
|
||||
).toEqual(["a.js", "b.js"]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user