// Copyright ©2018 The Gonum Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package testgraph provides a set of testing helper functions // that test Gonum graph interface implementations. package testgraph // import "gonum.org/v1/gonum/graph/testgraph" import ( "cmp" "fmt" "math/rand/v2" "reflect" "slices" "testing" "gonum.org/v1/gonum/floats/scalar" "gonum.org/v1/gonum/graph" "gonum.org/v1/gonum/graph/internal/set" "gonum.org/v1/gonum/internal/order" "gonum.org/v1/gonum/mat" ) // BUG(kortschak): Edge equality is tested in part with reflect.DeepEqual and // direct equality of weight values. This means that edges returned by graphs // must not contain NaN values. Weights returned by the Weight method are // compared with NaN-awareness, so they may be NaN when there is no edge // associated with the Weight call. func isValidIterator(it graph.Iterator) bool { return it != nil } func checkEmptyIterator(t *testing.T, it graph.Iterator, useEmpty bool) { t.Helper() if it.Len() != 0 { return } if it != graph.Empty { if useEmpty { t.Errorf("unexpected empty iterator: got:%T", it) return } // Only log this since we say that a graph should // return a graph.Empty when it is empty. t.Logf("unexpected empty iterator: got:%T", it) } } func hasEnds(x, y graph.Node, e Edge) bool { return (e.From().ID() == x.ID() && e.To().ID() == y.ID()) || (e.From().ID() == y.ID() && e.To().ID() == x.ID()) } // Edge supports basic edge operations. type Edge interface { // From returns the from node of the edge. From() graph.Node // To returns the to node of the edge. To() graph.Node } // WeightedLine is a generalized graph edge that supports all graph // edge operations except reversal. type WeightedLine interface { Edge // ID returns the unique ID for the Line. ID() int64 // Weight returns the weight of the edge. Weight() float64 } // A Builder function returns a graph constructed from the nodes, edges and // default weights passed in, potentially altering the nodes and edges to // conform to the requirements of the graph. The graph is returned along with // the nodes, edges and default weights used to construct the graph. // The returned edges may be any of graph.Edge, graph.WeightedEdge, graph.Line // or graph.WeightedLine depending on what the graph requires. // The client may skip a test case by returning ok=false when the input is not // a valid graph construction. type Builder func(nodes []graph.Node, edges []WeightedLine, self, absent float64) (g graph.Graph, n []graph.Node, e []Edge, s, a float64, ok bool) // edgeLister is a graph that can return all its edges. type edgeLister interface { // Edges returns all the edges of a graph. Edges() graph.Edges } // weightedEdgeLister is a graph that can return all its weighted edges. type weightedEdgeLister interface { // WeightedEdges returns all the weighted edges of a graph. WeightedEdges() graph.WeightedEdges } // matrixer is a graph that can return an adjacency matrix. type matrixer interface { // Matrix returns the graph's adjacency matrix. Matrix() mat.Matrix } // ReturnAllNodes tests the constructed graph for the ability to return all // the nodes it claims it has used in its construction. This is a check of // the Nodes method of graph.Graph and the iterator that is returned. // If useEmpty is true, graph iterators will be checked for the use of // graph.Empty if they are empty. func ReturnAllNodes(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, want, _, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } it := g.Nodes() if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) var got []graph.Node for it.Next() { got = append(got, it.Node()) } order.ByID(got) order.ByID(want) if !reflect.DeepEqual(got, want) { t.Errorf("unexpected nodes result for test %q:\ngot: %v\nwant:%v", test.name, got, want) } } } // ReturnNodeSlice tests the constructed graph for the ability to return all // the nodes it claims it has used in its construction using the NodeSlicer // interface. This is a check of the Nodes method of graph.Graph and the // iterator that is returned. // If useEmpty is true, graph iterators will be checked for the use of // graph.Empty if they are empty. func ReturnNodeSlice(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, want, _, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } it := g.Nodes() if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) if it == nil { continue } s, ok := it.(graph.NodeSlicer) if !ok { t.Errorf("invalid type for test %q: %T cannot return node slicer", test.name, g) continue } got := s.NodeSlice() order.ByID(got) order.ByID(want) if !reflect.DeepEqual(got, want) { t.Errorf("unexpected nodes result for test %q:\ngot: %v\nwant:%v", test.name, got, want) } } } // NodeExistence tests the constructed graph for the ability to correctly // return the existence of nodes within the graph. This is a check of the // Node method of graph.Graph. func NodeExistence(t *testing.T, b Builder) { for _, test := range testCases { g, want, _, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } seen := set.NewNodes() for _, exist := range want { seen.Add(exist) if g.Node(exist.ID()) == nil { t.Errorf("missing node for test %q: %v", test.name, exist) } } for _, ghost := range test.nonexist { if g.Node(ghost.ID()) != nil { if seen.Has(ghost) { // Do not fail nodes that the graph builder says can exist // even if the test case input thinks they should not. t.Logf("builder has modified non-exist node set: %v is now allowed and present", ghost) continue } t.Errorf("unexpected node for test %q: %v", test.name, ghost) } } } } // ReturnAllEdges tests the constructed graph for the ability to return all // the edges it claims it has used in its construction. This is a check of // the Edges method of graph.Graph and the iterator that is returned. // ReturnAllEdges also checks that the edge end nodes exist within the graph, // checking the Node method of graph.Graph. // If useEmpty is true, graph iterators will be checked for the use of // graph.Empty if they are empty. func ReturnAllEdges(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } var got []Edge switch eg := g.(type) { case edgeLister: it := eg.Edges() if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) for it.Next() { e := it.Edge() got = append(got, e) qe := g.Edge(e.From().ID(), e.To().ID()) if qe == nil { t.Errorf("missing edge for test %q: %v", test.name, e) } else if qe.From().ID() != e.From().ID() || qe.To().ID() != e.To().ID() { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, e.From().ID(), e.To().ID(), qe) } if g.Node(e.From().ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, e.From().ID()) } if g.Node(e.To().ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, e.To().ID()) } } default: t.Errorf("invalid type for test %q: %T cannot return edge iterator", test.name, g) continue } checkEdges(t, test.name, g, got, want) } } // ReturnEdgeSlice tests the constructed graph for the ability to return all // the edges it claims it has used in its construction using the EdgeSlicer // interface. This is a check of the Edges method of graph.Graph and the // iterator that is returned. ReturnEdgeSlice also checks that the edge end // nodes exist within the graph, checking the Node method of graph.Graph. // If useEmpty is true, graph iterators will be checked for the use of // graph.Empty if they are empty. func ReturnEdgeSlice(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } var got []Edge switch eg := g.(type) { case edgeLister: it := eg.Edges() if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) if it == nil { continue } s, ok := it.(graph.EdgeSlicer) if !ok { t.Errorf("invalid type for test %q: %T cannot return edge slicer", test.name, g) continue } gotNative := s.EdgeSlice() if len(gotNative) != 0 { got = make([]Edge, len(gotNative)) } for i, e := range gotNative { got[i] = e qe := g.Edge(e.From().ID(), e.To().ID()) if qe == nil { t.Errorf("missing edge for test %q: %v", test.name, e) } else if qe.From().ID() != e.From().ID() || qe.To().ID() != e.To().ID() { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, e.From().ID(), e.To().ID(), qe) } if g.Node(e.From().ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, e.From().ID()) } if g.Node(e.To().ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, e.To().ID()) } } default: t.Errorf("invalid type for test %T: cannot return edge iterator", g) continue } checkEdges(t, test.name, g, got, want) } } // ReturnAllLines tests the constructed graph for the ability to return all // the edges it claims it has used in its construction and then recover all // the lines that contribute to those edges. This is a check of the Edges // method of graph.Graph and the iterator that is returned and the graph.Lines // implementation of those edges. ReturnAllLines also checks that the edge // end nodes exist within the graph, checking the Node method of graph.Graph. // // The edges used within and returned by the Builder function should be // graph.Line. The edge parameter passed to b will contain only graph.Line. // If useEmpty is true, graph iterators will be checked for the use of // graph.Empty if they are empty. func ReturnAllLines(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } var got []Edge switch eg := g.(type) { case edgeLister: it := eg.Edges() if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) for _, e := range graph.EdgesOf(it) { qe := g.Edge(e.From().ID(), e.To().ID()) if qe == nil { t.Errorf("missing edge for test %q: %v", test.name, e) } else if qe.From().ID() != e.From().ID() || qe.To().ID() != e.To().ID() { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, e.From().ID(), e.To().ID(), qe) } // FIXME(kortschak): This would not be necessary // if graph.WeightedLines (and by symmetry) // graph.WeightedEdges also were graph.Lines // and graph.Edges. switch lit := e.(type) { case graph.Lines: if !isValidIterator(lit) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, lit) continue } checkEmptyIterator(t, lit, useEmpty) for lit.Next() { got = append(got, lit.Line()) } case graph.WeightedLines: if !isValidIterator(lit) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, lit) continue } checkEmptyIterator(t, lit, useEmpty) for lit.Next() { got = append(got, lit.WeightedLine()) } default: continue } if g.Node(e.From().ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, e.From().ID()) } if g.Node(e.To().ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, e.To().ID()) } } default: t.Errorf("invalid type for test: %T cannot return edge iterator", g) continue } checkEdges(t, test.name, g, got, want) } } // ReturnAllWeightedEdges tests the constructed graph for the ability to return // all the edges it claims it has used in its construction. This is a check of // the Edges method of graph.Graph and the iterator that is returned. // ReturnAllWeightedEdges also checks that the edge end nodes exist within the // graph, checking the Node method of graph.Graph. // // The edges used within and returned by the Builder function should be // graph.WeightedEdge. The edge parameter passed to b will contain only // graph.WeightedEdge. // If useEmpty is true, graph iterators will be checked for the use of // graph.Empty if they are empty. func ReturnAllWeightedEdges(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } var got []Edge switch eg := g.(type) { case weightedEdgeLister: it := eg.WeightedEdges() if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) for it.Next() { e := it.WeightedEdge() got = append(got, e) switch g := g.(type) { case graph.Weighted: qe := g.WeightedEdge(e.From().ID(), e.To().ID()) if qe == nil { t.Errorf("missing edge for test %q: %v", test.name, e) } else if qe.From().ID() != e.From().ID() || qe.To().ID() != e.To().ID() { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, e.From().ID(), e.To().ID(), qe) } default: t.Logf("weighted edge lister is not a weighted graph - are you sure?: %T", g) qe := g.Edge(e.From().ID(), e.To().ID()) if qe == nil { t.Errorf("missing edge for test %q: %v", test.name, e) } else if qe.From().ID() != e.From().ID() || qe.To().ID() != e.To().ID() { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, e.From().ID(), e.To().ID(), qe) } } if g.Node(e.From().ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, e.From().ID()) } if g.Node(e.To().ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, e.To().ID()) } } default: t.Errorf("invalid type for test: %T cannot return weighted edge iterator", g) continue } checkEdges(t, test.name, g, got, want) } } // ReturnWeightedEdgeSlice tests the constructed graph for the ability to // return all the edges it claims it has used in its construction using the // WeightedEdgeSlicer interface. This is a check of the Edges method of // graph.Graph and the iterator that is returned. ReturnWeightedEdgeSlice // also checks that the edge end nodes exist within the graph, checking // the Node method of graph.Graph. // // The edges used within and returned by the Builder function should be // graph.WeightedEdge. The edge parameter passed to b will contain only // graph.WeightedEdge. // If useEmpty is true, graph iterators will be checked for the use of // graph.Empty if they are empty. func ReturnWeightedEdgeSlice(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } var got []Edge switch eg := g.(type) { case weightedEdgeLister: it := eg.WeightedEdges() if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) s, ok := it.(graph.WeightedEdgeSlicer) if !ok { t.Errorf("invalid type for test %T: cannot return weighted edge slice", g) continue } for _, e := range s.WeightedEdgeSlice() { got = append(got, e) qe := g.Edge(e.From().ID(), e.To().ID()) if qe == nil { t.Errorf("missing edge for test %q: %v", test.name, e) } else if qe.From().ID() != e.From().ID() || qe.To().ID() != e.To().ID() { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, e.From().ID(), e.To().ID(), qe) } if g.Node(e.From().ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, e.From().ID()) } if g.Node(e.To().ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, e.To().ID()) } } default: t.Errorf("invalid type for test: %T cannot return weighted edge iterator", g) continue } checkEdges(t, test.name, g, got, want) } } // ReturnAllWeightedLines tests the constructed graph for the ability to return // all the edges it claims it has used in its construction and then recover all // the lines that contribute to those edges. This is a check of the Edges // method of graph.Graph and the iterator that is returned and the graph.Lines // implementation of those edges. ReturnAllWeightedLines also checks that the // edge end nodes exist within the graph, checking the Node method of // graph.Graph. // // The edges used within and returned by the Builder function should be // graph.WeightedLine. The edge parameter passed to b will contain only // graph.WeightedLine. // If useEmpty is true, graph iterators will be checked for the use of // graph.Empty if they are empty. func ReturnAllWeightedLines(t *testing.T, b Builder, useEmpty bool) { for _, test := range testCases { g, _, want, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } var got []Edge switch eg := g.(type) { case weightedEdgeLister: it := eg.WeightedEdges() if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) for _, e := range graph.WeightedEdgesOf(it) { qe := g.Edge(e.From().ID(), e.To().ID()) if qe == nil { t.Errorf("missing edge for test %q: %v", test.name, e) } else if qe.From().ID() != e.From().ID() || qe.To().ID() != e.To().ID() { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, e.From().ID(), e.To().ID(), qe) } // FIXME(kortschak): This would not be necessary // if graph.WeightedLines (and by symmetry) // graph.WeightedEdges also were graph.Lines // and graph.Edges. switch lit := e.(type) { case graph.Lines: if !isValidIterator(lit) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, lit) continue } checkEmptyIterator(t, lit, useEmpty) for lit.Next() { got = append(got, lit.Line()) } case graph.WeightedLines: if !isValidIterator(lit) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, lit) continue } checkEmptyIterator(t, lit, useEmpty) for lit.Next() { got = append(got, lit.WeightedLine()) } default: continue } if g.Node(e.From().ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, e.From().ID()) } if g.Node(e.To().ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, e.To().ID()) } } default: t.Errorf("invalid type for test: %T cannot return edge iterator", g) continue } checkEdges(t, test.name, g, got, want) } } // checkEdges compares got and want for the given graph type. func checkEdges(t *testing.T, name string, g graph.Graph, got, want []Edge) { t.Helper() switch g.(type) { case graph.Undirected: sortLexicalUndirectedEdges(got) sortLexicalUndirectedEdges(want) if !undirectedEdgeSetEqual(got, want) { t.Errorf("unexpected edges result for test %q:\ngot: %#v\nwant:%#v", name, got, want) } default: sortLexicalEdges(got) sortLexicalEdges(want) if !reflect.DeepEqual(got, want) { t.Errorf("unexpected edges result for test %q:\ngot: %#v\nwant:%#v", name, got, want) } } } // EdgeExistence tests the constructed graph for the ability to correctly // return the existence of edges within the graph. This is a check of the // Edge methods of graph.Graph, the EdgeBetween method of graph.Undirected // and the EdgeFromTo method of graph.Directed. EdgeExistence also checks // that the nodes and traversed edges exist within the graph, checking the // Node, Edge, EdgeBetween and HasEdgeBetween methods of graph.Graph, the // EdgeBetween method of graph.Undirected and the HasEdgeFromTo method of // graph.Directed. If reversedEdge is true, edges will be checked to make // sure edges returned match the orientation of an Edge or WeightedEdge // call. func EdgeExistence(t *testing.T, b Builder, reversedEdge bool) { for _, test := range testCases { g, nodes, edges, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } want := make(map[edge]bool) for _, e := range edges { want[edge{f: e.From().ID(), t: e.To().ID()}] = true } for _, x := range nodes { for _, y := range nodes { between := want[edge{f: x.ID(), t: y.ID()}] || want[edge{f: y.ID(), t: x.ID()}] if has := g.HasEdgeBetween(x.ID(), y.ID()); has != between { if has { t.Errorf("unexpected edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } else { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } } else { if want[edge{f: x.ID(), t: y.ID()}] { e := g.Edge(x.ID(), y.ID()) if e == nil || !hasEnds(x, y, e) { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } else if reversedEdge && (e.From().ID() != x.ID() || e.To().ID() != y.ID()) { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, x.ID(), y.ID(), e) } } if between && !g.HasEdgeBetween(x.ID(), y.ID()) { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } if g.Node(x.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, x.ID()) } if g.Node(y.ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, y.ID()) } } switch g := g.(type) { case graph.Directed: u := x v := y if has := g.HasEdgeFromTo(u.ID(), v.ID()); has != want[edge{f: u.ID(), t: v.ID()}] { if has { t.Errorf("unexpected edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } else { t.Errorf("missing edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } continue } // Edge has already been tested above. if g.Node(u.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, u.ID()) } if g.Node(v.ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, v.ID()) } case graph.Undirected: // HasEdgeBetween is already tested above. if between && g.Edge(x.ID(), y.ID()) == nil { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } if between && g.EdgeBetween(x.ID(), y.ID()) == nil { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } } switch g := g.(type) { case graph.WeightedDirected: u := x v := y if has := g.WeightedEdge(u.ID(), v.ID()) != nil; has != want[edge{f: u.ID(), t: v.ID()}] { if has { t.Errorf("unexpected edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } else { t.Errorf("missing edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } continue } case graph.WeightedUndirected: // HasEdgeBetween is already tested above. if between && g.WeightedEdge(x.ID(), y.ID()) == nil { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } if between && g.WeightedEdgeBetween(x.ID(), y.ID()) == nil { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } } } } } } // LineExistence tests the constructed graph for the ability to correctly // return the existence of lines within the graph. This is a check of the // Line methods of graph.Multigraph, the EdgeBetween method of graph.Undirected // and the EdgeFromTo method of graph.Directed. LineExistence also checks // that the nodes and traversed edges exist within the graph, checking the // Node, Edge, EdgeBetween and HasEdgeBetween methods of graph.Graph, the // EdgeBetween method of graph.Undirected and the HasEdgeFromTo method of // graph.Directed. If reversedLine is true, lines will be checked to make // sure lines returned match the orientation of an Line or WeightedLine // call. func LineExistence(t *testing.T, b Builder, useEmpty, reversedLine bool) { for _, test := range testCases { g, nodes, edges, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } switch mg := g.(type) { case graph.Multigraph: want := make(map[edge]bool) for _, e := range edges { want[edge{f: e.From().ID(), t: e.To().ID()}] = true } for _, x := range nodes { for _, y := range nodes { between := want[edge{f: x.ID(), t: y.ID()}] || want[edge{f: y.ID(), t: x.ID()}] if has := g.HasEdgeBetween(x.ID(), y.ID()); has != between { if has { t.Errorf("unexpected edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } else { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } } else { if want[edge{f: x.ID(), t: y.ID()}] { lit := mg.Lines(x.ID(), y.ID()) if !isValidIterator(lit) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, lit) continue } checkEmptyIterator(t, lit, useEmpty) if lit.Len() == 0 { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } else { for lit.Next() { l := lit.Line() if l == nil || !hasEnds(x, y, l) { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } else if reversedLine && (l.From().ID() != x.ID() || l.To().ID() != y.ID()) { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, x.ID(), y.ID(), l) } } } } if between && !g.HasEdgeBetween(x.ID(), y.ID()) { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } if g.Node(x.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, x.ID()) } if g.Node(y.ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, y.ID()) } } switch g := g.(type) { case graph.DirectedMultigraph: u := x v := y if has := g.HasEdgeFromTo(u.ID(), v.ID()); has != want[edge{f: u.ID(), t: v.ID()}] { if has { t.Errorf("unexpected edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } else { t.Errorf("missing edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } continue } // Edge has already been tested above. if g.Node(u.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, u.ID()) } if g.Node(v.ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, v.ID()) } case graph.UndirectedMultigraph: // HasEdgeBetween is already tested above. lit := g.Lines(x.ID(), y.ID()) if !isValidIterator(lit) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, lit) continue } checkEmptyIterator(t, lit, useEmpty) if between && lit.Len() == 0 { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } else { for lit.Next() { l := lit.Line() if l == nil || !hasEnds(x, y, l) { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } else if reversedLine && (l.From().ID() != x.ID() || l.To().ID() != y.ID()) { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, x.ID(), y.ID(), l) } } } lit = g.LinesBetween(x.ID(), y.ID()) if !isValidIterator(lit) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, lit) continue } checkEmptyIterator(t, lit, useEmpty) if between && lit.Len() == 0 { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } else { for lit.Next() { l := lit.Line() if l == nil || !hasEnds(x, y, l) { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } else if reversedLine && (l.From().ID() != x.ID() || l.To().ID() != y.ID()) { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, x.ID(), y.ID(), l) } } } } switch g := g.(type) { case graph.WeightedDirectedMultigraph: u := x v := y lit := g.WeightedLines(u.ID(), v.ID()) if !isValidIterator(lit) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, lit) continue } checkEmptyIterator(t, lit, useEmpty) if has := lit != graph.Empty; has != want[edge{f: u.ID(), t: v.ID()}] { if has { t.Errorf("unexpected edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } else { t.Errorf("missing edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } continue } for lit.Next() { l := lit.WeightedLine() if l.From().ID() != x.ID() || l.To().ID() != y.ID() { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, x.ID(), y.ID(), l) } } // Edge has already been tested above. if g.Node(u.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, u.ID()) } if g.Node(v.ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, v.ID()) } case graph.WeightedUndirectedMultigraph: // HasEdgeBetween is already tested above. lit := g.WeightedLines(x.ID(), y.ID()) if !isValidIterator(lit) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, lit) continue } checkEmptyIterator(t, lit, useEmpty) if between && lit.Len() == 0 { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } else { for lit.Next() { l := lit.WeightedLine() if reversedLine && (l.From().ID() != x.ID() || l.To().ID() != y.ID()) { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, x.ID(), y.ID(), l) } } } lit = g.WeightedLinesBetween(x.ID(), y.ID()) if !isValidIterator(lit) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, lit) continue } checkEmptyIterator(t, lit, useEmpty) if between && lit.Len() == 0 { t.Errorf("missing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } else { for lit.Next() { l := lit.WeightedLine() if reversedLine && (l.From().ID() != x.ID() || l.To().ID() != y.ID()) { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, x.ID(), y.ID(), l) } } } } } } default: t.Errorf("invalid type for test: %T not a multigraph", g) continue } } } // ReturnAdjacentNodes tests the constructed graph for the ability to correctly // return the nodes reachable from each node within the graph. This is a check // of the From method of graph.Graph and the To method of graph.Directed. // ReturnAdjacentNodes also checks that the nodes and traversed edges exist // within the graph, checking the Node, Edge, EdgeBetween and HasEdgeBetween // methods of graph.Graph, the EdgeBetween method of graph.Undirected and the // HasEdgeFromTo method of graph.Directed. // If useEmpty is true, graph iterators will be checked for the use of // graph.Empty if they are empty. If reversedEdge is true, edges will be checked // to make sure edges returned match the orientation of an Edge or WeightedEdge // call. func ReturnAdjacentNodes(t *testing.T, b Builder, useEmpty, reversedEdge bool) { for _, test := range testCases { g, nodes, edges, _, _, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } want := make(map[edge]bool) for _, e := range edges { want[edge{f: e.From().ID(), t: e.To().ID()}] = true if g.From(e.From().ID()).Len() == 0 { t.Errorf("missing path from node %v with outbound edge %v", e.From().ID(), e) } } for _, x := range nodes { switch g := g.(type) { case graph.Directed: // Test forward. u := x it := g.From(u.ID()) if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) for i := 0; it.Next(); i++ { v := it.Node() if i == 0 && g.Node(u.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, u.ID()) } if g.Node(v.ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, v.ID()) } qe := g.Edge(u.ID(), v.ID()) if qe == nil { t.Errorf("missing from edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } else if qe.From().ID() != u.ID() || qe.To().ID() != v.ID() { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, u.ID(), v.ID(), qe) } if !g.HasEdgeBetween(u.ID(), v.ID()) { t.Errorf("missing from edge for test %q: (%v)--(%v)", test.name, u.ID(), v.ID()) } if !g.HasEdgeFromTo(u.ID(), v.ID()) { t.Errorf("missing from edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } if !want[edge{f: u.ID(), t: v.ID()}] { t.Errorf("unexpected edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } } // Test backward. v := x it = g.To(v.ID()) if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) for i := 0; it.Next(); i++ { u := it.Node() if i == 0 && g.Node(v.ID()) == nil { t.Errorf("missing to node for test %q: %v", test.name, v.ID()) } if g.Node(u.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, u.ID()) } qe := g.Edge(u.ID(), v.ID()) if qe == nil { t.Errorf("missing from edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) continue } if qe.From().ID() != u.ID() || qe.To().ID() != v.ID() { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, u.ID(), v.ID(), qe) } if !g.HasEdgeBetween(u.ID(), v.ID()) { t.Errorf("missing from edge for test %q: (%v)--(%v)", test.name, u.ID(), v.ID()) continue } if !g.HasEdgeFromTo(u.ID(), v.ID()) { t.Errorf("missing from edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) continue } if !want[edge{f: u.ID(), t: v.ID()}] { t.Errorf("unexpected edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } } for _, e := range edges { if g.To(e.To().ID()).Len() == 0 { t.Errorf("missing path to node %v with inbound edge %v", e.To().ID(), e) } } case graph.Undirected: u := x it := g.From(u.ID()) if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) for i := 0; it.Next(); i++ { v := it.Node() if i == 0 && g.Node(u.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, u.ID()) } qe := g.Edge(u.ID(), v.ID()) if qe == nil || !hasEnds(u, v, qe) { t.Errorf("missing from edge for test %q: (%v)--(%v)", test.name, u.ID(), v.ID()) continue } if reversedEdge && (qe.From().ID() != u.ID() || qe.To().ID() != v.ID()) { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, u.ID(), v.ID(), qe) } qe = g.EdgeBetween(u.ID(), v.ID()) if qe == nil || !hasEnds(u, v, qe) { t.Errorf("missing from edge for test %q: (%v)--(%v)", test.name, u.ID(), v.ID()) continue } if reversedEdge && (qe.From().ID() != u.ID() || qe.To().ID() != v.ID()) { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, u.ID(), v.ID(), qe) } if !g.HasEdgeBetween(u.ID(), v.ID()) { t.Errorf("missing from edge for test %q: (%v)--(%v)", test.name, u.ID(), v.ID()) continue } between := want[edge{f: u.ID(), t: v.ID()}] || want[edge{f: v.ID(), t: u.ID()}] if !between { t.Errorf("unexpected edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } } default: u := x it := g.From(u.ID()) if !isValidIterator(it) { t.Errorf("invalid iterator for test %q: got:%#v", test.name, it) continue } checkEmptyIterator(t, it, useEmpty) for i := 0; it.Next(); i++ { v := it.Node() if i == 0 && g.Node(u.ID()) == nil { t.Errorf("missing from node for test %q: %v", test.name, u.ID()) } qe := g.Edge(u.ID(), v.ID()) if qe == nil { t.Errorf("missing from edge for test %q: (%v)--(%v)", test.name, u.ID(), v.ID()) continue } if qe.From().ID() != u.ID() || qe.To().ID() != v.ID() { t.Errorf("inverted edge for test %q query with F=%d T=%d: got:%#v", test.name, u.ID(), v.ID(), qe) } if !g.HasEdgeBetween(u.ID(), v.ID()) { t.Errorf("missing from edge for test %q: (%v)--(%v)", test.name, u.ID(), v.ID()) continue } between := want[edge{f: u.ID(), t: v.ID()}] || want[edge{f: v.ID(), t: u.ID()}] if !between { t.Errorf("unexpected edge for test %q: (%v)->(%v)", test.name, u.ID(), v.ID()) } } } } } } // Weight tests the constructed graph for the ability to correctly return // the weight between to nodes, checking the Weight method of graph.Weighted. // // The self and absent values returned by the Builder should match the values // used by the Weight method. func Weight(t *testing.T, b Builder) { for _, test := range testCases { g, nodes, _, self, absent, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } wg, ok := g.(graph.Weighted) if !ok { t.Errorf("invalid graph type for test %q: %T is not graph.Weighted", test.name, g) } _, multi := g.(graph.Multigraph) for _, x := range nodes { for _, y := range nodes { w, ok := wg.Weight(x.ID(), y.ID()) e := wg.WeightedEdge(x.ID(), y.ID()) switch { case !ok: if e != nil { t.Errorf("missing edge weight for existing edge for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) } if !scalar.Same(w, absent) { t.Errorf("unexpected absent weight for test %q: got:%v want:%v", test.name, w, absent) } case !multi && x.ID() == y.ID(): if !scalar.Same(w, self) { t.Errorf("unexpected self weight for test %q: got:%v want:%v", test.name, w, self) } case e == nil: t.Errorf("missing edge for existing non-self weight for test %q: (%v)--(%v)", test.name, x.ID(), y.ID()) case e.Weight() != w: t.Errorf("weight mismatch for test %q: edge=%v graph=%v", test.name, e.Weight(), w) } } } } } // AdjacencyMatrix tests the constructed graph for the ability to correctly // return an adjacency matrix that matches the weights returned by the graphs // Weight method. // // The self and absent values returned by the Builder should match the values // used by the Weight method. func AdjacencyMatrix(t *testing.T, b Builder) { for _, test := range testCases { g, nodes, _, self, absent, ok := b(test.nodes, test.edges, test.self, test.absent) if !ok { t.Logf("skipping test case: %q", test.name) continue } wg, ok := g.(graph.Weighted) if !ok { t.Errorf("invalid graph type for test %q: %T is not graph.Weighted", test.name, g) } mg, ok := g.(matrixer) if !ok { t.Errorf("invalid graph type for test %q: %T cannot return adjacency matrix", test.name, g) } m := mg.Matrix() r, c := m.Dims() if r != c || r != len(nodes) { t.Errorf("dimension mismatch for test %q: r=%d c=%d order=%d", test.name, r, c, len(nodes)) } for _, x := range nodes { i := int(x.ID()) for _, y := range nodes { j := int(y.ID()) w, ok := wg.Weight(x.ID(), y.ID()) switch { case !ok: if !scalar.Same(m.At(i, j), absent) { t.Errorf("weight mismatch for test %q: (%v)--(%v) matrix=%v graph=%v", test.name, x.ID(), y.ID(), m.At(i, j), w) } case x.ID() == y.ID(): if !scalar.Same(m.At(i, j), self) { t.Errorf("weight mismatch for test %q: (%v)--(%v) matrix=%v graph=%v", test.name, x.ID(), y.ID(), m.At(i, j), w) } default: if !scalar.Same(m.At(i, j), w) { t.Errorf("weight mismatch for test %q: (%v)--(%v) matrix=%v graph=%v", test.name, x.ID(), y.ID(), m.At(i, j), w) } } } } } } // sortLexicalEdges sorts a collection of edges lexically on the // keys: from.ID > to.ID > [line.ID] > [weight]. func sortLexicalEdges(edges []Edge) { slices.SortFunc(edges, func(a, b Edge) int { if n := cmp.Compare(a.From().ID(), b.From().ID()); n != 0 { return n } if n := cmp.Compare(a.To().ID(), b.To().ID()); n != 0 { return n } la, oka := a.(graph.Line) lb, okb := b.(graph.Line) if oka != okb { panic(fmt.Sprintf("testgraph: mismatched types %T != %T", a, b)) } if oka { if n := cmp.Compare(la.ID(), lb.ID()); n != 0 { return n } } return cmpWeight(a, b) }) } // sortLexicalUndirectedEdges sorts a collection of edges lexically on the // keys: lo.ID > hi.ID > [line.ID] > [weight]. func sortLexicalUndirectedEdges(edges []Edge) { slices.SortFunc(edges, func(a, b Edge) int { lida, hida, _ := undirectedIDs(a) lidb, hidb, _ := undirectedIDs(b) if n := cmp.Compare(lida, lidb); n != 0 { return n } if n := cmp.Compare(hida, hidb); n != 0 { return n } la, oka := a.(graph.Line) lb, okb := b.(graph.Line) if oka != okb { panic(fmt.Sprintf("testgraph: mismatched types %T != %T", a, b)) } if oka { if n := cmp.Compare(la.ID(), lb.ID()); n != 0 { return n } } return cmpWeight(a, b) }) } func cmpWeight(a, b Edge) int { wea, oka := a.(graph.WeightedEdge) web, okb := b.(graph.WeightedEdge) if oka != okb { panic(fmt.Sprintf("testgraph: mismatched types %T != %T", a, b)) } if !oka { return 0 } return cmp.Compare(wea.Weight(), web.Weight()) } // undirectedEdgeSetEqual returned whether a pair of undirected edge // slices sorted by lexicalUndirectedEdges are equal. func undirectedEdgeSetEqual(a, b []Edge) bool { if len(a) == 0 && len(b) == 0 { return true } if len(a) == 0 || len(b) == 0 { return false } if !undirectedEdgeEqual(a[0], b[0]) { return false } i, j := 0, 0 for { switch { case i == len(a)-1 && j == len(b)-1: return true case i < len(a)-1 && undirectedEdgeEqual(a[i+1], b[j]): i++ case j < len(b)-1 && undirectedEdgeEqual(a[i], b[j+1]): j++ case i < len(a)-1 && j < len(b)-1 && undirectedEdgeEqual(a[i+1], b[j+1]): i++ j++ default: return false } } } // undirectedEdgeEqual returns whether a pair of undirected edges are equal // after canonicalising from and to IDs by numerical sort order. func undirectedEdgeEqual(a, b Edge) bool { loa, hia, inva := undirectedIDs(a) lob, hib, invb := undirectedIDs(b) // Use reflect.DeepEqual if the edges are parallel // rather anti-parallel. if inva == invb { return reflect.DeepEqual(a, b) } if loa != lob || hia != hib { return false } la, oka := a.(graph.Line) lb, okb := b.(graph.Line) if !oka && !okb { return true } if la.ID() != lb.ID() { return false } wea, oka := a.(graph.WeightedEdge) web, okb := b.(graph.WeightedEdge) if !oka && !okb { return true } return wea.Weight() == web.Weight() } // NodeAdder is a graph.NodeAdder graph. type NodeAdder interface { graph.Graph graph.NodeAdder } // AddNodes tests whether g correctly implements the graph.NodeAdder interface. // AddNodes gets a new node from g and adds it to the graph, repeating this pair // of operations n times. The existence of added nodes is confirmed in the graph. // AddNodes also checks that re-adding each of the added nodes causes a panic. // If g satisfies NodeWithIDer, the NodeWithID method is tested for an additional // n rounds of node addition using NodeWithID to create new nodes as well as // confirming that NodeWithID returns existing nodes. func AddNodes(t *testing.T, g NodeAdder, n int) { defer func() { r := recover() if r != nil { t.Errorf("unexpected panic: %v", r) } }() var addedNodes []graph.Node for i := 0; i < n; i++ { node := g.NewNode() prev := len(graph.NodesOf(g.Nodes())) if g.Node(node.ID()) != nil { curr := g.Nodes().Len() if curr != prev { t.Fatalf("NewNode mutated graph: prev graph order != curr graph order, %d != %d", prev, curr) } t.Fatalf("NewNode returned existing: %#v", node) } g.AddNode(node) addedNodes = append(addedNodes, node) curr := len(graph.NodesOf(g.Nodes())) if curr != prev+1 { t.Fatalf("AddNode failed to mutate graph: curr graph order != prev graph order+1, %d != %d", curr, prev+1) } if g.Node(node.ID()) == nil { t.Fatalf("AddNode failed to add node to graph trying to add %#v", node) } } order.ByID(addedNodes) graphNodes := graph.NodesOf(g.Nodes()) order.ByID(graphNodes) if !reflect.DeepEqual(addedNodes, graphNodes) { if n > 20 { t.Errorf("unexpected node set after node addition: got len:%v want len:%v", len(graphNodes), len(addedNodes)) } else { t.Errorf("unexpected node set after node addition: got:\n %v\nwant:\n%v", graphNodes, addedNodes) } } it := g.Nodes() for it.Next() { panicked := panics(func() { g.AddNode(it.Node()) }) if !panicked { t.Fatalf("expected panic adding existing node: %v", it.Node()) } } if gwi, ok := g.(graph.NodeWithIDer); ok { // Test existing nodes. it := g.Nodes() for it.Next() { id := it.Node().ID() n, new := gwi.NodeWithID(id) if n == nil { t.Errorf("unexpected nil node for existing node with ID=%d", id) } if new { t.Errorf("unexpected new node for existing node with ID=%d", id) } } // Run n rounds of ID-specified node addition. for i := 0; i < n; i++ { id := g.NewNode().ID() // Get a guaranteed non-existing node. n, new := gwi.NodeWithID(id) if n == nil { // Could not create a node, valid behaviour. continue } if !new { t.Errorf("unexpected old node for non-existing node with ID=%d", id) } g.AddNode(n) // Use the node to advance to a new non-existing node. } } } // AddArbitraryNodes tests whether g correctly implements the AddNode method. Not all // graph.NodeAdder graphs are expected to implement the semantics of this test. // AddArbitraryNodes iterates over add, adding each node to the graph. The existence // of each added node in the graph is confirmed. func AddArbitraryNodes(t *testing.T, g NodeAdder, add graph.Nodes) { defer func() { r := recover() if r != nil { t.Errorf("unexpected panic: %v", r) } }() for add.Next() { node := add.Node() prev := len(graph.NodesOf(g.Nodes())) g.AddNode(node) curr := len(graph.NodesOf(g.Nodes())) if curr != prev+1 { t.Fatalf("AddNode failed to mutate graph: curr graph order != prev graph order+1, %d != %d", curr, prev+1) } if g.Node(node.ID()) == nil { t.Fatalf("AddNode failed to add node to graph trying to add %#v", node) } } add.Reset() addedNodes := graph.NodesOf(add) order.ByID(addedNodes) graphNodes := graph.NodesOf(g.Nodes()) order.ByID(graphNodes) if !reflect.DeepEqual(addedNodes, graphNodes) { t.Errorf("unexpected node set after node addition: got:\n %v\nwant:\n%v", graphNodes, addedNodes) } it := g.Nodes() for it.Next() { panicked := panics(func() { g.AddNode(it.Node()) }) if !panicked { t.Fatalf("expected panic adding existing node: %v", it.Node()) } } } // NodeRemover is a graph.NodeRemover graph. type NodeRemover interface { graph.Graph graph.NodeRemover } // RemoveNodes tests whether g correctly implements the graph.NodeRemover interface. // The input graph g must contain a set of nodes with some edges between them. func RemoveNodes(t *testing.T, g NodeRemover) { defer func() { r := recover() if r != nil { t.Errorf("unexpected panic: %v", r) } }() it := g.Nodes() first := true for it.Next() { u := it.Node() seen := make(map[edge]struct{}) // Collect all incident edges. var incident []graph.Edge to := g.From(u.ID()) for to.Next() { v := to.Node() e := g.Edge(u.ID(), v.ID()) if e == nil { t.Fatalf("bad graph: neighbors not connected: u=%#v v=%#v", u, v) } if _, ok := g.(graph.Undirected); ok { seen[edge{f: e.To().ID(), t: e.From().ID()}] = struct{}{} } seen[edge{f: e.From().ID(), t: e.To().ID()}] = struct{}{} incident = append(incident, e) } // Collect all other edges. var others []graph.Edge currit := g.Nodes() for currit.Next() { u := currit.Node() to := g.From(u.ID()) for to.Next() { v := to.Node() e := g.Edge(u.ID(), v.ID()) if e == nil { t.Fatalf("bad graph: neighbors not connected: u=%#v v=%#v", u, v) } seen[edge{f: e.From().ID(), t: e.To().ID()}] = struct{}{} others = append(others, e) } } if first && len(seen) == 0 { t.Fatal("incomplete test: no edges in graph") } first = false prev := len(graph.NodesOf(g.Nodes())) g.RemoveNode(u.ID()) curr := len(graph.NodesOf(g.Nodes())) if curr != prev-1 { t.Fatalf("RemoveNode failed to mutate graph: curr graph order != prev graph order-1, %d != %d", curr, prev-1) } if g.Node(u.ID()) != nil { t.Fatalf("failed to remove node: %#v", u) } for _, e := range incident { if g.HasEdgeBetween(e.From().ID(), e.To().ID()) { t.Fatalf("RemoveNode failed to remove connected edge: %#v", e) } } for _, e := range others { if e.From().ID() == u.ID() || e.To().ID() == u.ID() { continue } if g.Edge(e.From().ID(), e.To().ID()) == nil { t.Fatalf("RemoveNode %v removed unconnected edge: %#v", u, e) } } } } // EdgeAdder is a graph.EdgeAdder graph. type EdgeAdder interface { graph.Graph graph.EdgeAdder } // AddEdges tests whether g correctly implements the graph.EdgeAdder interface. // AddEdges creates n pairs of nodes with random IDs in [0,n) and joins edges // each node in the pair using SetEdge. AddEdges confirms that the end point // nodes are added to the graph and that the edges are stored in the graph. // If canLoop is true, self edges may be created. If canSet is true, a second // call to SetEdge is made for each edge to confirm that the nodes corresponding // the end points are updated. func AddEdges(t *testing.T, n int, g EdgeAdder, newNode func(id int64) graph.Node, canLoop, canSetNode bool) { defer func() { r := recover() if r != nil { t.Errorf("unexpected panic: %v", r) } }() type altNode struct { graph.Node } rnd := rand.New(rand.NewPCG(1, 1)) for i := 0; i < n; i++ { u := newNode(rnd.Int64N(int64(n))) var v graph.Node for { v = newNode(rnd.Int64N(int64(n))) if g.Edge(u.ID(), v.ID()) != nil { continue } if canLoop || u.ID() != v.ID() { break } } e := g.NewEdge(u, v) if g.Edge(u.ID(), v.ID()) != nil { t.Fatalf("NewEdge returned existing: %#v", e) } g.SetEdge(e) if g.Edge(u.ID(), v.ID()) == nil { t.Fatalf("SetEdge failed to add edge: %#v", e) } if g.Node(u.ID()) == nil { t.Fatalf("SetEdge failed to add from node: %#v", u) } if g.Node(v.ID()) == nil { t.Fatalf("SetEdge failed to add to node: %#v", v) } if !canSetNode { continue } g.SetEdge(g.NewEdge(altNode{u}, altNode{v})) if nu := g.Node(u.ID()); nu == u { t.Fatalf("SetEdge failed to update from node: u=%#v nu=%#v", u, nu) } if nv := g.Node(v.ID()); nv == v { t.Fatalf("SetEdge failed to update to node: v=%#v nv=%#v", v, nv) } } } // WeightedEdgeAdder is a graph.EdgeAdder graph. type WeightedEdgeAdder interface { graph.Graph graph.WeightedEdgeAdder } // AddWeightedEdges tests whether g correctly implements the graph.WeightedEdgeAdder // interface. AddWeightedEdges creates n pairs of nodes with random IDs in [0,n) and // joins edges each node in the pair using SetWeightedEdge with weight w. // AddWeightedEdges confirms that the end point nodes are added to the graph and that // the edges are stored in the graph. If canLoop is true, self edges may be created. // If canSet is true, a second call to SetWeightedEdge is made for each edge to // confirm that the nodes corresponding the end points are updated. func AddWeightedEdges(t *testing.T, n int, g WeightedEdgeAdder, w float64, newNode func(id int64) graph.Node, canLoop, canSetNode bool) { defer func() { r := recover() if r != nil { t.Errorf("unexpected panic: %v", r) } }() type altNode struct { graph.Node } rnd := rand.New(rand.NewPCG(1, 1)) for i := 0; i < n; i++ { u := newNode(rnd.Int64N(int64(n))) var v graph.Node for { v = newNode(rnd.Int64N(int64(n))) if g.Edge(u.ID(), v.ID()) != nil { continue } if canLoop || u.ID() != v.ID() { break } } e := g.NewWeightedEdge(u, v, w) if g.Edge(u.ID(), v.ID()) != nil { t.Fatalf("NewEdge returned existing: %#v", e) } g.SetWeightedEdge(e) ne := g.Edge(u.ID(), v.ID()) if ne == nil { t.Fatalf("SetWeightedEdge failed to add edge: %#v", e) } we, ok := ne.(graph.WeightedEdge) if !ok { t.Fatalf("SetWeightedEdge failed to add weighted edge: %#v", e) } if we.Weight() != w { t.Fatalf("edge weight mismatch: got:%f want:%f", we.Weight(), w) } if g.Node(u.ID()) == nil { t.Fatalf("SetWeightedEdge failed to add from node: %#v", u) } if g.Node(v.ID()) == nil { t.Fatalf("SetWeightedEdge failed to add to node: %#v", v) } if !canSetNode { continue } g.SetWeightedEdge(g.NewWeightedEdge(altNode{u}, altNode{v}, w)) if nu := g.Node(u.ID()); nu == u { t.Fatalf("SetWeightedEdge failed to update from node: u=%#v nu=%#v", u, nu) } if nv := g.Node(v.ID()); nv == v { t.Fatalf("SetWeightedEdge failed to update to node: v=%#v nv=%#v", v, nv) } } } // NoLoopAddEdges tests whether g panics for self-loop addition. NoLoopAddEdges // adds n nodes with IDs in [0,n) and creates an edge from the graph with NewEdge. // NoLoopAddEdges confirms that this does not panic and then adds the edge to the // graph to ensure that SetEdge will panic when adding a self-loop. func NoLoopAddEdges(t *testing.T, n int, g EdgeAdder, newNode func(id int64) graph.Node) { defer func() { r := recover() if r != nil { t.Errorf("unexpected panic: %v", r) } }() for id := 0; id < n; id++ { node := newNode(int64(id)) e := g.NewEdge(node, node) panicked := panics(func() { g.SetEdge(e) }) if !panicked { t.Errorf("expected panic for self-edge: %#v", e) } } } // NoLoopAddWeightedEdges tests whether g panics for self-loop addition. NoLoopAddWeightedEdges // adds n nodes with IDs in [0,n) and creates an edge from the graph with NewWeightedEdge. // NoLoopAddWeightedEdges confirms that this does not panic and then adds the edge to the // graph to ensure that SetWeightedEdge will panic when adding a self-loop. func NoLoopAddWeightedEdges(t *testing.T, n int, g WeightedEdgeAdder, w float64, newNode func(id int64) graph.Node) { defer func() { r := recover() if r != nil { t.Errorf("unexpected panic: %v", r) } }() for id := 0; id < n; id++ { node := newNode(int64(id)) e := g.NewWeightedEdge(node, node, w) panicked := panics(func() { g.SetWeightedEdge(e) }) if !panicked { t.Errorf("expected panic for self-edge: %#v", e) } } } // LineAdder is a graph.LineAdder multigraph. type LineAdder interface { graph.Multigraph graph.LineAdder } // AddLines tests whether g correctly implements the graph.LineAdder interface. // AddLines creates n pairs of nodes with random IDs in [0,n) and joins edges // each node in the pair using SetLine. AddLines confirms that the end point // nodes are added to the graph and that the edges are stored in the graph. // If canSet is true, a second call to SetLine is made for each edge to confirm // that the nodes corresponding the end points are updated. func AddLines(t *testing.T, n int, g LineAdder, newNode func(id int64) graph.Node, canSetNode bool) { defer func() { r := recover() if r != nil { t.Errorf("unexpected panic: %v", r) } }() type altNode struct { graph.Node } rnd := rand.New(rand.NewPCG(1, 1)) seen := make(tripleInt64s) for i := 0; i < n; i++ { u := newNode(rnd.Int64N(int64(n))) v := newNode(rnd.Int64N(int64(n))) prev := g.Lines(u.ID(), v.ID()) l := g.NewLine(u, v) if seen.has(u.ID(), v.ID(), l.ID()) { t.Fatalf("NewLine returned an existing line: %#v", l) } if g.Lines(u.ID(), v.ID()).Len() != prev.Len() { t.Fatalf("NewLine added a line: %#v", l) } g.SetLine(l) seen.add(u.ID(), v.ID(), l.ID()) if g.Lines(u.ID(), v.ID()).Len() != prev.Len()+1 { t.Fatalf("SetLine failed to add line: %#v", l) } if g.Node(u.ID()) == nil { t.Fatalf("SetLine failed to add from node: %#v", u) } if g.Node(v.ID()) == nil { t.Fatalf("SetLine failed to add to node: %#v", v) } if !canSetNode { continue } g.SetLine(g.NewLine(altNode{u}, altNode{v})) if nu := g.Node(u.ID()); nu == u { t.Fatalf("SetLine failed to update from node: u=%#v nu=%#v", u, nu) } if nv := g.Node(v.ID()); nv == v { t.Fatalf("SetLine failed to update to node: v=%#v nv=%#v", v, nv) } } } // WeightedLineAdder is a graph.WeightedLineAdder multigraph. type WeightedLineAdder interface { graph.Multigraph graph.WeightedLineAdder } // AddWeightedLines tests whether g correctly implements the graph.WeightedEdgeAdder // interface. AddWeightedLines creates n pairs of nodes with random IDs in [0,n) and // joins edges each node in the pair using SetWeightedLine with weight w. // AddWeightedLines confirms that the end point nodes are added to the graph and that // the edges are stored in the graph. If canSet is true, a second call to SetWeightedLine // is made for each edge to confirm that the nodes corresponding the end points are // updated. func AddWeightedLines(t *testing.T, n int, g WeightedLineAdder, w float64, newNode func(id int64) graph.Node, canSetNode bool) { defer func() { r := recover() if r != nil { t.Errorf("unexpected panic: %v", r) } }() type altNode struct { graph.Node } rnd := rand.New(rand.NewPCG(1, 1)) seen := make(tripleInt64s) for i := 0; i < n; i++ { u := newNode(rnd.Int64N(int64(n))) v := newNode(rnd.Int64N(int64(n))) prev := g.Lines(u.ID(), v.ID()) l := g.NewWeightedLine(u, v, w) if seen.has(u.ID(), v.ID(), l.ID()) { t.Fatalf("NewWeightedLine returned an existing line: %#v", l) } if g.Lines(u.ID(), v.ID()).Len() != prev.Len() { t.Fatalf("NewWeightedLine added a line: %#v", l) } g.SetWeightedLine(l) seen.add(u.ID(), v.ID(), l.ID()) curr := g.Lines(u.ID(), v.ID()) if curr.Len() != prev.Len()+1 { t.Fatalf("SetWeightedLine failed to add line: %#v", l) } var found bool for curr.Next() { if curr.Line().ID() == l.ID() { found = true wl, ok := curr.Line().(graph.WeightedLine) if !ok { t.Fatalf("SetWeightedLine failed to add weighted line: %#v", l) } if wl.Weight() != w { t.Fatalf("line weight mismatch: got:%f want:%f", wl.Weight(), w) } break } } if !found { t.Fatalf("SetWeightedLine failed to add line: %#v", l) } if g.Node(u.ID()) == nil { t.Fatalf("SetWeightedLine failed to add from node: %#v", u) } if g.Node(v.ID()) == nil { t.Fatalf("SetWeightedLine failed to add to node: %#v", v) } if !canSetNode { continue } g.SetWeightedLine(g.NewWeightedLine(altNode{u}, altNode{v}, w)) if nu := g.Node(u.ID()); nu == u { t.Fatalf("SetWeightedLine failed to update from node: u=%#v nu=%#v", u, nu) } if nv := g.Node(v.ID()); nv == v { t.Fatalf("SetWeightedLine failed to update to node: v=%#v nv=%#v", v, nv) } } } // EdgeRemover is a graph.EdgeRemover graph. type EdgeRemover interface { graph.Graph graph.EdgeRemover } // RemoveEdges tests whether g correctly implements the graph.EdgeRemover interface. // The input graph g must contain a set of nodes with some edges between them. // RemoveEdges iterates over remove, which must contain edges in g, removing each // edge. RemoveEdges confirms that the edge is removed, leaving its end-point nodes // and all other edges in the graph. func RemoveEdges(t *testing.T, g EdgeRemover, remove graph.Edges) { edges := make(map[edge]struct{}) nodes := g.Nodes() for nodes.Next() { u := nodes.Node() uid := u.ID() to := g.From(uid) for to.Next() { v := to.Node() edges[edge{f: u.ID(), t: v.ID()}] = struct{}{} } } for remove.Next() { e := remove.Edge() if g.Edge(e.From().ID(), e.To().ID()) == nil { t.Fatalf("bad tests: missing edge: %#v", e) } if g.Node(e.From().ID()) == nil { t.Fatalf("bad tests: missing from node: %#v", e.From()) } if g.Node(e.To().ID()) == nil { t.Fatalf("bad tests: missing to node: %#v", e.To()) } g.RemoveEdge(e.From().ID(), e.To().ID()) if _, ok := g.(graph.Undirected); ok { delete(edges, edge{f: e.To().ID(), t: e.From().ID()}) } delete(edges, edge{f: e.From().ID(), t: e.To().ID()}) for ge := range edges { if g.Edge(ge.f, ge.t) == nil { t.Fatalf("unexpected missing edge after removing edge %#v: %#v", e, ge) } } if ne := g.Edge(e.From().ID(), e.To().ID()); ne != nil { t.Fatalf("expected nil edge: got:%#v", ne) } if g.Node(e.From().ID()) == nil { t.Fatalf("unexpected deletion of from node: %#v", e.From()) } if g.Node(e.To().ID()) == nil { t.Fatalf("unexpected deletion to node: %#v", e.To()) } } } // LineRemover is a graph.EdgeRemove graph. type LineRemover interface { graph.Multigraph graph.LineRemover } // RemoveLines tests whether g correctly implements the graph.LineRemover interface. // The input graph g must contain a set of nodes with some lines between them. // RemoveLines iterates over remove, which must contain lines in g, removing each // line. RemoveLines confirms that the line is removed, leaving its end-point nodes // and all other lines in the graph. func RemoveLines(t *testing.T, g LineRemover, remove graph.Lines) { // lines is the set of lines in the graph. // The presence of a key indicates that the // line should exist in the graph. The value // for each key is used to indicate whether // it has been found during testing. lines := make(map[edge]bool) nodes := g.Nodes() for nodes.Next() { u := nodes.Node() uid := u.ID() to := g.From(uid) for to.Next() { v := to.Node() lit := g.Lines(u.ID(), v.ID()) for lit.Next() { lines[edge{f: u.ID(), t: v.ID(), id: lit.Line().ID()}] = true } } } for remove.Next() { l := remove.Line() if g.Lines(l.From().ID(), l.To().ID()) == graph.Empty { t.Fatalf("bad tests: missing line: %#v", l) } if g.Node(l.From().ID()) == nil { t.Fatalf("bad tests: missing from node: %#v", l.From()) } if g.Node(l.To().ID()) == nil { t.Fatalf("bad tests: missing to node: %#v", l.To()) } prev := g.Lines(l.From().ID(), l.To().ID()) g.RemoveLine(l.From().ID(), l.To().ID(), l.ID()) if _, ok := g.(graph.Undirected); ok { delete(lines, edge{f: l.To().ID(), t: l.From().ID(), id: l.ID()}) } delete(lines, edge{f: l.From().ID(), t: l.To().ID(), id: l.ID()}) // Mark all lines as not found. for gl := range lines { lines[gl] = false } // Mark found lines. This could be done far more efficiently. for gl := range lines { lit := g.Lines(gl.f, gl.t) for lit.Next() { lid := lit.Line().ID() if lid == gl.id { lines[gl] = true break } } } for gl, found := range lines { if !found { t.Fatalf("unexpected missing line after removing line %#v: %#v", l, gl) } } if curr := g.Lines(l.From().ID(), l.To().ID()); curr.Len() != prev.Len()-1 { t.Fatalf("RemoveLine failed to mutate graph: curr edge size != prev edge size-1, %d != %d", curr.Len(), prev.Len()-1) } if g.Node(l.From().ID()) == nil { t.Fatalf("unexpected deletion of from node: %#v", l.From()) } if g.Node(l.To().ID()) == nil { t.Fatalf("unexpected deletion to node: %#v", l.To()) } } } // undirectedIDs returns a numerical sort ordered canonicalisation of the // IDs of e. func undirectedIDs(e Edge) (lo, hi int64, inverted bool) { lid := e.From().ID() hid := e.To().ID() if hid < lid { inverted = true hid, lid = lid, hid } return lid, hid, inverted } type edge struct { f, t, id int64 } func panics(fn func()) (ok bool) { defer func() { ok = recover() != nil }() fn() return } // RandomNodes implements the graph.Nodes interface for a set of random nodes. type RandomNodes struct { n int seed uint64 newNode func(int64) graph.Node curr int64 state *rand.Rand seen set.Ints[int64] count int } var _ graph.Nodes = (*RandomNodes)(nil) // NewRandomNodes returns a new implicit node iterator containing a set of n nodes // with IDs generated from a PRNG seeded by the given seed. // The provided new func maps the id to a graph.Node. func NewRandomNodes(n int, seed uint64, new func(id int64) graph.Node) *RandomNodes { return &RandomNodes{ n: n, seed: seed, newNode: new, state: rand.New(rand.NewPCG(seed, seed)), seen: make(set.Ints[int64]), count: 0, } } // Len returns the remaining number of nodes to be iterated over. func (n *RandomNodes) Len() int { return n.n - n.count } // Next returns whether the next call of Node will return a valid node. func (n *RandomNodes) Next() bool { if n.count >= n.n { return false } n.count++ for { sign := int64(1) if n.state.Float64() < 0.5 { sign *= -1 } n.curr = sign * n.state.Int64() if !n.seen.Has(n.curr) { n.seen.Add(n.curr) return true } } } // Node returns the current node of the iterator. Next must have been // called prior to a call to Node. func (n *RandomNodes) Node() graph.Node { if n.Len() == -1 || n.count == 0 { return nil } return n.newNode(n.curr) } // Reset returns the iterator to its initial state. func (n *RandomNodes) Reset() { n.state = rand.New(rand.NewPCG(n.seed, n.seed)) n.seen = make(set.Ints[int64]) n.count = 0 } // tripleInt64s is a set of [3]int64 identifiers. type tripleInt64s map[[3]int64]struct{} // add inserts an element into the set. func (s tripleInt64s) add(x, y, z int64) { s[[3]int64{x, y, z}] = struct{}{} } // has reports the existence of the element in the set. func (s tripleInt64s) has(x, y, z int64) bool { _, ok := s[[3]int64{x, y, z}] return ok }