Refactor graph-related test code (#1179)
This commit adds new helper methods for creating test nodes (`node` and `partsNode`) and for creating test edges (`edge` and `partsEdge`) to graphTestUtil.js. This is very helpful in light of work related to #1136. I'm going to change the concept of "node" from a raw address to an object, add fields to that object, and add fields to the `Edge` type. If done naively, we would need to change all the test code across the project for every one of those changes. By centralizing the creation of test nodes and edges behind the new functions, we can update all the test code in a single place. This change is trivial from a conceputal perspective, and very broad-reaching from a code-touching perspective. It should be easy to review, because if tests pass then the change is probably working as intended. :) Test plan: `yarn test`
This commit is contained in:
parent
e916bc91c8
commit
e493af2307
|
@ -16,6 +16,7 @@ import type {
|
|||
import * as RepoIdRegistry from "../core/repoIdRegistry";
|
||||
import {makeRepoId, type RepoId} from "../core/repoId";
|
||||
import {loadGraph} from "./loadGraph";
|
||||
import {node} from "../core/graphTestUtil";
|
||||
|
||||
const declaration = (name) => ({
|
||||
name,
|
||||
|
@ -115,8 +116,8 @@ describe("analysis/loadGraph", () => {
|
|||
expect(result).toEqual({status: "REPO_NOT_LOADED"});
|
||||
});
|
||||
it("returns status:SUCCESS with merged graph on success", async () => {
|
||||
const g1 = new Graph().addNode(NodeAddress.fromParts(["g1"]));
|
||||
const g2 = new Graph().addNode(NodeAddress.fromParts(["g2"]));
|
||||
const g1 = new Graph().addNode(node("n1"));
|
||||
const g2 = new Graph().addNode(node("n2"));
|
||||
const m1 = new MockStaticAdapter("foo", g1);
|
||||
const m2 = new MockStaticAdapter("bar", g2);
|
||||
const mergedGraph = Graph.merge([g1, g2]);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import {EdgeAddress, Graph, NodeAddress, edgeToStrings} from "../core/graph";
|
||||
import {Graph, NodeAddress, edgeToStrings} from "../core/graph";
|
||||
import {
|
||||
distributionToNodeDistribution,
|
||||
createConnections,
|
||||
|
@ -17,7 +17,7 @@ import {
|
|||
} from "./pagerankNodeDecomposition";
|
||||
import * as MapUtil from "../util/map";
|
||||
|
||||
import {advancedGraph} from "../core/graphTestUtil";
|
||||
import {advancedGraph, node, edge} from "../core/graphTestUtil";
|
||||
|
||||
/**
|
||||
* Format a decomposition to be shown in a snapshot. This converts
|
||||
|
@ -117,13 +117,13 @@ function validateDecomposition(decomposition) {
|
|||
describe("analysis/pagerankNodeDecomposition", () => {
|
||||
describe("decompose", () => {
|
||||
it("has the expected output on a simple asymmetric chain", async () => {
|
||||
const n1 = NodeAddress.fromParts(["n1"]);
|
||||
const n2 = NodeAddress.fromParts(["n2"]);
|
||||
const n3 = NodeAddress.fromParts(["sink"]);
|
||||
const e1 = {src: n1, dst: n2, address: EdgeAddress.fromParts(["e1"])};
|
||||
const e2 = {src: n2, dst: n3, address: EdgeAddress.fromParts(["e2"])};
|
||||
const e3 = {src: n1, dst: n3, address: EdgeAddress.fromParts(["e3"])};
|
||||
const e4 = {src: n3, dst: n3, address: EdgeAddress.fromParts(["e4"])};
|
||||
const n1 = node("n1");
|
||||
const n2 = node("n2");
|
||||
const n3 = node("sink");
|
||||
const e1 = edge("e1", n1, n2);
|
||||
const e2 = edge("e2", n2, n3);
|
||||
const e3 = edge("e3", n1, n3);
|
||||
const e4 = edge("e4", n3, n3);
|
||||
const g = new Graph()
|
||||
.addNode(n1)
|
||||
.addNode(n2)
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
import {run} from "./testUtil";
|
||||
import {help, makeExportGraph} from "./exportGraph";
|
||||
import {Graph, NodeAddress} from "../core/graph";
|
||||
import {Graph} from "../core/graph";
|
||||
import {node} from "../core/graphTestUtil";
|
||||
import stringify from "json-stable-stringify";
|
||||
|
||||
import {makeRepoId} from "../core/repoId";
|
||||
|
@ -61,7 +62,7 @@ describe("cli/exportGraph", () => {
|
|||
});
|
||||
|
||||
it("on load success, prints the stringified graph to stdout", async () => {
|
||||
const graph = new Graph().addNode(NodeAddress.empty);
|
||||
const graph = new Graph().addNode(node("n"));
|
||||
const loadGraphResult = {status: "SUCCESS", graph};
|
||||
const exportGraph = makeExportGraph(
|
||||
(_unused_repoId) => new Promise((resolve) => resolve(loadGraphResult))
|
||||
|
|
|
@ -314,7 +314,8 @@ export async function saveTimestamps(
|
|||
const adapters = await Promise.all(
|
||||
adapterLoaders.map((a) => a.load(Common.sourcecredDirectory(), repoId))
|
||||
);
|
||||
const timestampMap = createTimestampMap(graph.nodes(), adapters);
|
||||
const nodeAddresses = Array.from(graph.nodes());
|
||||
const timestampMap = createTimestampMap(nodeAddresses, adapters);
|
||||
writeTimestampMap(timestampMap, Common.sourcecredDirectory(), repoId);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
defaultSaver,
|
||||
} from "./pagerank";
|
||||
import {Graph, NodeAddress, EdgeAddress} from "../core/graph";
|
||||
import {advancedGraph} from "../core/graphTestUtil";
|
||||
import {node, edge, advancedGraph} from "../core/graphTestUtil";
|
||||
import {
|
||||
PagerankGraph,
|
||||
DEFAULT_SYNTHETIC_LOOP_WEIGHT,
|
||||
|
@ -127,7 +127,7 @@ describe("cli/pagerank", () => {
|
|||
});
|
||||
|
||||
describe("on successful load", () => {
|
||||
const graph = () => new Graph().addNode(NodeAddress.empty);
|
||||
const graph = () => new Graph().addNode(node("n"));
|
||||
const graphResult = () => ({status: "SUCCESS", graph: graph()});
|
||||
const loader = (_unused_repoId) =>
|
||||
new Promise((resolve) => resolve(graphResult()));
|
||||
|
@ -169,7 +169,7 @@ describe("cli/pagerank", () => {
|
|||
|
||||
describe("savePagerankGraph", () => {
|
||||
it("saves the PagerankGraphJSON to the right filepath", async () => {
|
||||
const graph = new Graph().addNode(NodeAddress.empty);
|
||||
const graph = new Graph().addNode(node("n"));
|
||||
const evaluator = (_unused_edge) => ({toWeight: 1, froWeight: 2});
|
||||
const prg = new PagerankGraph(graph, evaluator);
|
||||
const dirname = tmp.dirSync().name;
|
||||
|
@ -229,11 +229,9 @@ describe("cli/pagerank", () => {
|
|||
expect(actualPagerankGraph.equals(expectedPagerankGraph)).toBe(true);
|
||||
});
|
||||
it("default pageRank is robust to nodes that are not owned by any plugin", async () => {
|
||||
const graph = new Graph().addNode(NodeAddress.empty).addEdge({
|
||||
address: EdgeAddress.empty,
|
||||
src: NodeAddress.empty,
|
||||
dst: NodeAddress.empty,
|
||||
});
|
||||
const n = node("n");
|
||||
const e = edge("no-plugin", n, n);
|
||||
const graph = new Graph().addNode(n).addEdge(e);
|
||||
await defaultPagerank(graph);
|
||||
});
|
||||
});
|
||||
|
@ -242,8 +240,11 @@ describe("cli/pagerank", () => {
|
|||
process.env.SOURCECRED_DIRECTORY = dirname;
|
||||
const repoId = makeRepoId("foo", "bar");
|
||||
const prg = new PagerankGraph(
|
||||
new Graph().addNode(NodeAddress.empty),
|
||||
(_unused_edge) => ({toWeight: 1, froWeight: 2})
|
||||
new Graph().addNode(node("n")),
|
||||
(_unused_edge) => ({
|
||||
toWeight: 1,
|
||||
froWeight: 2,
|
||||
})
|
||||
);
|
||||
await defaultSaver(repoId, prg);
|
||||
const expectedPath = path.join(
|
||||
|
|
|
@ -17,11 +17,10 @@ Array [
|
|||
},
|
||||
Object {
|
||||
"address": Array [
|
||||
"edge",
|
||||
"2",
|
||||
"wat",
|
||||
],
|
||||
"dstIndex": 1,
|
||||
"srcIndex": 0,
|
||||
"dstIndex": 0,
|
||||
"srcIndex": 1,
|
||||
},
|
||||
],
|
||||
"nodes": Array [
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import sortBy from "lodash.sortby";
|
||||
|
||||
import {EdgeAddress, Graph, NodeAddress} from "../graph";
|
||||
import {Graph, NodeAddress} from "../graph";
|
||||
import {
|
||||
distributionToNodeDistribution,
|
||||
createConnections,
|
||||
|
@ -14,13 +14,13 @@ import {
|
|||
} from "./graphToMarkovChain";
|
||||
import * as MapUtil from "../../util/map";
|
||||
|
||||
import {advancedGraph} from "../graphTestUtil";
|
||||
import {node, advancedGraph, edge} from "../graphTestUtil";
|
||||
|
||||
describe("core/attribution/graphToMarkovChain", () => {
|
||||
const n1 = node("n1");
|
||||
const n2 = node("n2");
|
||||
const n3 = node("n3");
|
||||
describe("permute", () => {
|
||||
const n1 = NodeAddress.fromParts(["n1"]);
|
||||
const n2 = NodeAddress.fromParts(["n2"]);
|
||||
const n3 = NodeAddress.fromParts(["n3"]);
|
||||
// This chain isn't a proper stochastic chain, but that's okay:
|
||||
// the actual values aren't relevant.
|
||||
const old = {
|
||||
|
@ -51,9 +51,6 @@ describe("core/attribution/graphToMarkovChain", () => {
|
|||
});
|
||||
|
||||
describe("normalizeNeighbors", () => {
|
||||
const n1 = NodeAddress.fromParts(["n1"]);
|
||||
const n2 = NodeAddress.fromParts(["n2"]);
|
||||
const n3 = NodeAddress.fromParts(["n3"]);
|
||||
// This chain isn't a proper stochastic chain, but that's okay:
|
||||
// the actual values aren't relevant.
|
||||
const old = {
|
||||
|
@ -86,13 +83,11 @@ describe("core/attribution/graphToMarkovChain", () => {
|
|||
// The tests for `createOrderedSparseMarkovChain` also must invoke
|
||||
// `createConnections`, so we add only light testing separately.
|
||||
it("works on a simple asymmetric chain", () => {
|
||||
const n1 = NodeAddress.fromParts(["n1"]);
|
||||
const n2 = NodeAddress.fromParts(["n2"]);
|
||||
const n3 = NodeAddress.fromParts(["sink"]);
|
||||
const e1 = {src: n1, dst: n2, address: EdgeAddress.fromParts(["e1"])};
|
||||
const e2 = {src: n2, dst: n3, address: EdgeAddress.fromParts(["e2"])};
|
||||
const e3 = {src: n1, dst: n3, address: EdgeAddress.fromParts(["e3"])};
|
||||
const e4 = {src: n3, dst: n3, address: EdgeAddress.fromParts(["e4"])};
|
||||
const e1 = edge("e1", n1, n2);
|
||||
const e2 = edge("e2", n2, n3);
|
||||
const e3 = edge("e3", n1, n3);
|
||||
const e4 = edge("e4", n3, n3);
|
||||
|
||||
const g = new Graph()
|
||||
.addNode(n1)
|
||||
.addNode(n2)
|
||||
|
@ -133,8 +128,7 @@ describe("core/attribution/graphToMarkovChain", () => {
|
|||
|
||||
describe("createOrderedSparseMarkovChain", () => {
|
||||
it("works on a trivial one-node chain with no edge", () => {
|
||||
const n = NodeAddress.fromParts(["foo"]);
|
||||
const g = new Graph().addNode(n);
|
||||
const g = new Graph().addNode(n1);
|
||||
const edgeWeight = (_unused_edge) => {
|
||||
throw new Error("Don't even look at me");
|
||||
};
|
||||
|
@ -142,7 +136,7 @@ describe("core/attribution/graphToMarkovChain", () => {
|
|||
createConnections(g, edgeWeight, 1e-3)
|
||||
);
|
||||
const expected = {
|
||||
nodeOrder: [n],
|
||||
nodeOrder: [n1],
|
||||
chain: [
|
||||
{neighbor: new Uint32Array([0]), weight: new Float64Array([1.0])},
|
||||
],
|
||||
|
@ -151,13 +145,11 @@ describe("core/attribution/graphToMarkovChain", () => {
|
|||
});
|
||||
|
||||
it("works on a simple asymmetric chain", () => {
|
||||
const n1 = NodeAddress.fromParts(["n1"]);
|
||||
const n2 = NodeAddress.fromParts(["n2"]);
|
||||
const n3 = NodeAddress.fromParts(["sink"]);
|
||||
const e1 = {src: n1, dst: n2, address: EdgeAddress.fromParts(["e1"])};
|
||||
const e2 = {src: n2, dst: n3, address: EdgeAddress.fromParts(["e2"])};
|
||||
const e3 = {src: n1, dst: n3, address: EdgeAddress.fromParts(["e3"])};
|
||||
const e4 = {src: n3, dst: n3, address: EdgeAddress.fromParts(["e4"])};
|
||||
const e1 = edge("e1", n1, n2);
|
||||
const e2 = edge("e2", n2, n3);
|
||||
const e3 = edge("e3", n1, n3);
|
||||
const e4 = edge("e4", n3, n3);
|
||||
|
||||
const g = new Graph()
|
||||
.addNode(n1)
|
||||
.addNode(n2)
|
||||
|
@ -191,12 +183,9 @@ describe("core/attribution/graphToMarkovChain", () => {
|
|||
});
|
||||
|
||||
it("works on a symmetric K_3", () => {
|
||||
const n1 = NodeAddress.fromParts(["n1"]);
|
||||
const n2 = NodeAddress.fromParts(["n2"]);
|
||||
const n3 = NodeAddress.fromParts(["n3"]);
|
||||
const e1 = {src: n1, dst: n2, address: EdgeAddress.fromParts(["e1"])};
|
||||
const e2 = {src: n2, dst: n3, address: EdgeAddress.fromParts(["e2"])};
|
||||
const e3 = {src: n3, dst: n1, address: EdgeAddress.fromParts(["e3"])};
|
||||
const e1 = edge("e1", n1, n2);
|
||||
const e2 = edge("e2", n2, n3);
|
||||
const e3 = edge("e3", n3, n1);
|
||||
const g = new Graph()
|
||||
.addNode(n1)
|
||||
.addNode(n2)
|
||||
|
@ -257,10 +246,10 @@ describe("core/attribution/graphToMarkovChain", () => {
|
|||
// - dst: `epsilon / 2` from dst, `(8 - epsilon) / 8` from src
|
||||
const expected = {
|
||||
nodeOrder: [
|
||||
ag.nodes.src(),
|
||||
ag.nodes.dst(),
|
||||
ag.nodes.loop(),
|
||||
ag.nodes.isolated(),
|
||||
ag.nodes.src,
|
||||
ag.nodes.dst,
|
||||
ag.nodes.loop,
|
||||
ag.nodes.isolated,
|
||||
],
|
||||
chain: [
|
||||
{
|
||||
|
@ -282,8 +271,6 @@ describe("core/attribution/graphToMarkovChain", () => {
|
|||
describe("distributionToNodeDistribution", () => {
|
||||
it("works", () => {
|
||||
const pi = new Float64Array([0.25, 0.75]);
|
||||
const n1 = NodeAddress.fromParts(["foo"]);
|
||||
const n2 = NodeAddress.fromParts(["bar"]);
|
||||
expect(distributionToNodeDistribution([n1, n2], pi)).toEqual(
|
||||
new Map().set(n1, 0.25).set(n2, 0.75)
|
||||
);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,60 @@
|
|||
// @flow
|
||||
|
||||
import {EdgeAddress, Graph, NodeAddress} from "./graph";
|
||||
import {
|
||||
EdgeAddress,
|
||||
Graph,
|
||||
NodeAddress,
|
||||
type NodeAddressT,
|
||||
type Edge,
|
||||
} from "./graph";
|
||||
|
||||
/**
|
||||
* Create a new NodeAddressT from an array of string address parts.
|
||||
*
|
||||
* Note: This is included as a preliminary clean-up method so that it will be easy to
|
||||
* switch Graph nodes from being represented by a NodeAddressT to a rich Node object.
|
||||
* In a followon commit, this method will create a Node instead of a NodeAddressT.
|
||||
*/
|
||||
export function partsNode(parts: string[]): NodeAddressT {
|
||||
return NodeAddress.fromParts(parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Node from a single address part.
|
||||
*
|
||||
* The same considerations as partsNode apply.
|
||||
*/
|
||||
export function node(name: string): NodeAddressT {
|
||||
return partsNode([name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Edge from address parts and a src and dst.
|
||||
*
|
||||
* This is a convenience method for constructing example edges more concisely in test code.
|
||||
*
|
||||
* The returned edge is frozen, so it is safe to use across test cases.
|
||||
*/
|
||||
export function partsEdge(
|
||||
parts: string[],
|
||||
src: NodeAddressT,
|
||||
dst: NodeAddressT
|
||||
): Edge {
|
||||
return Object.freeze({
|
||||
address: EdgeAddress.fromParts(parts),
|
||||
src,
|
||||
dst,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Edge from a single address part and a src and dst.
|
||||
*
|
||||
* The same considerations as partsEdge apply.
|
||||
*/
|
||||
export function edge(name: string, src: NodeAddressT, dst: NodeAddressT): Edge {
|
||||
return partsEdge([name], src, dst);
|
||||
}
|
||||
|
||||
export function advancedGraph() {
|
||||
// The advanced graph has the following features:
|
||||
|
@ -13,88 +67,69 @@ export function advancedGraph() {
|
|||
// logically equivalent but very different history
|
||||
// To avoid contamination, every piece is exposed as a function
|
||||
// which generates a clean copy of that piece.
|
||||
const src = () => NodeAddress.fromParts(["src"]);
|
||||
const dst = () => NodeAddress.fromParts(["dst"]);
|
||||
const hom1 = () => ({
|
||||
src: src(),
|
||||
dst: dst(),
|
||||
address: EdgeAddress.fromParts(["hom", "1"]),
|
||||
});
|
||||
const hom2 = () => ({
|
||||
src: src(),
|
||||
dst: dst(),
|
||||
address: EdgeAddress.fromParts(["hom", "2"]),
|
||||
});
|
||||
const loop = () => NodeAddress.fromParts(["loop"]);
|
||||
const loop_loop = () => ({
|
||||
src: loop(),
|
||||
dst: loop(),
|
||||
address: EdgeAddress.fromParts(["loop"]),
|
||||
});
|
||||
const isolated = () => NodeAddress.fromParts(["isolated"]);
|
||||
const src = node("src");
|
||||
const dst = node("dst");
|
||||
const hom1 = partsEdge(["hom", "1"], src, dst);
|
||||
const hom2 = partsEdge(["hom", "2"], src, dst);
|
||||
const loop = node("loop");
|
||||
const loop_loop = edge("loop", loop, loop);
|
||||
const isolated = node("isolated");
|
||||
const graph1 = () =>
|
||||
new Graph()
|
||||
.addNode(src())
|
||||
.addNode(dst())
|
||||
.addNode(loop())
|
||||
.addNode(isolated())
|
||||
.addEdge(hom1())
|
||||
.addEdge(hom2())
|
||||
.addEdge(loop_loop());
|
||||
.addNode(src)
|
||||
.addNode(dst)
|
||||
.addNode(loop)
|
||||
.addNode(isolated)
|
||||
.addEdge(hom1)
|
||||
.addEdge(hom2)
|
||||
.addEdge(loop_loop);
|
||||
|
||||
// graph2 is logically equivalent to graph1, but is constructed with very
|
||||
// different history.
|
||||
// Use this to check that logically equivalent graphs are treated
|
||||
// equivalently, regardless of their history.
|
||||
const phantomNode = () => NodeAddress.fromParts(["phantom"]);
|
||||
const phantomEdge1 = () => ({
|
||||
src: src(),
|
||||
dst: phantomNode(),
|
||||
address: EdgeAddress.fromParts(["phantom"]),
|
||||
});
|
||||
const phantomEdge2 = () => ({
|
||||
src: src(),
|
||||
dst: isolated(),
|
||||
address: EdgeAddress.fromParts(["not", "so", "isolated"]),
|
||||
});
|
||||
const phantomNode = node("phantom");
|
||||
const phantomEdge1 = edge("phantom", src, phantomNode);
|
||||
const phantomEdge2 = edge("not-so-isolated", src, isolated);
|
||||
|
||||
// To verify that the graphs are equivalent, every mutation is preceded
|
||||
// by a comment stating what the set of nodes and edges are prior to that mutation
|
||||
const graph2 = () =>
|
||||
new Graph()
|
||||
// N: [], E: []
|
||||
.addNode(phantomNode())
|
||||
.addNode(phantomNode)
|
||||
// N: [phantomNode], E: []
|
||||
.addNode(src())
|
||||
.addNode(src)
|
||||
// N: [phantomNode, src], E: []
|
||||
.addEdge(phantomEdge1())
|
||||
.addEdge(phantomEdge1)
|
||||
// N: [phantomNode, src], E: [phantomEdge1]
|
||||
.addNode(isolated())
|
||||
.addNode(isolated)
|
||||
// N: [phantomNode, src, isolated], E: [phantomEdge1]
|
||||
.removeEdge(phantomEdge1().address)
|
||||
.removeEdge(phantomEdge1.address)
|
||||
// N: [phantomNode, src, isolated], E: []
|
||||
.addNode(dst())
|
||||
.addNode(dst)
|
||||
// N: [phantomNode, src, isolated, dst], E: []
|
||||
.addEdge(hom1())
|
||||
.addEdge(hom1)
|
||||
// N: [phantomNode, src, isolated, dst], E: [hom1]
|
||||
.addEdge(phantomEdge2())
|
||||
.addEdge(phantomEdge2)
|
||||
// N: [phantomNode, src, isolated, dst], E: [hom1, phantomEdge2]
|
||||
.addEdge(hom2())
|
||||
.addEdge(hom2)
|
||||
// N: [phantomNode, src, isolated, dst], E: [hom1, phantomEdge2, hom2]
|
||||
.removeEdge(hom1().address)
|
||||
.removeEdge(hom1.address)
|
||||
// N: [phantomNode, src, isolated, dst], E: [phantomEdge2, hom2]
|
||||
.removeNode(phantomNode())
|
||||
.removeNode(phantomNode)
|
||||
// N: [src, isolated, dst], E: [phantomEdge2, hom2]
|
||||
.removeEdge(phantomEdge2().address)
|
||||
.removeEdge(phantomEdge2.address)
|
||||
// N: [src, isolated, dst], E: [hom2]
|
||||
.removeNode(isolated())
|
||||
.removeNode(isolated)
|
||||
// N: [src, dst], E: [hom2]
|
||||
.addNode(isolated())
|
||||
.addNode(isolated)
|
||||
// N: [src, dst, isolated], E: [hom2]
|
||||
.addNode(loop())
|
||||
.addNode(loop)
|
||||
// N: [src, dst, isolated, loop], E: [hom2]
|
||||
.addEdge(loop_loop())
|
||||
.addEdge(loop_loop)
|
||||
// N: [src, dst, isolated, loop], E: [hom2, loop_loop]
|
||||
.addEdge(hom1());
|
||||
.addEdge(hom1);
|
||||
// N: [src, dst, isolated, loop], E: [hom2, loop_loop, hom1]
|
||||
const nodes = {src, dst, loop, isolated, phantomNode};
|
||||
const edges = {hom1, hom2, loop_loop, phantomEdge1, phantomEdge2};
|
||||
|
|
|
@ -17,13 +17,12 @@ import {
|
|||
DEFAULT_ALPHA,
|
||||
DEFAULT_SEED,
|
||||
} from "./pagerankGraph";
|
||||
import {advancedGraph} from "./graphTestUtil";
|
||||
import {advancedGraph, node, partsNode, partsEdge} from "./graphTestUtil";
|
||||
import * as NullUtil from "../util/null";
|
||||
|
||||
describe("core/pagerankGraph", () => {
|
||||
const defaultEvaluator = (_unused_edge) => ({toWeight: 1, froWeight: 0});
|
||||
const nonEmptyGraph = () =>
|
||||
new Graph().addNode(NodeAddress.fromParts(["hi"]));
|
||||
const nonEmptyGraph = () => new Graph().addNode(node("hi"));
|
||||
|
||||
function examplePagerankGraph(
|
||||
edgeEvaluator = defaultEvaluator
|
||||
|
@ -39,9 +38,7 @@ describe("core/pagerankGraph", () => {
|
|||
|
||||
it("cannot construct PagerankGraph with empty Graph", () => {
|
||||
const eg1 = new Graph();
|
||||
const eg2 = new Graph()
|
||||
.addNode(NodeAddress.empty)
|
||||
.removeNode(NodeAddress.empty);
|
||||
const eg2 = new Graph().addNode(node("hi")).removeNode(node("hi"));
|
||||
expect(() => new PagerankGraph(eg1, defaultEvaluator)).toThrowError(
|
||||
"empty graph"
|
||||
);
|
||||
|
@ -93,8 +90,7 @@ describe("core/pagerankGraph", () => {
|
|||
const pg = new PagerankGraph(g, defaultEvaluator);
|
||||
const graphNodes = Array.from(g.nodes());
|
||||
const pgNodes = Array.from(pg.nodes()).map((x) => x.address);
|
||||
expect(graphNodes.length).toEqual(pgNodes.length);
|
||||
expect(new Set(graphNodes)).toEqual(new Set(pgNodes));
|
||||
expect(graphNodes).toEqual(pgNodes);
|
||||
});
|
||||
it("node and nodes both return consistent scores", async () => {
|
||||
const pg = await convergedPagerankGraph();
|
||||
|
@ -104,7 +100,7 @@ describe("core/pagerankGraph", () => {
|
|||
});
|
||||
it("node and nodes both throw an error if underlying graph is modified", () => {
|
||||
const pg = new PagerankGraph(nonEmptyGraph(), defaultEvaluator);
|
||||
pg.graph().addNode(NodeAddress.empty);
|
||||
pg.graph().addNode(node("foo"));
|
||||
expect(() => pg.nodes()).toThrowError(
|
||||
"underlying Graph has been modified"
|
||||
);
|
||||
|
@ -115,10 +111,10 @@ describe("core/pagerankGraph", () => {
|
|||
});
|
||||
|
||||
describe("node prefix filter matches graph filter", () => {
|
||||
const n1 = NodeAddress.empty;
|
||||
const n2 = NodeAddress.fromParts(["foo"]);
|
||||
const n3 = NodeAddress.fromParts(["foo", "bar"]);
|
||||
const n4 = NodeAddress.fromParts(["zod", "bar"]);
|
||||
const n1 = partsNode([]);
|
||||
const n2 = partsNode(["foo"]);
|
||||
const n3 = partsNode(["foo", "bar"]);
|
||||
const n4 = partsNode(["zod", "bar"]);
|
||||
const g = () =>
|
||||
new Graph()
|
||||
.addNode(n1)
|
||||
|
@ -200,7 +196,7 @@ describe("core/pagerankGraph", () => {
|
|||
|
||||
it("edge and edges both throw an error if underlying graph is modified", () => {
|
||||
const pg = new PagerankGraph(nonEmptyGraph(), defaultEvaluator);
|
||||
pg.graph().addNode(NodeAddress.empty);
|
||||
pg.graph().addNode(node("foo"));
|
||||
expect(() => pg.edges()).toThrowError(
|
||||
"underlying Graph has been modified"
|
||||
);
|
||||
|
@ -213,10 +209,10 @@ describe("core/pagerankGraph", () => {
|
|||
describe("totalOutWeight", () => {
|
||||
it("errors on a modified graph", () => {
|
||||
const eg = examplePagerankGraph();
|
||||
eg.graph().addNode(NodeAddress.fromParts(["bad", "node"]));
|
||||
expect(() =>
|
||||
eg.totalOutWeight(NodeAddress.fromParts(["bad", "node"]))
|
||||
).toThrowError("has been modified");
|
||||
eg.graph().addNode(partsNode(["bad", "node"]));
|
||||
expect(() => eg.totalOutWeight(partsNode(["bad", "node"]))).toThrowError(
|
||||
"has been modified"
|
||||
);
|
||||
});
|
||||
it("errors on nonexistent node", () => {
|
||||
const eg = examplePagerankGraph();
|
||||
|
@ -271,30 +267,14 @@ describe("core/pagerankGraph", () => {
|
|||
});
|
||||
|
||||
describe("edge filtering", () => {
|
||||
const src1 = NodeAddress.fromParts(["src", "1"]);
|
||||
const src2 = NodeAddress.fromParts(["src", "2"]);
|
||||
const dst1 = NodeAddress.fromParts(["dst", "1"]);
|
||||
const dst2 = NodeAddress.fromParts(["dst", "2"]);
|
||||
const e11 = {
|
||||
src: src1,
|
||||
dst: dst1,
|
||||
address: EdgeAddress.fromParts(["e", "1", "1"]),
|
||||
};
|
||||
const e12 = {
|
||||
src: src1,
|
||||
dst: dst2,
|
||||
address: EdgeAddress.fromParts(["e", "1", "2"]),
|
||||
};
|
||||
const e21 = {
|
||||
src: src2,
|
||||
dst: dst1,
|
||||
address: EdgeAddress.fromParts(["e", "2", "1"]),
|
||||
};
|
||||
const e22 = {
|
||||
src: src2,
|
||||
dst: dst2,
|
||||
address: EdgeAddress.fromParts(["e", "2", "2"]),
|
||||
};
|
||||
const src1 = partsNode(["src", "1"]);
|
||||
const src2 = partsNode(["src", "2"]);
|
||||
const dst1 = partsNode(["dst", "1"]);
|
||||
const dst2 = partsNode(["dst", "2"]);
|
||||
const e11 = partsEdge(["e", "1", "1"], src1, dst1);
|
||||
const e12 = partsEdge(["e", "1", "2"], src1, dst2);
|
||||
const e21 = partsEdge(["e", "2", "1"], src2, dst1);
|
||||
const e22 = partsEdge(["e", "2", "2"], src2, dst2);
|
||||
const graph = () => {
|
||||
const g = new Graph();
|
||||
[src1, src2, dst1, dst2].forEach((n) => g.addNode(n));
|
||||
|
@ -395,7 +375,7 @@ describe("core/pagerankGraph", () => {
|
|||
});
|
||||
it("is an error to call neighbors after modifying the underlying graph", () => {
|
||||
const pg = examplePagerankGraph();
|
||||
pg.graph().addNode(NodeAddress.fromParts(["foomfazzle"]));
|
||||
pg.graph().addNode(partsNode(["foomfazzle"]));
|
||||
expect(() =>
|
||||
pg.neighbors(NodeAddress.fromParts(["src"]), allNeighbors())
|
||||
).toThrowError("has been modified");
|
||||
|
@ -549,8 +529,8 @@ describe("core/pagerankGraph", () => {
|
|||
const pg1 = examplePagerankGraph();
|
||||
const pg2 = examplePagerankGraph();
|
||||
const {nodes} = advancedGraph();
|
||||
const seed1 = new Map().set(nodes.src(), 1);
|
||||
const seed2 = new Map().set(nodes.dst(), 1);
|
||||
const seed1 = new Map().set(nodes.src, 1);
|
||||
const seed2 = new Map().set(nodes.dst, 1);
|
||||
await pg1.runPagerank({seed: seed1, alpha: 0});
|
||||
await pg2.runPagerank({seed: seed2, alpha: 0});
|
||||
expect(pg1.equals(pg2)).toBe(true);
|
||||
|
@ -559,16 +539,16 @@ describe("core/pagerankGraph", () => {
|
|||
it("seed is returned directly if alpha is 1", async () => {
|
||||
const pg = examplePagerankGraph();
|
||||
const src = advancedGraph().nodes.src;
|
||||
const seed = new Map().set(src(), 1);
|
||||
const seed = new Map().set(src, 1);
|
||||
await pg.runPagerank({seed, alpha: 1});
|
||||
const score = NullUtil.get(pg.node(src())).score;
|
||||
const score = NullUtil.get(pg.node(src)).score;
|
||||
expect(score).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("promise rejects if the graph was modified", async () => {
|
||||
const pg = examplePagerankGraph();
|
||||
pg.graph().addNode(NodeAddress.empty);
|
||||
pg.graph().addNode(node("foo"));
|
||||
expect(
|
||||
pg.runPagerank({maxIterations: 1, convergenceThreshold: 1})
|
||||
).rejects.toThrow("underlying Graph has been modified");
|
||||
|
@ -644,7 +624,7 @@ describe("core/pagerankGraph", () => {
|
|||
});
|
||||
it("unequal graph => unequal", () => {
|
||||
const pg1 = new PagerankGraph(nonEmptyGraph(), defaultEvaluator, 0.1);
|
||||
const g2 = nonEmptyGraph().addNode(NodeAddress.empty);
|
||||
const g2 = nonEmptyGraph().addNode(node("foo"));
|
||||
const pg2 = new PagerankGraph(g2, defaultEvaluator, 0.1);
|
||||
expect(pg1.equals(pg2)).toBe(false);
|
||||
});
|
||||
|
@ -676,7 +656,7 @@ describe("core/pagerankGraph", () => {
|
|||
});
|
||||
it("throws an error if the underlying graph is modified", () => {
|
||||
const pg = examplePagerankGraph();
|
||||
pg.graph().addNode(NodeAddress.fromParts(["modification"]));
|
||||
pg.graph().addNode(node("modification"));
|
||||
expect(() => pg.equals(pg)).toThrowError("has been modified");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,30 +1,31 @@
|
|||
// @flow
|
||||
|
||||
import {Graph, NodeAddress, EdgeAddress} from "../../core/graph";
|
||||
import {Graph} from "../../core/graph";
|
||||
import {partsNode, partsEdge} from "../../core/graphTestUtil";
|
||||
|
||||
export const nodes = Object.freeze({
|
||||
inserter1: NodeAddress.fromParts(["factorio", "inserter", "1"]),
|
||||
machine1: NodeAddress.fromParts(["factorio", "machine", "1"]),
|
||||
inserter2: NodeAddress.fromParts(["factorio", "inserter", "2"]),
|
||||
machine2: NodeAddress.fromParts(["factorio", "machine", "2"]),
|
||||
inserter1: partsNode(["factorio", "inserter", "1"]),
|
||||
machine1: partsNode(["factorio", "machine", "1"]),
|
||||
inserter2: partsNode(["factorio", "inserter", "2"]),
|
||||
machine2: partsNode(["factorio", "machine", "2"]),
|
||||
});
|
||||
|
||||
export const edges = Object.freeze({
|
||||
transports1: Object.freeze({
|
||||
src: nodes.inserter1,
|
||||
dst: nodes.machine1,
|
||||
address: EdgeAddress.fromParts(["factorio", "transports", "1"]),
|
||||
}),
|
||||
assembles1: Object.freeze({
|
||||
src: nodes.machine1,
|
||||
dst: nodes.inserter2,
|
||||
address: EdgeAddress.fromParts(["factorio", "assembles", "1"]),
|
||||
}),
|
||||
transports2: Object.freeze({
|
||||
src: nodes.inserter2,
|
||||
dst: nodes.machine2,
|
||||
address: EdgeAddress.fromParts(["factorio", "assembles", "2"]),
|
||||
}),
|
||||
transports1: partsEdge(
|
||||
["factorio", "transports", "1"],
|
||||
nodes.inserter1,
|
||||
nodes.machine1
|
||||
),
|
||||
transports2: partsEdge(
|
||||
["factorio", "transports", "2"],
|
||||
nodes.inserter2,
|
||||
nodes.machine2
|
||||
),
|
||||
assembles1: partsEdge(
|
||||
["factorio", "assembles", "1"],
|
||||
nodes.machine1,
|
||||
nodes.inserter2
|
||||
),
|
||||
});
|
||||
|
||||
export function graph() {
|
||||
|
|
Loading…
Reference in New Issue