Add and implement `Graph.equals` (#369)

It turns out we forgot to add this to the API, so I added it. I also
implemented it. The tests are pretty thorough; as an added innovation
over our previous tests (e.g. in #312 and #61), we now consistently test
that equality is commutative.

In contrast to our previous implementations, this one is massively
simpler. That's an upside of using primitive ES6 data structures to
store all of the graph's information... which is itself an upside of not
trying to store arbitrary additional information in the graph. Now we
can just do a deep equality check on the underlying nodes set and edges
map!

We might be able to performance tune this method by taking advantage of
the structure of our nodes and edges. This should suffice for now,
though.

Paired with @wchargin

Test plan:
Unit tests were added. Run `yarn travis`
This commit is contained in:
Dandelion Mané 2018-06-08 14:17:57 -07:00 committed by GitHub
parent 4a441eb287
commit feef119250
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 0 deletions

View File

@ -1,5 +1,7 @@
// @flow
import deepEqual from "lodash.isequal";
import {makeAddressModule, type AddressModule} from "./address";
export opaque type NodeAddressT: string = string;
@ -292,6 +294,15 @@ export class Graph {
this._checkForComodification(initialModificationCount);
}
equals(that: Graph): boolean {
if (!(that instanceof Graph)) {
throw new Error(`Expected Graph, got ${String(that)}`);
}
return (
deepEqual(this._nodes, that._nodes) && deepEqual(this._edges, that._edges)
);
}
copy(): Graph {
throw new Error("copy");
}

View File

@ -703,6 +703,100 @@ describe("core/graph", () => {
});
});
});
describe("equals", () => {
const src = NodeAddress.fromParts(["src"]);
const dst = NodeAddress.fromParts(["dst"]);
const edge1 = () => ({
src,
dst,
address: EdgeAddress.fromParts(["edge"]),
});
const conflictingEdge = () => ({
src: dst,
dst: src,
address: EdgeAddress.fromParts(["edge"]),
});
const edge2 = () => ({
src: dst,
dst: src,
address: EdgeAddress.fromParts(["edge", "2"]),
});
function expectEquality(g1, g2, isEqual: boolean) {
expect(g1.equals(g2)).toBe(isEqual);
expect(g2.equals(g1)).toBe(isEqual);
}
it("empty graph equals itself", () => {
expectEquality(new Graph(), new Graph(), true);
});
it("empty graph doesn't equal nonempty graph", () => {
const g = new Graph().addNode(src);
expectEquality(g, new Graph(), false);
});
it("adding and removing a node doesn't change equality", () => {
const g = new Graph().addNode(src).removeNode(src);
expectEquality(g, new Graph(), true);
});
it("adding an edge changes equality", () => {
const g1 = new Graph().addNode(src).addNode(dst);
const g2 = new Graph()
.addNode(src)
.addNode(dst)
.addEdge(edge1());
expectEquality(g1, g2, false);
});
it("adding nodes in different order doesn't change equality", () => {
const g1 = new Graph().addNode(src).addNode(dst);
const g2 = new Graph().addNode(dst).addNode(src);
expectEquality(g1, g2, true);
});
it("graphs with conflicting edges are not equal", () => {
const g1 = new Graph()
.addNode(src)
.addNode(dst)
.addEdge(edge1());
const g2 = new Graph()
.addNode(src)
.addNode(dst)
.addEdge(conflictingEdge());
expectEquality(g1, g2, false);
});
it("adding edges in different order doesn't change equality", () => {
const g1 = new Graph()
.addNode(src)
.addNode(dst)
.addEdge(edge1())
.addEdge(edge2());
const g2 = new Graph()
.addNode(src)
.addNode(dst)
.addEdge(edge2())
.addEdge(edge1());
expectEquality(g1, g2, true);
});
it("adding and removing an edge doesn't change equality", () => {
const g1 = new Graph().addNode(src).addNode(dst);
const g2 = new Graph()
.addNode(src)
.addNode(dst)
.addEdge(edge1())
.removeEdge(edge1().address);
expectEquality(g1, g2, true);
});
it("throws error on null", () => {
// $ExpectFlowError
expect(() => new Graph().equals(null)).toThrow("null");
});
it("throws error on undefined", () => {
// $ExpectFlowError
expect(() => new Graph().equals(undefined)).toThrow("undefined");
});
it("throws error on non-graph object", () => {
// $ExpectFlowError
expect(() => new Graph().equals({})).toThrow("object");
});
});
});
describe("edgeToString", () => {