mirror of
				https://github.com/gonum/gonum.git
				synced 2025-10-31 18:42:45 +08:00 
			
		
		
		
	graph/testgraph: add mutation tests
This commit is contained in:
		| @@ -13,6 +13,8 @@ import ( | |||||||
| 	"sort" | 	"sort" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"golang.org/x/exp/rand" | ||||||
|  |  | ||||||
| 	"gonum.org/v1/gonum/graph" | 	"gonum.org/v1/gonum/graph" | ||||||
| 	"gonum.org/v1/gonum/graph/internal/ordered" | 	"gonum.org/v1/gonum/graph/internal/ordered" | ||||||
| 	"gonum.org/v1/gonum/graph/internal/set" | 	"gonum.org/v1/gonum/graph/internal/set" | ||||||
| @@ -1010,6 +1012,678 @@ func undirectedEdgeEqual(a, b graph.Edge) bool { | |||||||
| 	return wea.Weight() == web.Weight() | 	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. | ||||||
|  | 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 := g.Nodes().Len() | ||||||
|  | 		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 := g.Nodes().Len() | ||||||
|  | 		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) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sort.Sort(ordered.ByID(addedNodes)) | ||||||
|  | 	graphNodes := graph.NodesOf(g.Nodes()) | ||||||
|  | 	sort.Sort(ordered.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()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 := g.Nodes().Len() | ||||||
|  | 		g.AddNode(node) | ||||||
|  | 		curr := g.Nodes().Len() | ||||||
|  | 		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) | ||||||
|  | 	sort.Sort(ordered.ByID(addedNodes)) | ||||||
|  | 	graphNodes := graph.NodesOf(g.Nodes()) | ||||||
|  | 	sort.Sort(ordered.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 := g.Nodes().Len() | ||||||
|  | 		g.RemoveNode(u.ID()) | ||||||
|  | 		curr := g.Nodes().Len() | ||||||
|  | 		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.NewSource(1)) | ||||||
|  | 	for i := 0; i < n; i++ { | ||||||
|  | 		u := newNode(rnd.Int63n(int64(n))) | ||||||
|  | 		var v graph.Node | ||||||
|  | 		for { | ||||||
|  | 			v = newNode(rnd.Int63n(int64(n))) | ||||||
|  | 			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.NewSource(1)) | ||||||
|  | 	for i := 0; i < n; i++ { | ||||||
|  | 		u := newNode(rnd.Int63n(int64(n))) | ||||||
|  | 		var v graph.Node | ||||||
|  | 		for { | ||||||
|  | 			v = newNode(rnd.Int63n(int64(n))) | ||||||
|  | 			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.NewSource(1)) | ||||||
|  | 	seen := make(set.Int64s) | ||||||
|  | 	for i := 0; i < n; i++ { | ||||||
|  | 		u := newNode(rnd.Int63n(int64(n))) | ||||||
|  | 		v := newNode(rnd.Int63n(int64(n))) | ||||||
|  | 		prev := g.Lines(u.ID(), v.ID()) | ||||||
|  | 		l := g.NewLine(u, v) | ||||||
|  | 		if seen.Has(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(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.NewSource(1)) | ||||||
|  | 	seen := make(set.Int64s) | ||||||
|  | 	for i := 0; i < n; i++ { | ||||||
|  | 		u := newNode(rnd.Int63n(int64(n))) | ||||||
|  | 		v := newNode(rnd.Int63n(int64(n))) | ||||||
|  | 		prev := g.Lines(u.ID(), v.ID()) | ||||||
|  | 		l := g.NewWeightedLine(u, v, w) | ||||||
|  | 		if seen.Has(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(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 | // undirectedIDs returns a numerical sort ordered canonicalisation of the | ||||||
| // IDs of e. | // IDs of e. | ||||||
| func undirectedIDs(e graph.Edge) (lo, hi int64, inverted bool) { | func undirectedIDs(e graph.Edge) (lo, hi int64, inverted bool) { | ||||||
| @@ -1023,9 +1697,87 @@ func undirectedIDs(e graph.Edge) (lo, hi int64, inverted bool) { | |||||||
| } | } | ||||||
|  |  | ||||||
| type edge struct { | type edge struct { | ||||||
| 	f, t int64 | 	f, t, id int64 | ||||||
| } | } | ||||||
|  |  | ||||||
| func same(a, b float64) bool { | func same(a, b float64) bool { | ||||||
| 	return (math.IsNaN(a) && math.IsNaN(b)) || a == b | 	return (math.IsNaN(a) && math.IsNaN(b)) || a == b | ||||||
| } | } | ||||||
|  |  | ||||||
|  | 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.Int64s | ||||||
|  | 	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.NewSource(seed)), | ||||||
|  | 		seen:  make(set.Int64s), | ||||||
|  | 		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.Int63() | ||||||
|  | 		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.NewSource(n.seed)) | ||||||
|  | 	n.seen = make(set.Int64s) | ||||||
|  | 	n.count = 0 | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										80
									
								
								graph/testgraph/testgraph_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								graph/testgraph/testgraph_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | // Copyright ©2019 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 | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"reflect" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"gonum.org/v1/gonum/graph" | ||||||
|  | 	"gonum.org/v1/gonum/graph/simple" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var randomNodesTests = []struct { | ||||||
|  | 	n    int | ||||||
|  | 	seed uint64 | ||||||
|  | 	new  func(int64) graph.Node | ||||||
|  | 	want []graph.Node | ||||||
|  | }{ | ||||||
|  | 	{ | ||||||
|  | 		n:    0, | ||||||
|  | 		want: nil, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		n:    1, | ||||||
|  | 		seed: 1, | ||||||
|  | 		new:  newSimpleNode, | ||||||
|  | 		want: []graph.Node{simple.Node(-106976941678315313)}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		n:    1, | ||||||
|  | 		seed: 2, | ||||||
|  | 		new:  newSimpleNode, | ||||||
|  | 		want: []graph.Node{simple.Node(6816453162648937526)}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		n:    4, | ||||||
|  | 		seed: 1, | ||||||
|  | 		new:  newSimpleNode, | ||||||
|  | 		want: []graph.Node{ | ||||||
|  | 			simple.Node(-106976941678315313), | ||||||
|  | 			simple.Node(867649948573917593), | ||||||
|  | 			simple.Node(-4246677790793934368), | ||||||
|  | 			simple.Node(406519965772129914), | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		n:    4, | ||||||
|  | 		seed: 2, | ||||||
|  | 		new:  newSimpleNode, | ||||||
|  | 		want: []graph.Node{ | ||||||
|  | 			simple.Node(6816453162648937526), | ||||||
|  | 			simple.Node(-4921844272880608907), | ||||||
|  | 			simple.Node(159088832891557680), | ||||||
|  | 			simple.Node(-2611333848016927708), | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newSimpleNode(id int64) graph.Node { return simple.Node(id) } | ||||||
|  |  | ||||||
|  | func TestRandomNodesIterate(t *testing.T) { | ||||||
|  | 	for _, test := range randomNodesTests { | ||||||
|  | 		for i := 0; i < 2; i++ { | ||||||
|  | 			it := NewRandomNodes(test.n, test.seed, test.new) | ||||||
|  | 			if it.Len() != len(test.want) { | ||||||
|  | 				t.Errorf("unexpected iterator length for round %d: got:%d want:%d", i, it.Len(), len(test.want)) | ||||||
|  | 			} | ||||||
|  | 			var got []graph.Node | ||||||
|  | 			for it.Next() { | ||||||
|  | 				got = append(got, it.Node()) | ||||||
|  | 			} | ||||||
|  | 			if !reflect.DeepEqual(got, test.want) { | ||||||
|  | 				t.Errorf("unexpected iterator output for round %d: got:%#v want:%#v", i, got, test.want) | ||||||
|  | 			} | ||||||
|  | 			it.Reset() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Dan Kortschak
					Dan Kortschak