`gg.Graph` has been reworked in its internal functionality, to more closely match the capability of a purely stack-based implementation. The current implementation is _very_ inefficient, however. Tests have been deliberately left pretty sparse, as I expect the internals to continue to change significantly.rust
parent
e4bdeb8797
commit
1e30ad6959
@ -1,665 +1,93 @@ |
||||
package gg |
||||
|
||||
import ( |
||||
"fmt" |
||||
"sort" |
||||
"strings" |
||||
. "testing" |
||||
"strconv" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func edge(val Value, from *Vertex) Edge { |
||||
return Edge{Value: val, From: from} |
||||
} |
||||
|
||||
func value(val Value, in ...Edge) *Vertex { |
||||
return &Vertex{ |
||||
VertexType: ValueVertex, |
||||
Value: val, |
||||
In: in, |
||||
} |
||||
} |
||||
|
||||
func tuple(val Value, in ...Edge) Edge { |
||||
return Edge{ |
||||
From: &Vertex{ |
||||
VertexType: TupleVertex, |
||||
In: in, |
||||
}, |
||||
Value: val, |
||||
} |
||||
} |
||||
|
||||
func assertVertexEqual(t *T, exp, got *Vertex, msgAndArgs ...interface{}) bool { |
||||
var assertInner func(*Vertex, *Vertex, map[*Vertex]bool) bool |
||||
assertInner = func(exp, got *Vertex, m map[*Vertex]bool) bool { |
||||
// if got is already in m then we've already looked at it
|
||||
if m[got] { |
||||
return true |
||||
} |
||||
m[got] = true |
||||
|
||||
assert.Equal(t, exp.VertexType, got.VertexType, msgAndArgs...) |
||||
assert.Equal(t, exp.Value, got.Value, msgAndArgs...) |
||||
if !assert.Len(t, got.In, len(exp.In), msgAndArgs...) { |
||||
return false |
||||
} |
||||
for i := range exp.In { |
||||
assertInner(exp.In[i].From, got.In[i].From, m) |
||||
assert.Equal(t, exp.In[i].Value, got.In[i].Value, msgAndArgs...) |
||||
assert.Equal(t, got, got.In[i].To) |
||||
assert.Contains(t, got.In[i].From.Out, got.In[i]) |
||||
} |
||||
return true |
||||
|
||||
} |
||||
return assertInner(exp, got, map[*Vertex]bool{}) |
||||
} |
||||
|
||||
func assertIter(t *T, expVals, expJuncs int, g *Graph, msgAndArgs ...interface{}) { |
||||
seen := map[*Vertex]bool{} |
||||
var gotVals, gotJuncs int |
||||
g.Iter(func(v *Vertex) bool { |
||||
assert.NotContains(t, seen, v, msgAndArgs...) |
||||
seen[v] = true |
||||
if v.VertexType == ValueVertex { |
||||
gotVals++ |
||||
} else { |
||||
gotJuncs++ |
||||
} |
||||
return true |
||||
}) |
||||
assert.Equal(t, expVals, gotVals, msgAndArgs...) |
||||
assert.Equal(t, expJuncs, gotJuncs, msgAndArgs...) |
||||
} |
||||
|
||||
type graphTest struct { |
||||
name string |
||||
out func() *Graph |
||||
exp []*Vertex |
||||
numVals, numJuncs int |
||||
} |
||||
func TestEqual(t *testing.T) { |
||||
|
||||
func mkTest(name string, out func() *Graph, numVals, numJuncs int, exp ...*Vertex) graphTest { |
||||
return graphTest{ |
||||
name: name, |
||||
out: out, |
||||
exp: exp, |
||||
numVals: numVals, numJuncs: numJuncs, |
||||
i := func(i int64) Value { |
||||
return Value{Number: &i} |
||||
} |
||||
} |
||||
|
||||
func TestGraph(t *T) { |
||||
var ( |
||||
v0 = NewValue("v0") |
||||
v1 = NewValue("v1") |
||||
v2 = NewValue("v2") |
||||
v3 = NewValue("v3") |
||||
e0 = NewValue("e0") |
||||
e00 = NewValue("e00") |
||||
e01 = NewValue("e01") |
||||
e1 = NewValue("e1") |
||||
e10 = NewValue("e10") |
||||
e11 = NewValue("e11") |
||||
e2 = NewValue("e2") |
||||
e20 = NewValue("e20") |
||||
e21 = NewValue("e21") |
||||
ej0 = NewValue("ej0") |
||||
ej1 = NewValue("ej1") |
||||
ej2 = NewValue("ej2") |
||||
) |
||||
tests := []graphTest{ |
||||
mkTest( |
||||
"values-basic", |
||||
func() *Graph { |
||||
return ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) |
||||
}, |
||||
2, 0, |
||||
value(v0), |
||||
value(v1, edge(e0, value(v0))), |
||||
), |
||||
|
||||
mkTest( |
||||
"values-2edges", |
||||
func() *Graph { |
||||
g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v2) |
||||
return g0.AddValueIn(ValueOut(v1, e1), v2) |
||||
}, |
||||
3, 0, |
||||
value(v0), |
||||
value(v1), |
||||
value(v2, |
||||
edge(e0, value(v0)), |
||||
edge(e1, value(v1)), |
||||
), |
||||
), |
||||
|
||||
mkTest( |
||||
"values-separate", |
||||
func() *Graph { |
||||
g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) |
||||
return g0.AddValueIn(ValueOut(v2, e2), v3) |
||||
}, |
||||
4, 0, |
||||
value(v0), |
||||
value(v1, edge(e0, value(v0))), |
||||
value(v2), |
||||
value(v3, edge(e2, value(v2))), |
||||
), |
||||
|
||||
mkTest( |
||||
"values-circular", |
||||
func() *Graph { |
||||
return ZeroGraph.AddValueIn(ValueOut(v0, e0), v0) |
||||
}, |
||||
1, 0, |
||||
value(v0, edge(e0, value(v0))), |
||||
), |
||||
|
||||
mkTest( |
||||
"values-circular2", |
||||
func() *Graph { |
||||
g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) |
||||
return g0.AddValueIn(ValueOut(v1, e1), v0) |
||||
}, |
||||
2, 0, |
||||
value(v0, edge(e1, value(v1, edge(e0, value(v0))))), |
||||
value(v1, edge(e0, value(v0, edge(e1, value(v1))))), |
||||
), |
||||
|
||||
mkTest( |
||||
"values-circular3", |
||||
func() *Graph { |
||||
g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) |
||||
g1 := g0.AddValueIn(ValueOut(v1, e1), v2) |
||||
return g1.AddValueIn(ValueOut(v2, e2), v1) |
||||
}, |
||||
3, 0, |
||||
value(v0), |
||||
value(v1, |
||||
edge(e0, value(v0)), |
||||
edge(e2, value(v2, edge(e1, value(v1)))), |
||||
), |
||||
value(v2, edge(e1, value(v1, |
||||
edge(e0, value(v0)), |
||||
edge(e2, value(v2)), |
||||
))), |
||||
), |
||||
|
||||
mkTest( |
||||
"tuple-basic", |
||||
func() *Graph { |
||||
e0 := ValueOut(v0, e0) |
||||
e1 := ValueOut(v1, e1) |
||||
ej0 := TupleOut([]OpenEdge{e0, e1}, ej0) |
||||
return ZeroGraph.AddValueIn(ej0, v2) |
||||
}, |
||||
3, 1, |
||||
value(v0), value(v1), |
||||
value(v2, tuple(ej0, |
||||
edge(e0, value(v0)), |
||||
edge(e1, value(v1)), |
||||
)), |
||||
), |
||||
|
||||
mkTest( |
||||
"tuple-basic2", |
||||
func() *Graph { |
||||
e00 := ValueOut(v0, e00) |
||||
e10 := ValueOut(v1, e10) |
||||
ej0 := TupleOut([]OpenEdge{e00, e10}, ej0) |
||||
e01 := ValueOut(v0, e01) |
||||
e11 := ValueOut(v1, e11) |
||||
ej1 := TupleOut([]OpenEdge{e01, e11}, ej1) |
||||
ej2 := TupleOut([]OpenEdge{ej0, ej1}, ej2) |
||||
return ZeroGraph.AddValueIn(ej2, v2) |
||||
}, |
||||
3, 3, |
||||
value(v0), value(v1), |
||||
value(v2, tuple(ej2, |
||||
tuple(ej0, |
||||
edge(e00, value(v0)), |
||||
edge(e10, value(v1)), |
||||
), |
||||
tuple(ej1, |
||||
edge(e01, value(v0)), |
||||
edge(e11, value(v1)), |
||||
), |
||||
)), |
||||
), |
||||
|
||||
mkTest( |
||||
"tuple-circular", |
||||
func() *Graph { |
||||
e0 := ValueOut(v0, e0) |
||||
e1 := ValueOut(v1, e1) |
||||
ej0 := TupleOut([]OpenEdge{e0, e1}, ej0) |
||||
g0 := ZeroGraph.AddValueIn(ej0, v2) |
||||
e20 := ValueOut(v2, e20) |
||||
g1 := g0.AddValueIn(e20, v0) |
||||
e21 := ValueOut(v2, e21) |
||||
return g1.AddValueIn(e21, v1) |
||||
}, |
||||
3, 1, |
||||
value(v0, edge(e20, value(v2, tuple(ej0, |
||||
edge(e0, value(v0)), |
||||
edge(e1, value(v1, edge(e21, value(v2)))), |
||||
)))), |
||||
value(v1, edge(e21, value(v2, tuple(ej0, |
||||
edge(e0, value(v0, edge(e20, value(v2)))), |
||||
edge(e1, value(v1)), |
||||
)))), |
||||
value(v2, tuple(ej0, |
||||
edge(e0, value(v0, edge(e20, value(v2)))), |
||||
edge(e1, value(v1, edge(e21, value(v2)))), |
||||
)), |
||||
), |
||||
n := func(n string) Value { |
||||
return Value{Name: &n} |
||||
} |
||||
|
||||
for i := range tests { |
||||
t.Logf("test[%d]:%q", i, tests[i].name) |
||||
out := tests[i].out() |
||||
for j, exp := range tests[i].exp { |
||||
msgAndArgs := []interface{}{ |
||||
"tests[%d].name:%q exp[%d].val:%q", |
||||
i, tests[i].name, j, exp.Value.V.(string), |
||||
} |
||||
v := out.ValueVertex(exp.Value) |
||||
if !assert.NotNil(t, v, msgAndArgs...) { |
||||
continue |
||||
} |
||||
assertVertexEqual(t, exp, v, msgAndArgs...) |
||||
} |
||||
|
||||
msgAndArgs := []interface{}{ |
||||
"tests[%d].name:%q", |
||||
i, tests[i].name, |
||||
} |
||||
|
||||
// sanity check that graphs are equal to themselves
|
||||
assert.True(t, Equal(out, out), msgAndArgs...) |
||||
|
||||
// test the Iter method in here too
|
||||
assertIter(t, tests[i].numVals, tests[i].numJuncs, out, msgAndArgs...) |
||||
} |
||||
} |
||||
|
||||
func TestGraphImmutability(t *T) { |
||||
v0 := NewValue("v0") |
||||
v1 := NewValue("v1") |
||||
e0 := NewValue("e0") |
||||
oe0 := ValueOut(v0, e0) |
||||
g0 := ZeroGraph.AddValueIn(oe0, v1) |
||||
assert.Nil(t, ZeroGraph.ValueVertex(v0)) |
||||
assert.Nil(t, ZeroGraph.ValueVertex(v1)) |
||||
assert.NotNil(t, g0.ValueVertex(v0)) |
||||
assert.NotNil(t, g0.ValueVertex(v1)) |
||||
|
||||
// half-edges should be re-usable
|
||||
v2 := NewValue("v2") |
||||
v3a, v3b := NewValue("v3a"), NewValue("v3b") |
||||
e1 := NewValue("e1") |
||||
oe1 := ValueOut(v2, e1) |
||||
g1a := g0.AddValueIn(oe1, v3a) |
||||
g1b := g0.AddValueIn(oe1, v3b) |
||||
assertVertexEqual(t, value(v3a, edge(e1, value(v2))), g1a.ValueVertex(v3a)) |
||||
assert.Nil(t, g1a.ValueVertex(v3b)) |
||||
assertVertexEqual(t, value(v3b, edge(e1, value(v2))), g1b.ValueVertex(v3b)) |
||||
assert.Nil(t, g1b.ValueVertex(v3a)) |
||||
|
||||
// ... even re-usable twice in succession
|
||||
v3 := NewValue("v3") |
||||
v4 := NewValue("v4") |
||||
g2 := g0.AddValueIn(oe1, v3).AddValueIn(oe1, v4) |
||||
assert.Nil(t, g2.ValueVertex(v3b)) |
||||
assert.Nil(t, g2.ValueVertex(v3a)) |
||||
assertVertexEqual(t, value(v3, edge(e1, value(v2))), g2.ValueVertex(v3)) |
||||
assertVertexEqual(t, value(v4, edge(e1, value(v2))), g2.ValueVertex(v4)) |
||||
} |
||||
|
||||
func TestGraphDelValueIn(t *T) { |
||||
v0 := NewValue("v0") |
||||
v1 := NewValue("v1") |
||||
e0 := NewValue("e0") |
||||
{ // removing from null
|
||||
g := ZeroGraph.DelValueIn(ValueOut(v0, e0), v1) |
||||
assert.True(t, Equal(ZeroGraph, g)) |
||||
} |
||||
|
||||
e1 := NewValue("e1") |
||||
{ // removing edge from vertex which doesn't have that edge
|
||||
g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) |
||||
g1 := g0.DelValueIn(ValueOut(v0, e1), v1) |
||||
assert.True(t, Equal(g0, g1)) |
||||
} |
||||
|
||||
{ // removing only edge
|
||||
oe := ValueOut(v0, e0) |
||||
g0 := ZeroGraph.AddValueIn(oe, v1) |
||||
g1 := g0.DelValueIn(oe, v1) |
||||
assert.True(t, Equal(ZeroGraph, g1)) |
||||
} |
||||
|
||||
ej0 := NewValue("ej0") |
||||
v2 := NewValue("v2") |
||||
{ // removing only edge (tuple)
|
||||
oe := TupleOut([]OpenEdge{ |
||||
ValueOut(v0, e0), |
||||
ValueOut(v1, e1), |
||||
}, ej0) |
||||
g0 := ZeroGraph.AddValueIn(oe, v2) |
||||
g1 := g0.DelValueIn(oe, v2) |
||||
assert.True(t, Equal(ZeroGraph, g1)) |
||||
} |
||||
|
||||
{ // removing one of two edges
|
||||
oe := ValueOut(v1, e0) |
||||
g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v2) |
||||
g1 := g0.AddValueIn(oe, v2) |
||||
g2 := g1.DelValueIn(oe, v2) |
||||
assert.True(t, Equal(g0, g2)) |
||||
assert.NotNil(t, g2.ValueVertex(v0)) |
||||
assert.Nil(t, g2.ValueVertex(v1)) |
||||
assert.NotNil(t, g2.ValueVertex(v2)) |
||||
} |
||||
|
||||
e2 := NewValue("e2") |
||||
eja, ejb := NewValue("eja"), NewValue("ejb") |
||||
v3 := NewValue("v3") |
||||
{ // removing one of two edges (tuple)
|
||||
e0 := ValueOut(v0, e0) |
||||
e1 := ValueOut(v1, e1) |
||||
e2 := ValueOut(v2, e2) |
||||
oeA := TupleOut([]OpenEdge{e0, e1}, eja) |
||||
oeB := TupleOut([]OpenEdge{e1, e2}, ejb) |
||||
g0a := ZeroGraph.AddValueIn(oeA, v3) |
||||
g0b := ZeroGraph.AddValueIn(oeB, v3) |
||||
g1 := g0a.Union(g0b).DelValueIn(oeA, v3) |
||||
assert.True(t, Equal(g1, g0b)) |
||||
assert.Nil(t, g1.ValueVertex(v0)) |
||||
assert.NotNil(t, g1.ValueVertex(v1)) |
||||
assert.NotNil(t, g1.ValueVertex(v2)) |
||||
assert.NotNil(t, g1.ValueVertex(v3)) |
||||
} |
||||
|
||||
{ // removing one of two edges in circular graph
|
||||
e0 := ValueOut(v0, e0) |
||||
e1 := ValueOut(v1, e1) |
||||
g0 := ZeroGraph.AddValueIn(e0, v1).AddValueIn(e1, v0) |
||||
g1 := g0.DelValueIn(e0, v1) |
||||
assert.True(t, Equal(ZeroGraph.AddValueIn(e1, v0), g1)) |
||||
assert.NotNil(t, g1.ValueVertex(v0)) |
||||
assert.NotNil(t, g1.ValueVertex(v1)) |
||||
} |
||||
|
||||
ej := NewValue("ej") |
||||
{ // removing to's only edge, sub-nodes have edge to each other
|
||||
oej := TupleOut([]OpenEdge{ |
||||
ValueOut(v0, ej0), |
||||
ValueOut(v1, ej0), |
||||
}, ej) |
||||
g0 := ZeroGraph.AddValueIn(oej, v2) |
||||
e0 := ValueOut(v0, e0) |
||||
g1 := g0.AddValueIn(e0, v1) |
||||
g2 := g1.DelValueIn(oej, v2) |
||||
assert.True(t, Equal(ZeroGraph.AddValueIn(e0, v1), g2)) |
||||
assert.NotNil(t, g2.ValueVertex(v0)) |
||||
assert.NotNil(t, g2.ValueVertex(v1)) |
||||
assert.Nil(t, g2.ValueVertex(v2)) |
||||
} |
||||
} |
||||
|
||||
// deterministically hashes a Graph.
|
||||
func graphStr(g *Graph) string { |
||||
var vStr func(vertex) string |
||||
var oeStr func(OpenEdge) string |
||||
vStr = func(v vertex) string { |
||||
if v.VertexType == ValueVertex { |
||||
return fmt.Sprintf("v:%q\n", v.val.V.(string)) |
||||
} |
||||
s := fmt.Sprintf("j:%d\n", len(v.in)) |
||||
ssOE := make([]string, len(v.in)) |
||||
for i := range v.in { |
||||
ssOE[i] = oeStr(v.in[i]) |
||||
} |
||||
sort.Strings(ssOE) |
||||
return s + strings.Join(ssOE, "") |
||||
} |
||||
oeStr = func(oe OpenEdge) string { |
||||
s := fmt.Sprintf("oe:%q\n", oe.val.V.(string)) |
||||
return s + vStr(oe.fromV) |
||||
} |
||||
sVV := make([]string, 0, len(g.vM)) |
||||
for _, v := range g.vM { |
||||
sVV = append(sVV, vStr(v)) |
||||
} |
||||
sort.Strings(sVV) |
||||
return strings.Join(sVV, "") |
||||
} |
||||
|
||||
func assertEqualSets(t *T, exp, got []*Graph) bool { |
||||
if !assert.Equal(t, len(exp), len(got)) { |
||||
return false |
||||
} |
||||
|
||||
m := map[*Graph]string{} |
||||
for _, g := range exp { |
||||
m[g] = graphStr(g) |
||||
} |
||||
for _, g := range got { |
||||
m[g] = graphStr(g) |
||||
} |
||||
|
||||
sort.Slice(exp, func(i, j int) bool { |
||||
return m[exp[i]] < m[exp[j]] |
||||
}) |
||||
sort.Slice(got, func(i, j int) bool { |
||||
return m[got[i]] < m[got[j]] |
||||
}) |
||||
|
||||
b := true |
||||
for i := range exp { |
||||
b = b || assert.True(t, Equal(exp[i], got[i]), "i:%d exp:%q got:%q", i, m[exp[i]], m[got[i]]) |
||||
} |
||||
return b |
||||
} |
||||
|
||||
func TestGraphUnion(t *T) { |
||||
assertUnion := func(g1, g2 *Graph) *Graph { |
||||
ga := g1.Union(g2) |
||||
gb := g2.Union(g1) |
||||
assert.True(t, Equal(ga, gb)) |
||||
return ga |
||||
} |
||||
|
||||
assertDisjoin := func(g *Graph, exp ...*Graph) { |
||||
ggDisj := g.Disjoin() |
||||
assertEqualSets(t, exp, ggDisj) |
||||
} |
||||
|
||||
v0 := NewValue("v0") |
||||
v1 := NewValue("v1") |
||||
e0 := NewValue("e0") |
||||
{ // Union with ZeroGraph
|
||||
assert.True(t, Equal(ZeroGraph, ZeroGraph.Union(ZeroGraph))) |
||||
|
||||
g := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) |
||||
assert.True(t, Equal(g, assertUnion(g, ZeroGraph))) |
||||
|
||||
assertDisjoin(g, g) |
||||
} |
||||
|
||||
v2 := NewValue("v2") |
||||
v3 := NewValue("v3") |
||||
e1 := NewValue("e1") |
||||
{ // Two disparate graphs union'd
|
||||
g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) |
||||
g1 := ZeroGraph.AddValueIn(ValueOut(v2, e1), v3) |
||||
g := assertUnion(g0, g1) |
||||
assertVertexEqual(t, value(v0), g.ValueVertex(v0)) |
||||
assertVertexEqual(t, value(v1, edge(e0, value(v0))), g.ValueVertex(v1)) |
||||
assertVertexEqual(t, value(v2), g.ValueVertex(v2)) |
||||
assertVertexEqual(t, value(v3, edge(e1, value(v2))), g.ValueVertex(v3)) |
||||
|
||||
assertDisjoin(g, g0, g1) |
||||
} |
||||
|
||||
va0, vb0 := NewValue("va0"), NewValue("vb0") |
||||
va1, vb1 := NewValue("va1"), NewValue("vb1") |
||||
va2, vb2 := NewValue("va2"), NewValue("vb2") |
||||
ea0, eb0 := NewValue("ea0"), NewValue("eb0") |
||||
ea1, eb1 := NewValue("ea1"), NewValue("eb1") |
||||
eaj, ebj := NewValue("eaj"), NewValue("ebj") |
||||
{ // Two disparate graphs with tuples
|
||||
ga := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(va0, ea0), |
||||
ValueOut(va1, ea1), |
||||
}, eaj), va2) |
||||
gb := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(vb0, eb0), |
||||
ValueOut(vb1, eb1), |
||||
}, ebj), vb2) |
||||
g := assertUnion(ga, gb) |
||||
assertVertexEqual(t, value(va0), g.ValueVertex(va0)) |
||||
assertVertexEqual(t, value(va1), g.ValueVertex(va1)) |
||||
assertVertexEqual(t, |
||||
value(va2, tuple(eaj, |
||||
edge(ea0, value(va0)), |
||||
edge(ea1, value(va1)))), |
||||
g.ValueVertex(va2), |
||||
) |
||||
assertVertexEqual(t, value(vb0), g.ValueVertex(vb0)) |
||||
assertVertexEqual(t, value(vb1), g.ValueVertex(vb1)) |
||||
assertVertexEqual(t, |
||||
value(vb2, tuple(ebj, |
||||
edge(eb0, value(vb0)), |
||||
edge(eb1, value(vb1)))), |
||||
g.ValueVertex(vb2), |
||||
) |
||||
|
||||
assertDisjoin(g, ga, gb) |
||||
} |
||||
|
||||
{ // Two partially overlapping graphs
|
||||
g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v2) |
||||
g1 := ZeroGraph.AddValueIn(ValueOut(v1, e1), v2) |
||||
g := assertUnion(g0, g1) |
||||
assertVertexEqual(t, value(v0), g.ValueVertex(v0)) |
||||
assertVertexEqual(t, value(v1), g.ValueVertex(v1)) |
||||
assertVertexEqual(t, |
||||
value(v2, |
||||
edge(e0, value(v0)), |
||||
edge(e1, value(v1)), |
||||
), |
||||
g.ValueVertex(v2), |
||||
) |
||||
|
||||
assertDisjoin(g, g) |
||||
} |
||||
|
||||
ej0 := NewValue("ej0") |
||||
ej1 := NewValue("ej1") |
||||
{ // two partially overlapping graphs with tuples
|
||||
g0 := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(v0, e0), |
||||
ValueOut(v1, e1), |
||||
}, ej0), v2) |
||||
g1 := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(v0, e0), |
||||
ValueOut(v1, e1), |
||||
}, ej1), v2) |
||||
g := assertUnion(g0, g1) |
||||
assertVertexEqual(t, value(v0), g.ValueVertex(v0)) |
||||
assertVertexEqual(t, value(v1), g.ValueVertex(v1)) |
||||
assertVertexEqual(t, |
||||
value(v2, |
||||
tuple(ej0, edge(e0, value(v0)), edge(e1, value(v1))), |
||||
tuple(ej1, edge(e0, value(v0)), edge(e1, value(v1))), |
||||
), |
||||
g.ValueVertex(v2), |
||||
) |
||||
|
||||
assertDisjoin(g, g) |
||||
} |
||||
|
||||
{ // Two equal graphs
|
||||
g0 := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) |
||||
g := assertUnion(g0, g0) |
||||
assertVertexEqual(t, value(v0), g.ValueVertex(v0)) |
||||
assertVertexEqual(t, |
||||
value(v1, edge(e0, value(v0))), |
||||
g.ValueVertex(v1), |
||||
) |
||||
} |
||||
|
||||
{ // Two equal graphs with tuples
|
||||
g0 := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(v0, e0), |
||||
ValueOut(v1, e1), |
||||
}, ej0), v2) |
||||
g := assertUnion(g0, g0) |
||||
assertVertexEqual(t, value(v0), g.ValueVertex(v0)) |
||||
assertVertexEqual(t, value(v1), g.ValueVertex(v1)) |
||||
assertVertexEqual(t, |
||||
value(v2, |
||||
tuple(ej0, edge(e0, value(v0)), edge(e1, value(v1))), |
||||
), |
||||
g.ValueVertex(v2), |
||||
) |
||||
} |
||||
} |
||||
|
||||
func TestGraphEqual(t *T) { |
||||
assertEqual := func(g1, g2 *Graph) { |
||||
assert.True(t, Equal(g1, g2)) |
||||
assert.True(t, Equal(g2, g1)) |
||||
} |
||||
|
||||
assertNotEqual := func(g1, g2 *Graph) { |
||||
assert.False(t, Equal(g1, g2)) |
||||
assert.False(t, Equal(g2, g1)) |
||||
} |
||||
|
||||
assertEqual(ZeroGraph, ZeroGraph) // duh
|
||||
|
||||
v0 := NewValue("v0") |
||||
v1 := NewValue("v1") |
||||
v2 := NewValue("v2") |
||||
e0 := NewValue("e0") |
||||
e1 := NewValue("e1") |
||||
e1a, e1b := NewValue("e1a"), NewValue("e1b") |
||||
{ |
||||
// graph is equal to itself, not to null
|
||||
e0 := ValueOut(v0, e0) |
||||
g0 := ZeroGraph.AddValueIn(e0, v1) |
||||
assertNotEqual(g0, ZeroGraph) |
||||
assertEqual(g0, g0) |
||||
|
||||
// adding the an existing edge again shouldn't do anything
|
||||
assertEqual(g0, g0.AddValueIn(e0, v1)) |
||||
|
||||
// g1a and g1b have the same vertices, but the edges are different
|
||||
g1a := g0.AddValueIn(ValueOut(v0, e1a), v2) |
||||
g1b := g0.AddValueIn(ValueOut(v0, e1b), v2) |
||||
assertNotEqual(g1a, g1b) |
||||
} |
||||
|
||||
{ // equal construction should yield equality, even if out of order
|
||||
ga := ZeroGraph.AddValueIn(ValueOut(v0, e0), v1) |
||||
ga = ga.AddValueIn(ValueOut(v1, e1), v2) |
||||
gb := ZeroGraph.AddValueIn(ValueOut(v1, e1), v2) |
||||
gb = gb.AddValueIn(ValueOut(v0, e0), v1) |
||||
assertEqual(ga, gb) |
||||
tests := []struct { |
||||
a, b *Graph |
||||
exp bool |
||||
}{ |
||||
{ |
||||
a: ZeroGraph, |
||||
b: ZeroGraph, |
||||
exp: true, |
||||
}, |
||||
{ |
||||
a: ZeroGraph, |
||||
b: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
a: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
b: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
exp: true, |
||||
}, |
||||
{ |
||||
a: ZeroGraph.AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
b: ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(n("in"), n("ident")), |
||||
ValueOut(i(1), n("ident")), |
||||
}, n("add")), n("out")), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
// tuples are different order
|
||||
a: ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(i(1), n("ident")), |
||||
ValueOut(n("in"), n("ident")), |
||||
}, n("add")), n("out")), |
||||
b: ZeroGraph.AddValueIn(TupleOut([]OpenEdge{ |
||||
ValueOut(n("in"), n("ident")), |
||||
ValueOut(i(1), n("ident")), |
||||
}, n("add")), n("out")), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
a: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")). |
||||
AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), |
||||
b: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
exp: false, |
||||
}, |
||||
{ |
||||
a: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")). |
||||
AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), |
||||
b: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")). |
||||
AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), |
||||
exp: true, |
||||
}, |
||||
{ |
||||
// order of value ins shouldn't matter
|
||||
a: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")). |
||||
AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")), |
||||
b: ZeroGraph. |
||||
AddValueIn(ValueOut(n("in2"), n("incr2")), n("out2")). |
||||
AddValueIn(ValueOut(n("in"), n("incr")), n("out")), |
||||
exp: true, |
||||
}, |
||||
} |
||||
|
||||
ej := NewValue("ej") |
||||
{ // tuple basic test
|
||||
e0 := ValueOut(v0, e0) |
||||
e1 := ValueOut(v1, e1) |
||||
ga := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{e0, e1}, ej), v2) |
||||
gb := ZeroGraph.AddValueIn(TupleOut([]OpenEdge{e1, e0}, ej), v2) |
||||
assertEqual(ga, ga) |
||||
assertNotEqual(ga, gb) |
||||
for i, test := range tests { |
||||
t.Run(strconv.Itoa(i), func(t *testing.T) { |
||||
assert.Equal(t, test.exp, Equal(test.a, test.b)) |
||||
}) |
||||
} |
||||
} |
||||
|
@ -1,186 +0,0 @@ |
||||
package gg |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
) |
||||
|
||||
type openEdgeJSON struct { |
||||
From vertexJSON `json:"from"` |
||||
ValueID string `json:"valueID"` |
||||
} |
||||
|
||||
type vertexJSON struct { |
||||
Type VertexType `json:"type"` |
||||
ValueID string `json:"valueID,omitempty"` |
||||
In []openEdgeJSON `json:"in"` |
||||
} |
||||
|
||||
type graphJSON struct { |
||||
Values map[string]json.RawMessage `json:"values"` |
||||
ValueVertices []vertexJSON `json:"valueVertices"` |
||||
} |
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for a Graph. All Values
|
||||
// in the Graph will have json.Marshal called on them as-is in order to marshal
|
||||
// them.
|
||||
func (g *Graph) MarshalJSON() ([]byte, error) { |
||||
gJ := graphJSON{ |
||||
Values: map[string]json.RawMessage{}, |
||||
ValueVertices: make([]vertexJSON, 0, len(g.vM)), |
||||
} |
||||
|
||||
withVal := func(val Value) (string, error) { |
||||
if _, ok := gJ.Values[val.ID]; !ok { |
||||
valJ, err := json.Marshal(val.V) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
gJ.Values[val.ID] = json.RawMessage(valJ) |
||||
} |
||||
return val.ID, nil |
||||
} |
||||
|
||||
// two locally defined, mutually recursive functions. This kind of thing
|
||||
// could probably be abstracted out, I feel like it happens frequently with
|
||||
// graph code.
|
||||
var mkIns func([]OpenEdge) ([]openEdgeJSON, error) |
||||
var mkVert func(vertex) (vertexJSON, error) |
||||
|
||||
mkIns = func(in []OpenEdge) ([]openEdgeJSON, error) { |
||||
inJ := make([]openEdgeJSON, len(in)) |
||||
for i := range in { |
||||
valID, err := withVal(in[i].val) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
vJ, err := mkVert(in[i].fromV) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
inJ[i] = openEdgeJSON{From: vJ, ValueID: valID} |
||||
} |
||||
return inJ, nil |
||||
} |
||||
|
||||
mkVert = func(v vertex) (vertexJSON, error) { |
||||
ins, err := mkIns(v.in) |
||||
if err != nil { |
||||
return vertexJSON{}, err |
||||
} |
||||
vJ := vertexJSON{ |
||||
Type: v.VertexType, |
||||
In: ins, |
||||
} |
||||
if v.VertexType == ValueVertex { |
||||
valID, err := withVal(v.val) |
||||
if err != nil { |
||||
return vJ, err |
||||
} |
||||
vJ.ValueID = valID |
||||
} |
||||
return vJ, nil |
||||
} |
||||
|
||||
for _, v := range g.vM { |
||||
vJ, err := mkVert(v) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
gJ.ValueVertices = append(gJ.ValueVertices, vJ) |
||||
} |
||||
|
||||
return json.Marshal(gJ) |
||||
} |
||||
|
||||
type jsonUnmarshaler struct { |
||||
g *Graph |
||||
fn func(json.RawMessage) (interface{}, error) |
||||
} |
||||
|
||||
// JSONUnmarshaler returns a json.Unmarshaler instance which, when used, will
|
||||
// unmarshal a json string into the Graph instance being called on here.
|
||||
//
|
||||
// The passed in function is used to unmarshal Values (used in both ValueVertex
|
||||
// vertices and edges) from json strings into go values. The returned inteface{}
|
||||
// should have already had the unmarshal from the given json string performed on
|
||||
// it.
|
||||
//
|
||||
// The json.Unmarshaler returned can be used many times, but will reset the
|
||||
// Graph completely before each use.
|
||||
func (g *Graph) JSONUnmarshaler(fn func(json.RawMessage) (interface{}, error)) json.Unmarshaler { |
||||
return jsonUnmarshaler{g: g, fn: fn} |
||||
} |
||||
|
||||
func (jm jsonUnmarshaler) UnmarshalJSON(b []byte) error { |
||||
*(jm.g) = Graph{} |
||||
jm.g.vM = map[string]vertex{} |
||||
|
||||
var gJ graphJSON |
||||
if err := json.Unmarshal(b, &gJ); err != nil { |
||||
return err |
||||
} |
||||
|
||||
vals := map[string]Value{} |
||||
getVal := func(valID string) (Value, error) { |
||||
if val, ok := vals[valID]; ok { |
||||
return val, nil |
||||
} |
||||
|
||||
j, ok := gJ.Values[valID] |
||||
if !ok { |
||||
return Value{}, fmt.Errorf("unmarshaling malformed graph, value with ID %q not defined", valID) |
||||
} |
||||
|
||||
V, err := jm.fn(j) |
||||
if err != nil { |
||||
return Value{}, err |
||||
} |
||||
|
||||
val := Value{ID: valID, V: V} |
||||
vals[valID] = val |
||||
return val, nil |
||||
} |
||||
|
||||
var mkIns func([]openEdgeJSON) ([]OpenEdge, error) |
||||
var mkVert func(vertexJSON) (vertex, error) |
||||
|
||||
mkIns = func(inJ []openEdgeJSON) ([]OpenEdge, error) { |
||||
in := make([]OpenEdge, len(inJ)) |
||||
for i := range inJ { |
||||
val, err := getVal(inJ[i].ValueID) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
v, err := mkVert(inJ[i].From) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
in[i] = OpenEdge{fromV: v, val: val} |
||||
} |
||||
return in, nil |
||||
} |
||||
|
||||
mkVert = func(vJ vertexJSON) (vertex, error) { |
||||
ins, err := mkIns(vJ.In) |
||||
if err != nil { |
||||
return vertex{}, err |
||||
} |
||||
var val Value |
||||
if vJ.Type == ValueVertex { |
||||
if val, err = getVal(vJ.ValueID); err != nil { |
||||
return vertex{}, err |
||||
} |
||||
} |
||||
return mkVertex(vJ.Type, val, ins...), nil |
||||
} |
||||
|
||||
for _, v := range gJ.ValueVertices { |
||||
v, err := mkVert(v) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
jm.g.vM[v.id] = v |
||||
} |
||||
return nil |
||||
} |
Loading…
Reference in new issue