diff --git a/pgraph/graphsync.go b/pgraph/graphsync.go index 6895bab4..b008f6ce 100644 --- a/pgraph/graphsync.go +++ b/pgraph/graphsync.go @@ -23,12 +23,25 @@ import ( errwrap "github.com/pkg/errors" ) +func strVertexCmpFn(v1, v2 Vertex) (bool, error) { + if v1.String() == "" || v2.String() == "" { + return false, fmt.Errorf("empty vertex") + } + return v1.String() == v2.String(), nil +} + +func strEdgeCmpFn(e1, e2 Edge) (bool, error) { + if e1.String() == "" || e2.String() == "" { + return false, fmt.Errorf("empty edge") + } + return e1.String() == e2.String(), nil +} + // GraphSync updates the Graph so that it matches the newGraph. It leaves // identical elements alone so that they don't need to be refreshed. // It tries to mutate existing elements into new ones, if they support this. // This updates the Graph on success only. // FIXME: should we do this with copies of the vertex resources? -// FIXME: add test cases func (obj *Graph) GraphSync(newGraph *Graph, vertexCmpFn func(Vertex, Vertex) (bool, error), vertexAddFn func(Vertex) error, vertexRemoveFn func(Vertex) error, edgeCmpFn func(Edge, Edge) (bool, error)) error { oldGraph := obj.Copy() // work on a copy of the old graph @@ -41,6 +54,19 @@ func (obj *Graph) GraphSync(newGraph *Graph, vertexCmpFn func(Vertex, Vertex) (b } oldGraph.SetName(newGraph.GetName()) // overwrite the name + if vertexCmpFn == nil { + vertexCmpFn = strVertexCmpFn // use simple string cmp version + } + if vertexAddFn == nil { + vertexAddFn = func(Vertex) error { return nil } // noop + } + if vertexRemoveFn == nil { + vertexRemoveFn = func(Vertex) error { return nil } // noop + } + if edgeCmpFn == nil { + edgeCmpFn = strEdgeCmpFn // use simple string cmp version + } + var lookup = make(map[Vertex]Vertex) var vertexKeep []Vertex // list of vertices which are the same in new graph var edgeKeep []Edge // list of vertices which are the same in new graph diff --git a/pgraph/graphsync_test.go b/pgraph/graphsync_test.go new file mode 100644 index 00000000..feacf053 --- /dev/null +++ b/pgraph/graphsync_test.go @@ -0,0 +1,92 @@ +// Mgmt +// Copyright (C) 2013-2017+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package pgraph + +import ( + "testing" +) + +func TestGraphSync1(t *testing.T) { + g := &Graph{} + v1 := NV("v1") + v2 := NV("v2") + v3 := NV("v3") + + e1 := NE("e1") + e2 := NE("e2") + g.AddEdge(v1, v3, e1) + g.AddEdge(v2, v3, e2) + + // new graph + newGraph := &Graph{} + v4 := NV("v4") + v5 := NV("v5") + e3 := NE("e3") + newGraph.AddEdge(v4, v5, e3) + + err := g.GraphSync(newGraph, nil, nil, nil, nil) + if err != nil { + t.Errorf("GraphSync failed: %v", err) + return + } + + // g should change and become the same + if s := runGraphCmp(t, g, newGraph); s != "" { + t.Errorf("%s", s) + } +} + +func TestGraphSync2(t *testing.T) { + v1 := NV("v1") + v2 := NV("v2") + v3 := NV("v3") + v4 := NV("v4") + v5 := NV("v5") + e1 := NE("e1") + e2 := NE("e2") + e3 := NE("e3") + + g := &Graph{} + g.AddEdge(v1, v3, e1) + g.AddEdge(v2, v3, e2) + + // new graph + newGraph := &Graph{} + newGraph.AddEdge(v1, v3, e1) + newGraph.AddEdge(v2, v3, e2) + newGraph.AddEdge(v4, v5, e3) + //newGraph.AddEdge(v3, v4, NE("v3,v4")) + //newGraph.AddEdge(v3, v5, NE("v3,v5")) + + // graphs should differ! + if runGraphCmp(t, g, newGraph) == "" { + t.Errorf("graphs should differ initially") + return + } + + err := g.GraphSync(newGraph, strVertexCmpFn, vertexAddFn, vertexRemoveFn, strEdgeCmpFn) + if err != nil { + t.Errorf("GraphSync failed: %v", err) + return + } + + // g should change and become the same + if s := runGraphCmp(t, g, newGraph); s != "" { + t.Errorf("%s", s) + } +} diff --git a/pgraph/pgraph_test.go b/pgraph/pgraph_test.go index dfedd24d..11da2fc9 100644 --- a/pgraph/pgraph_test.go +++ b/pgraph/pgraph_test.go @@ -18,43 +18,11 @@ package pgraph import ( - "fmt" "reflect" "testing" ) -// vertex is a test struct to test the library. -type vertex struct { - name string -} - -// String is a required method of the Vertex interface that we must fulfill. -func (v *vertex) String() string { - return v.name -} - -// NV is a helper function to make testing easier. It creates a new noop vertex. -func NV(s string) Vertex { - return &vertex{s} -} - -// edge is a test struct to test the library. -type edge struct { - name string -} - -// String is a required method of the Edge interface that we must fulfill. -func (e *edge) String() string { - return e.name -} - -// NE is a helper function to make testing easier. It creates a new noop edge. -func NE(s string) Edge { - return &edge{s} -} - -func TestPgraphT1(t *testing.T) { - +func TestCount1(t *testing.T) { G := &Graph{} if i := G.NumVertices(); i != 0 { @@ -79,8 +47,7 @@ func TestPgraphT1(t *testing.T) { } } -func TestPgraphT2(t *testing.T) { - +func TestAddVertex1(t *testing.T) { G := &Graph{Name: "g2"} v1 := NV("v1") v2 := NV("v2") @@ -106,8 +73,7 @@ func TestPgraphT2(t *testing.T) { } } -func TestPgraphT3(t *testing.T) { - +func TestDFS1(t *testing.T) { G, _ := NewGraph("g3") v1 := NV("v1") v2 := NV("v2") @@ -147,8 +113,7 @@ func TestPgraphT3(t *testing.T) { } } -func TestPgraphT4(t *testing.T) { - +func TestDFS2(t *testing.T) { G, _ := NewGraph("g4") v1 := NV("v1") v2 := NV("v2") @@ -170,7 +135,7 @@ func TestPgraphT4(t *testing.T) { } } -func TestPgraphT5(t *testing.T) { +func TestFilterGraph1(t *testing.T) { G, _ := NewGraph("g5") v1 := NV("v1") v2 := NV("v2") @@ -203,7 +168,7 @@ func TestPgraphT5(t *testing.T) { } } -func TestPgraphT6(t *testing.T) { +func TestDisconnectedGraphs1(t *testing.T) { G, _ := NewGraph("g6") v1 := NV("v1") v2 := NV("v2") @@ -235,8 +200,7 @@ func TestPgraphT6(t *testing.T) { } } -func TestPgraphT7(t *testing.T) { - +func TestDeleteVertex1(t *testing.T) { G, _ := NewGraph("g7") v1 := NV("v1") v2 := NV("v2") @@ -277,8 +241,17 @@ func TestPgraphT7(t *testing.T) { } } -func TestPgraphT8(t *testing.T) { +func TestDeleteVertex2(t *testing.T) { + G := &Graph{} + v1 := NV("v1") + G.DeleteVertex(v1) // check this doesn't panic + if i := G.NumVertices(); i != 0 { + t.Errorf("should have 0 vertices instead of: %d", i) + } +} + +func TestVertexContains1(t *testing.T) { v1 := NV("v1") v2 := NV("v2") v3 := NV("v3") @@ -306,8 +279,7 @@ func TestPgraphT8(t *testing.T) { } } -func TestPgraphT9(t *testing.T) { - +func TestTopoSort1(t *testing.T) { G, _ := NewGraph("g9") v1 := NV("v1") v2 := NV("v2") @@ -382,8 +354,7 @@ func TestPgraphT9(t *testing.T) { } } -func TestPgraphT10(t *testing.T) { - +func TestTopoSort2(t *testing.T) { G, _ := NewGraph("g10") v1 := NV("v1") v2 := NV("v2") @@ -410,7 +381,7 @@ func TestPgraphT10(t *testing.T) { } // empty -func TestPgraphReachability0(t *testing.T) { +func TestReachability0(t *testing.T) { { G, _ := NewGraph("g") result := G.Reachability(nil, nil) @@ -474,7 +445,7 @@ func TestPgraphReachability0(t *testing.T) { } // simple linear path -func TestPgraphReachability1(t *testing.T) { +func TestReachability1(t *testing.T) { G, _ := NewGraph("g") v1 := NV("v1") v2 := NV("v2") @@ -508,7 +479,7 @@ func TestPgraphReachability1(t *testing.T) { } // pick one of two correct paths -func TestPgraphReachability2(t *testing.T) { +func TestReachability2(t *testing.T) { G, _ := NewGraph("g") v1 := NV("v1") v2 := NV("v2") @@ -545,7 +516,7 @@ func TestPgraphReachability2(t *testing.T) { } // pick shortest path -func TestPgraphReachability3(t *testing.T) { +func TestReachability3(t *testing.T) { G, _ := NewGraph("g") v1 := NV("v1") v2 := NV("v2") @@ -580,7 +551,7 @@ func TestPgraphReachability3(t *testing.T) { } // direct path -func TestPgraphReachability4(t *testing.T) { +func TestReachability4(t *testing.T) { G, _ := NewGraph("g") v1 := NV("v1") v2 := NV("v2") @@ -614,7 +585,7 @@ func TestPgraphReachability4(t *testing.T) { } } -func TestPgraphT11(t *testing.T) { +func TestReverse1(t *testing.T) { v1 := NV("v1") v2 := NV("v2") v3 := NV("v3") @@ -639,7 +610,7 @@ func TestPgraphT11(t *testing.T) { } } -func TestPgraphCopy1(t *testing.T) { +func TestCopy1(t *testing.T) { g1 := &Graph{} g2 := g1.Copy() // check this doesn't panic if !reflect.DeepEqual(g1.String(), g2.String()) { @@ -647,31 +618,7 @@ func TestPgraphCopy1(t *testing.T) { } } -func TestPgraphDelete1(t *testing.T) { - G := &Graph{} - v1 := NV("v1") - G.DeleteVertex(v1) // check this doesn't panic - - if i := G.NumVertices(); i != 0 { - t.Errorf("should have 0 vertices instead of: %d", i) - } -} - -func vertexCmpFn(v1, v2 Vertex) (bool, error) { - if v1.String() == "" || v2.String() == "" { - return false, fmt.Errorf("oops, empty vertex") - } - return v1.String() == v2.String(), nil -} - -func edgeCmpFn(e1, e2 Edge) (bool, error) { - if e1.String() == "" || e2.String() == "" { - return false, fmt.Errorf("oops, empty edge") - } - return e1.String() == e2.String(), nil -} - -func TestPgraphGraphCmp1(t *testing.T) { +func TestGraphCmp1(t *testing.T) { g1 := &Graph{} g2 := &Graph{} g3 := &Graph{} @@ -679,20 +626,20 @@ func TestPgraphGraphCmp1(t *testing.T) { g4 := &Graph{} g4.AddVertex(NV("v2")) - if err := g1.GraphCmp(g2, vertexCmpFn, edgeCmpFn); err != nil { + if err := g1.GraphCmp(g2, strVertexCmpFn, strEdgeCmpFn); err != nil { t.Errorf("should have no error during GraphCmp, but got: %v", err) } - if err := g1.GraphCmp(g3, vertexCmpFn, edgeCmpFn); err == nil { + if err := g1.GraphCmp(g3, strVertexCmpFn, strEdgeCmpFn); err == nil { t.Errorf("should have error during GraphCmp, but got nil") } - if err := g3.GraphCmp(g4, vertexCmpFn, edgeCmpFn); err == nil { + if err := g3.GraphCmp(g4, strVertexCmpFn, strEdgeCmpFn); err == nil { t.Errorf("should have error during GraphCmp, but got nil") } } -func TestPgraphSort0(t *testing.T) { +func TestSort0(t *testing.T) { vs := []Vertex{} s := Sort(vs) @@ -710,7 +657,7 @@ func TestPgraphSort0(t *testing.T) { } } -func TestPgraphSort1(t *testing.T) { +func TestSort1(t *testing.T) { v1 := NV("v1") v2 := NV("v2") v3 := NV("v3") diff --git a/pgraph/subgraph_test.go b/pgraph/subgraph_test.go index 04f0465e..4dcf3ad4 100644 --- a/pgraph/subgraph_test.go +++ b/pgraph/subgraph_test.go @@ -18,43 +18,10 @@ package pgraph import ( - "fmt" "testing" ) -// TODO: unify with the other function like this... -// TODO: where should we put our test helpers? -func runGraphCmp(t *testing.T, g1, g2 *Graph) { - err := g1.GraphCmp(g2, vertexCmpFn, edgeCmpFn) - if err != nil { - t.Logf(" actual (g1): %v%v", g1, fullPrint(g1)) - t.Logf("expected (g2): %v%v", g2, fullPrint(g2)) - t.Logf("Cmp error:") - t.Errorf("%v", err) - } -} - -// TODO: unify with the other function like this... -func fullPrint(g *Graph) (str string) { - str += "\n" - for v := range g.Adjacency() { - str += fmt.Sprintf("* v: %s\n", v) - } - for v1 := range g.Adjacency() { - for v2, e := range g.Adjacency()[v1] { - str += fmt.Sprintf("* e: %s -> %s # %s\n", v1, v2, e) - } - } - return -} - -// edgeGenFn generates unique edges for each vertex pair, assuming unique -// vertices. -func edgeGenFn(v1, v2 Vertex) Edge { - return NE(fmt.Sprintf("%s,%s", v1, v2)) -} - -func TestPgraphAddEdgeGraph1(t *testing.T) { +func TestAddEdgeGraph1(t *testing.T) { v1 := NV("v1") v2 := NV("v2") v3 := NV("v3") @@ -82,10 +49,12 @@ func TestPgraphAddEdgeGraph1(t *testing.T) { //expected.AddEdge(v3, v4, NE("v3,v4")) //expected.AddEdge(v3, v5, NE("v3,v5")) - runGraphCmp(t, g, expected) + if s := runGraphCmp(t, g, expected); s != "" { + t.Errorf("%s", s) + } } -func TestPgraphAddEdgeVertexGraph1(t *testing.T) { +func TestAddEdgeVertexGraph1(t *testing.T) { v1 := NV("v1") v2 := NV("v2") v3 := NV("v3") @@ -113,10 +82,12 @@ func TestPgraphAddEdgeVertexGraph1(t *testing.T) { expected.AddEdge(v3, v4, NE("v3,v4")) expected.AddEdge(v3, v5, NE("v3,v5")) - runGraphCmp(t, g, expected) + if s := runGraphCmp(t, g, expected); s != "" { + t.Errorf("%s", s) + } } -func TestPgraphAddEdgeGraphVertex1(t *testing.T) { +func TestAddEdgeGraphVertex1(t *testing.T) { v1 := NV("v1") v2 := NV("v2") v3 := NV("v3") @@ -144,10 +115,12 @@ func TestPgraphAddEdgeGraphVertex1(t *testing.T) { expected.AddEdge(v4, v3, NE("v4,v3")) expected.AddEdge(v5, v3, NE("v5,v3")) - runGraphCmp(t, g, expected) + if s := runGraphCmp(t, g, expected); s != "" { + t.Errorf("%s", s) + } } -func TestPgraphAddEdgeVertexGraphLight1(t *testing.T) { +func TestAddEdgeVertexGraphLight1(t *testing.T) { v1 := NV("v1") v2 := NV("v2") v3 := NV("v3") @@ -175,10 +148,12 @@ func TestPgraphAddEdgeVertexGraphLight1(t *testing.T) { expected.AddEdge(v3, v4, NE("v3,v4")) //expected.AddEdge(v3, v5, NE("v3,v5")) // not needed with light - runGraphCmp(t, g, expected) + if s := runGraphCmp(t, g, expected); s != "" { + t.Errorf("%s", s) + } } -func TestPgraphAddEdgeGraphVertexLight1(t *testing.T) { +func TestAddEdgeGraphVertexLight1(t *testing.T) { v1 := NV("v1") v2 := NV("v2") v3 := NV("v3") @@ -206,5 +181,7 @@ func TestPgraphAddEdgeGraphVertexLight1(t *testing.T) { //expected.AddEdge(v4, v3, NE("v4,v3")) // not needed with light expected.AddEdge(v5, v3, NE("v5,v3")) - runGraphCmp(t, g, expected) + if s := runGraphCmp(t, g, expected); s != "" { + t.Errorf("%s", s) + } } diff --git a/pgraph/util_test.go b/pgraph/util_test.go new file mode 100644 index 00000000..348f049d --- /dev/null +++ b/pgraph/util_test.go @@ -0,0 +1,93 @@ +// Mgmt +// Copyright (C) 2013-2017+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package pgraph + +import ( + "fmt" + "testing" +) + +// vertex is a test struct to test the library. +type vertex struct { + name string +} + +// String is a required method of the Vertex interface that we must fulfill. +func (v *vertex) String() string { + return v.name +} + +// NV is a helper function to make testing easier. It creates a new noop vertex. +func NV(s string) Vertex { + return &vertex{s} +} + +// edge is a test struct to test the library. +type edge struct { + name string +} + +// String is a required method of the Edge interface that we must fulfill. +func (e *edge) String() string { + return e.name +} + +// NE is a helper function to make testing easier. It creates a new noop edge. +func NE(s string) Edge { + return &edge{s} +} + +// edgeGenFn generates unique edges for each vertex pair, assuming unique +// vertices. +func edgeGenFn(v1, v2 Vertex) Edge { + return NE(fmt.Sprintf("%s,%s", v1, v2)) +} + +func vertexAddFn(v Vertex) error { + return nil +} + +func vertexRemoveFn(v Vertex) error { + return nil +} + +func runGraphCmp(t *testing.T, g1, g2 *Graph) string { + err := g1.GraphCmp(g2, strVertexCmpFn, strEdgeCmpFn) + if err != nil { + str := "" + str += fmt.Sprintf(" actual (g1): %v%s", g1, fullPrint(g1)) + str += fmt.Sprintf("expected (g2): %v%s", g2, fullPrint(g2)) + str += fmt.Sprintf("cmp error:") + str += fmt.Sprintf("%v", err) + return str + } + return "" +} + +func fullPrint(g *Graph) (str string) { + str += "\n" + for v := range g.Adjacency() { + str += fmt.Sprintf("* v: %s\n", v) + } + for v1 := range g.Adjacency() { + for v2, e := range g.Adjacency()[v1] { + str += fmt.Sprintf("* e: %s -> %s # %s\n", v1, v2, e) + } + } + return +}