Add an `advancedGraph` test case (#377)

The `advancedGraph` is an example graph defined in `graph.test.js`.
It shows off many tricksy features, like having loop edges, multiple
edges from the same src to same dst, etc. We also provide two ways of
constructing it: `graph1` is straightforward, `graph2` adds tons of
spurious adds, removes, and odd ordering. This way we can ensure that
our functions treat `graph1` and `graph2` equivalently.

Test plan:
New unit tests are added verifying that `equals`, `merge`, and
`to/fromJSON` handle the advanced graph appropriately.
This commit is contained in:
Dandelion Mané 2018-06-11 12:08:53 -07:00 committed by GitHub
parent 5fc0d42c1f
commit c352b5b8d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 181 additions and 8 deletions

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`core/graph toJSON / fromJSON toJSON matches snapshot 1`] = ` exports[`core/graph toJSON / fromJSON snapshot testing a trivial graph 1`] = `
Array [ Array [
Object { Object {
"type": "sourcecred/graph", "type": "sourcecred/graph",
@ -35,3 +35,53 @@ Array [
}, },
] ]
`; `;
exports[`core/graph toJSON / fromJSON snapshot testing an advanced graph 1`] = `
Array [
Object {
"type": "sourcecred/graph",
"version": "0.4.0",
},
Object {
"edges": Array [
Object {
"address": Array [
"hom",
"1",
],
"dstIndex": 0,
"srcIndex": 3,
},
Object {
"address": Array [
"hom",
"2",
],
"dstIndex": 0,
"srcIndex": 3,
},
Object {
"address": Array [
"loop",
],
"dstIndex": 2,
"srcIndex": 2,
},
],
"nodes": Array [
Array [
"dst",
],
Array [
"isolated",
],
Array [
"loop",
],
Array [
"src",
],
],
},
]
`;

View File

@ -31,6 +31,105 @@ describe("core/graph", () => {
}); });
}); });
function advancedGraph() {
// The advanced graph has the following features:
// - Multiple edges of same hom, from `src` to `dst`
// - An isolated node, `isolated`
// - A loop
// - A node and edge with the same toParts representation
// This function exposes all of the pieces of the advanced graph.
// It also returns two different versions of the graph, which are
// 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 graph1 = () =>
new Graph()
.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"]),
});
// 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())
// N: [phantomNode], E: []
.addNode(src())
// N: [phantomNode, src], E: []
.addEdge(phantomEdge1())
// N: [phantomNode, src], E: [phantomEdge1]
.addNode(isolated())
// N: [phantomNode, src, isolated], E: [phantomEdge1]
.removeEdge(phantomEdge1().address)
// N: [phantomNode, src, isolated], E: []
.addNode(dst())
// N: [phantomNode, src, isolated, dst], E: []
.addEdge(hom1())
// N: [phantomNode, src, isolated, dst], E: [hom1]
.addEdge(phantomEdge2())
// N: [phantomNode, src, isolated, dst], E: [hom1, phantomEdge2]
.addEdge(hom2())
// N: [phantomNode, src, isolated, dst], E: [hom1, phantomEdge2, hom2]
.removeEdge(hom1().address)
// N: [phantomNode, src, isolated, dst], E: [phantomEdge2, hom2]
.removeNode(phantomNode())
// N: [src, isolated, dst], E: [phantomEdge2, hom2]
.removeEdge(phantomEdge2().address)
// N: [src, isolated, dst], E: [hom2]
.removeNode(isolated())
// N: [src, dst], E: [hom2]
.addNode(isolated())
// N: [src, dst, isolated], E: [hom2]
.addNode(loop())
// N: [src, dst, isolated, loop], E: [hom2]
.addEdge(loop_loop())
// N: [src, dst, isolated, loop], E: [hom2, loop_loop]
.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};
return {nodes, edges, graph1, graph2};
}
describe("Graph class", () => { describe("Graph class", () => {
it("can be constructed", () => { it("can be constructed", () => {
const x = new Graph(); const x = new Graph();
@ -784,6 +883,10 @@ describe("core/graph", () => {
.removeEdge(edge1().address); .removeEdge(edge1().address);
expectEquality(g1, g2, true); expectEquality(g1, g2, true);
}); });
it("the logically-equivalent advanced graphs are equal", () => {
const {graph1, graph2} = advancedGraph();
expect(graph1().equals(graph2())).toBe(true);
});
it("throws error on null", () => { it("throws error on null", () => {
// $ExpectFlowError // $ExpectFlowError
expect(() => new Graph().equals(null)).toThrow("null"); expect(() => new Graph().equals(null)).toThrow("null");
@ -934,6 +1037,12 @@ describe("core/graph", () => {
const actual = Graph.merge(graphs); const actual = Graph.merge(graphs);
expect(actual.equals(expected)).toBe(true); expect(actual.equals(expected)).toBe(true);
}); });
it("merges the advanced graphs together", () => {
const {graph1, graph2} = advancedGraph();
const graph3 = Graph.merge([graph1(), graph2()]);
expect(graph1().equals(graph3)).toBe(true);
expect(graph2().equals(graph3)).toBe(true);
});
it("rejects graphs with conflicting edges", () => { it("rejects graphs with conflicting edges", () => {
const g1 = new Graph() const g1 = new Graph()
.addNode(foo) .addNode(foo)
@ -960,13 +1069,19 @@ describe("core/graph", () => {
dst: src, dst: src,
address: EdgeAddress.fromParts(["edge", "2"]), address: EdgeAddress.fromParts(["edge", "2"]),
}); });
it("toJSON matches snapshot", () => { describe("snapshot testing", () => {
const graph = new Graph() it("a trivial graph", () => {
.addNode(src) const graph = new Graph()
.addNode(dst) .addNode(src)
.addEdge(edge1()) .addNode(dst)
.addEdge(edge2()); .addEdge(edge1())
expect(graph.toJSON()).toMatchSnapshot(); .addEdge(edge2());
expect(graph.toJSON()).toMatchSnapshot();
});
it("an advanced graph", () => {
const graph = advancedGraph().graph1();
expect(graph.toJSON()).toMatchSnapshot();
});
}); });
describe("compose to identity", () => { describe("compose to identity", () => {
@ -999,6 +1114,10 @@ describe("core/graph", () => {
.addEdge(edge2()); .addEdge(edge2());
expectCompose(g); expectCompose(g);
}); });
it("for the advanced graph", () => {
const g = advancedGraph().graph1();
expectCompose(g);
});
}); });
describe("toJSON representation is canonical", () => { describe("toJSON representation is canonical", () => {
@ -1034,6 +1153,10 @@ describe("core/graph", () => {
.addEdge(edge2()); .addEdge(edge2());
expectCanonicity(g1, g2); expectCanonicity(g1, g2);
}); });
it("for the advanced graph", () => {
const {graph1, graph2} = advancedGraph();
expectCanonicity(graph1(), graph2());
});
}); });
}); });