diff --git a/graph/iterator/linksto_iterator_test.go b/graph/iterator/linksto_iterator_test.go index 8850871..5a1f55e 100644 --- a/graph/iterator/linksto_iterator_test.go +++ b/graph/iterator/linksto_iterator_test.go @@ -21,13 +21,17 @@ import ( ) func TestLinksTo(t *testing.T) { - ts := new(TestTripleStore) - tsFixed := newFixed() - tsFixed.Add(2) - ts.On("ValueOf", "cool").Return(1) - ts.On("TripleIterator", graph.Object, 1).Return(tsFixed) + ts := &store{ + data: []string{1: "cool"}, + iter: newFixed(), + } + ts.iter.(*Fixed).Add(2) fixed := newFixed() - fixed.Add(ts.ValueOf("cool")) + val := ts.ValueOf("cool") + if val != 1 { + t.Fatalf("Failed to return correct value, got:%v expect:1", val) + } + fixed.Add(val) lto := NewLinksTo(ts, fixed, graph.Object) val, ok := lto.Next() if !ok { diff --git a/graph/iterator/mock_ts_test.go b/graph/iterator/mock_ts_test.go index 7ab3090..b815487 100644 --- a/graph/iterator/mock_ts_test.go +++ b/graph/iterator/mock_ts_test.go @@ -17,44 +17,58 @@ package iterator // A quickly mocked version of the TripleStore interface, for use in tests. // Can better used Mock.Called but will fill in as needed. -import ( - "github.com/stretchrcom/testify/mock" +import "github.com/google/cayley/graph" - "github.com/google/cayley/graph" -) - -type TestTripleStore struct { - mock.Mock +type store struct { + data []string + iter graph.Iterator } -func (ts *TestTripleStore) ValueOf(s string) graph.Value { - args := ts.Mock.Called(s) - return args.Get(0) +func (ts *store) ValueOf(s string) graph.Value { + for i, v := range ts.data { + if s == v { + return i + } + } + return nil } -func (ts *TestTripleStore) AddTriple(*graph.Triple) {} -func (ts *TestTripleStore) AddTripleSet([]*graph.Triple) {} -func (ts *TestTripleStore) Triple(graph.Value) *graph.Triple { return &graph.Triple{} } -func (ts *TestTripleStore) TripleIterator(d graph.Direction, i graph.Value) graph.Iterator { - args := ts.Mock.Called(d, i) - return args.Get(0).(graph.Iterator) + +func (ts *store) AddTriple(*graph.Triple) {} + +func (ts *store) AddTripleSet([]*graph.Triple) {} + +func (ts *store) Triple(graph.Value) *graph.Triple { return &graph.Triple{} } + +func (ts *store) TripleIterator(d graph.Direction, i graph.Value) graph.Iterator { + return ts.iter } -func (ts *TestTripleStore) NodesAllIterator() graph.Iterator { return &Null{} } -func (ts *TestTripleStore) TriplesAllIterator() graph.Iterator { return &Null{} } -func (ts *TestTripleStore) GetIteratorByString(string, string, string) graph.Iterator { - return &Null{} + +func (ts *store) NodesAllIterator() graph.Iterator { return &Null{} } + +func (ts *store) TriplesAllIterator() graph.Iterator { return &Null{} } + +func (ts *store) NameOf(v graph.Value) string { + i := v.(int) + if i < 0 || i >= len(ts.data) { + return "" + } + return ts.data[i] } -func (ts *TestTripleStore) NameOf(v graph.Value) string { - args := ts.Mock.Called(v) - return args.Get(0).(string) -} -func (ts *TestTripleStore) Size() int64 { return 0 } -func (ts *TestTripleStore) DebugPrint() {} -func (ts *TestTripleStore) OptimizeIterator(it graph.Iterator) (graph.Iterator, bool) { + +func (ts *store) Size() int64 { return 0 } + +func (ts *store) DebugPrint() {} + +func (ts *store) OptimizeIterator(it graph.Iterator) (graph.Iterator, bool) { return &Null{}, false } -func (ts *TestTripleStore) FixedIterator() graph.FixedIterator { + +func (ts *store) FixedIterator() graph.FixedIterator { return NewFixedIteratorWithCompare(BasicEquality) } -func (ts *TestTripleStore) Close() {} -func (ts *TestTripleStore) TripleDirection(graph.Value, graph.Direction) graph.Value { return 0 } -func (ts *TestTripleStore) RemoveTriple(t *graph.Triple) {} + +func (ts *store) Close() {} + +func (ts *store) TripleDirection(graph.Value, graph.Direction) graph.Value { return 0 } + +func (ts *store) RemoveTriple(t *graph.Triple) {} diff --git a/graph/iterator/or_iterator_test.go b/graph/iterator/or_iterator_test.go index 3542149..3b52ccd 100644 --- a/graph/iterator/or_iterator_test.go +++ b/graph/iterator/or_iterator_test.go @@ -15,131 +15,140 @@ package iterator import ( + "reflect" "testing" - . "github.com/smartystreets/goconvey/convey" - "github.com/google/cayley/graph" ) -func extractNumbersFromIterator(it graph.Iterator) []int { - var outputNumbers []int +func iterated(it graph.Iterator) []int { + var res []int for { val, ok := it.Next() if !ok { break } - outputNumbers = append(outputNumbers, val.(int)) + res = append(res, val.(int)) } - return outputNumbers + return res } func TestOrIteratorBasics(t *testing.T) { - var orIt *Or + or := NewOr() + f1 := newFixed() + f1.Add(1) + f1.Add(2) + f1.Add(3) + f2 := newFixed() + f2.Add(3) + f2.Add(9) + f2.Add(20) + f2.Add(21) + or.AddSubIterator(f1) + or.AddSubIterator(f2) - Convey("Given an Or Iterator of two fixed iterators", t, func() { - orIt = NewOr() - fixed1 := newFixed() - fixed1.Add(1) - fixed1.Add(2) - fixed1.Add(3) - fixed2 := newFixed() - fixed2.Add(3) - fixed2.Add(9) - fixed2.Add(20) - fixed2.Add(21) - orIt.AddSubIterator(fixed1) - orIt.AddSubIterator(fixed2) + if v, _ := or.Size(); v != 7 { + t.Errorf("Unexpected iterator size, got:%d expected %d", v, 7) + } - Convey("It should guess its size.", func() { - v, _ := orIt.Size() - So(v, ShouldEqual, 7) - }) + expect := []int{1, 2, 3, 3, 9, 20, 21} + for i := 0; i < 2; i++ { + if got := iterated(or); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to iterate Or correctly on repeat %d, got:%v expect:%v", i, got, expect) + } + or.Reset() + } - Convey("It should extract all the numbers, potentially twice.", func() { - allNumbers := []int{1, 2, 3, 3, 9, 20, 21} - So(extractNumbersFromIterator(orIt), ShouldResemble, allNumbers) - orIt.Reset() - So(extractNumbersFromIterator(orIt), ShouldResemble, allNumbers) - // Optimization works - newOr, _ := orIt.Optimize() - So(extractNumbersFromIterator(newOr), ShouldResemble, allNumbers) - }) + // Check that optimization works. + optOr, _ := or.Optimize() + if got := iterated(optOr); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to iterate optimized Or correctly, got:%v expect:%v", got, expect) + } - Convey("It should check that numbers in either iterator exist.", func() { - So(orIt.Check(2), ShouldEqual, true) - So(orIt.Check(3), ShouldEqual, true) - So(orIt.Check(21), ShouldEqual, true) - }) - - Convey("It should check that numbers not in either iterator are false.", func() { - So(orIt.Check(22), ShouldEqual, false) - So(orIt.Check(5), ShouldEqual, false) - So(orIt.Check(0), ShouldEqual, false) - }) - - }) + for _, v := range []int{2, 3, 21} { + if !or.Check(v) { + t.Errorf("Failed to correctly check %d as true", v) + } + } + for _, v := range []int{22, 5, 0} { + if or.Check(v) { + t.Errorf("Failed to correctly check %d as false", v) + } + } } func TestShortCircuitingOrBasics(t *testing.T) { - var orIt *Or + var or *Or - Convey("Given a short-circuiting Or of two fixed iterators", t, func() { - orIt = NewShortCircuitOr() - fixed1 := newFixed() - fixed1.Add(1) - fixed1.Add(2) - fixed1.Add(3) - fixed2 := newFixed() - fixed2.Add(3) - fixed2.Add(9) - fixed2.Add(20) - fixed2.Add(21) + f1 := newFixed() + f1.Add(1) + f1.Add(2) + f1.Add(3) + f2 := newFixed() + f2.Add(3) + f2.Add(9) + f2.Add(20) + f2.Add(21) - Convey("It should guess its size.", func() { - orIt.AddSubIterator(fixed1) - orIt.AddSubIterator(fixed2) - v, _ := orIt.Size() - So(v, ShouldEqual, 4) - }) + or = NewShortCircuitOr() + or.AddSubIterator(f1) + or.AddSubIterator(f2) + v, exact := or.Size() + if v != 4 { + t.Errorf("Unexpected iterator size, got:%d expected %d", v, 4) + } + if !exact { + t.Error("Size not exact.") + } - Convey("It should extract the first iterators' numbers.", func() { - orIt.AddSubIterator(fixed1) - orIt.AddSubIterator(fixed2) - allNumbers := []int{1, 2, 3} - So(extractNumbersFromIterator(orIt), ShouldResemble, allNumbers) - orIt.Reset() - So(extractNumbersFromIterator(orIt), ShouldResemble, allNumbers) - // Optimization works - newOr, _ := orIt.Optimize() - So(extractNumbersFromIterator(newOr), ShouldResemble, allNumbers) - }) + // It should extract the first iterators' numbers. + or = NewShortCircuitOr() + or.AddSubIterator(f1) + or.AddSubIterator(f2) + expect := []int{1, 2, 3} + for i := 0; i < 2; i++ { + if got := iterated(or); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to iterate Or correctly on repeat %d, got:%v expect:%v", i, got, expect) + } + or.Reset() + } - Convey("It should check that numbers in either iterator exist.", func() { - orIt.AddSubIterator(fixed1) - orIt.AddSubIterator(fixed2) - So(orIt.Check(2), ShouldEqual, true) - So(orIt.Check(3), ShouldEqual, true) - So(orIt.Check(21), ShouldEqual, true) - So(orIt.Check(22), ShouldEqual, false) - So(orIt.Check(5), ShouldEqual, false) - So(orIt.Check(0), ShouldEqual, false) + // Check optimization works. + optOr, _ := or.Optimize() + if got := iterated(optOr); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to iterate optimized Or correctly, got:%v expect:%v", got, expect) + } - }) - - Convey("It should check that it pulls the second iterator's numbers if the first is empty.", func() { - orIt.AddSubIterator(newFixed()) - orIt.AddSubIterator(fixed2) - allNumbers := []int{3, 9, 20, 21} - So(extractNumbersFromIterator(orIt), ShouldResemble, allNumbers) - orIt.Reset() - So(extractNumbersFromIterator(orIt), ShouldResemble, allNumbers) - // Optimization works - newOr, _ := orIt.Optimize() - So(extractNumbersFromIterator(newOr), ShouldResemble, allNumbers) - }) - - }) + // Check that numbers in either iterator exist. + or = NewShortCircuitOr() + or.AddSubIterator(f1) + or.AddSubIterator(f2) + for _, v := range []int{2, 3, 21} { + if !or.Check(v) { + t.Errorf("Failed to correctly check %d as true", v) + } + } + for _, v := range []int{22, 5, 0} { + if or.Check(v) { + t.Errorf("Failed to correctly check %d as false", v) + } + } + // Check that it pulls the second iterator's numbers if the first is empty. + or = NewShortCircuitOr() + or.AddSubIterator(newFixed()) + or.AddSubIterator(f2) + expect = []int{3, 9, 20, 21} + for i := 0; i < 2; i++ { + if got := iterated(or); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to iterate Or correctly on repeat %d, got:%v expect:%v", i, got, expect) + } + or.Reset() + } + // Check optimization works. + optOr, _ = or.Optimize() + if got := iterated(optOr); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to iterate optimized Or correctly, got:%v expect:%v", got, expect) + } } diff --git a/graph/iterator/query_shape.go b/graph/iterator/query_shape.go index da40db9..3e7eba1 100644 --- a/graph/iterator/query_shape.go +++ b/graph/iterator/query_shape.go @@ -42,7 +42,7 @@ type queryShape struct { hasaDirs []graph.Direction } -func OutputQueryShapeForIterator(it graph.Iterator, ts graph.TripleStore, outputMap *map[string]interface{}) { +func OutputQueryShapeForIterator(it graph.Iterator, ts graph.TripleStore, outputMap map[string]interface{}) { qs := &queryShape{ ts: ts, nodeId: 1, @@ -50,8 +50,8 @@ func OutputQueryShapeForIterator(it graph.Iterator, ts graph.TripleStore, output node := qs.MakeNode(it.Clone()) qs.AddNode(node) - (*outputMap)["nodes"] = qs.nodes - (*outputMap)["links"] = qs.links + outputMap["nodes"] = qs.nodes + outputMap["links"] = qs.links } func (qs *queryShape) AddNode(n *Node) { diff --git a/graph/iterator/query_shape_test.go b/graph/iterator/query_shape_test.go index 9e53d28..a67ba62 100644 --- a/graph/iterator/query_shape_test.go +++ b/graph/iterator/query_shape_test.go @@ -15,112 +15,116 @@ package iterator import ( + "reflect" "testing" - . "github.com/smartystreets/goconvey/convey" - "github.com/google/cayley/graph" ) -func buildHasaWithTag(ts graph.TripleStore, tag string, target string) *HasA { - fixed_obj := ts.FixedIterator() - fixed_pred := ts.FixedIterator() - fixed_obj.Add(ts.ValueOf(target)) - fixed_pred.Add(ts.ValueOf("status")) - fixed_obj.AddTag(tag) - lto1 := NewLinksTo(ts, fixed_obj, graph.Object) - lto2 := NewLinksTo(ts, fixed_pred, graph.Predicate) +func hasaWithTag(ts graph.TripleStore, tag string, target string) *HasA { and := NewAnd() - and.AddSubIterator(lto1) - and.AddSubIterator(lto2) - hasa := NewHasA(ts, and, graph.Subject) - return hasa + + obj := ts.FixedIterator() + obj.Add(ts.ValueOf(target)) + obj.AddTag(tag) + and.AddSubIterator(NewLinksTo(ts, obj, graph.Object)) + + pred := ts.FixedIterator() + pred.Add(ts.ValueOf("status")) + and.AddSubIterator(NewLinksTo(ts, pred, graph.Predicate)) + + return NewHasA(ts, and, graph.Subject) } func TestQueryShape(t *testing.T) { - var queryShape map[string]interface{} - ts := new(TestTripleStore) - ts.On("ValueOf", "cool").Return(1) - ts.On("NameOf", 1).Return("cool") - ts.On("ValueOf", "status").Return(2) - ts.On("NameOf", 2).Return("status") - ts.On("ValueOf", "fun").Return(3) - ts.On("NameOf", 3).Return("fun") - ts.On("ValueOf", "name").Return(4) - ts.On("NameOf", 4).Return("name") + ts := &store{ + data: []string{ + 1: "cool", + 2: "status", + 3: "fun", + 4: "name", + }, + } - Convey("Given a single linkage iterator's shape", t, func() { - queryShape = make(map[string]interface{}) - hasa := buildHasaWithTag(ts, "tag", "cool") - hasa.AddTag("top") - OutputQueryShapeForIterator(hasa, ts, &queryShape) + // Given a single linkage iterator's shape. + hasa := hasaWithTag(ts, "tag", "cool") + hasa.AddTag("top") - Convey("It should have three nodes and one link", func() { - nodes := queryShape["nodes"].([]Node) - links := queryShape["links"].([]Link) - So(len(nodes), ShouldEqual, 3) - So(len(links), ShouldEqual, 1) - }) + shape := make(map[string]interface{}) + OutputQueryShapeForIterator(hasa, ts, shape) - Convey("These nodes should be correctly tagged", func() { - nodes := queryShape["nodes"].([]Node) - So(nodes[0].Tags, ShouldResemble, []string{"tag"}) - So(nodes[1].IsLinkNode, ShouldEqual, true) - So(nodes[2].Tags, ShouldResemble, []string{"top"}) + nodes := shape["nodes"].([]Node) + if len(nodes) != 3 { + t.Errorf("Failed to get correct number of nodes, got:%d expect:4", len(nodes)) + } + links := shape["links"].([]Link) + if len(nodes) != 3 { + t.Errorf("Failed to get correct number of links, got:%d expect:1", len(links)) + } - }) + // Nodes should be correctly tagged. + nodes = shape["nodes"].([]Node) + for i, expect := range [][]string{{"tag"}, nil, {"top"}} { + if !reflect.DeepEqual(nodes[i].Tags, expect) { + t.Errorf("Failed to get correct tag for node[%d], got:%s expect:%s", i, nodes[i].Tags, expect) + } + } + if !nodes[1].IsLinkNode { + t.Error("Failed to get node[1] as link node") + } - Convey("The link should be correctly typed", func() { - nodes := queryShape["nodes"].([]Node) - links := queryShape["links"].([]Link) - So(links[0].Source, ShouldEqual, nodes[2].Id) - So(links[0].Target, ShouldEqual, nodes[0].Id) - So(links[0].LinkNode, ShouldEqual, nodes[1].Id) - So(links[0].Pred, ShouldEqual, 0) + // Link should be correctly typed. + nodes = shape["nodes"].([]Node) + link := shape["links"].([]Link)[0] + if link.Source != nodes[2].Id { + t.Errorf("Failed to get correct link source, got:%v expect:%v", link.Source, nodes[2].Id) + } + if link.Target != nodes[0].Id { + t.Errorf("Failed to get correct link target, got:%v expect:%v", link.Target, nodes[0].Id) + } + if link.LinkNode != nodes[1].Id { + t.Errorf("Failed to get correct link node, got:%v expect:%v", link.LinkNode, nodes[1].Id) + } + if link.Pred != 0 { + t.Errorf("Failed to get correct number of predecessors:%v expect:0", link.Pred) + } - }) + // Given a name-of-an-and-iterator's shape. + andInternal := NewAnd() - }) + hasa1 := hasaWithTag(ts, "tag1", "cool") + hasa1.AddTag("hasa1") + andInternal.AddSubIterator(hasa1) - Convey("Given a name-of-an-and-iterator's shape", t, func() { - queryShape = make(map[string]interface{}) - hasa1 := buildHasaWithTag(ts, "tag1", "cool") - hasa1.AddTag("hasa1") - hasa2 := buildHasaWithTag(ts, "tag2", "fun") - hasa1.AddTag("hasa2") - andInternal := NewAnd() - andInternal.AddSubIterator(hasa1) - andInternal.AddSubIterator(hasa2) - fixed_pred := ts.FixedIterator() - fixed_pred.Add(ts.ValueOf("name")) - lto1 := NewLinksTo(ts, andInternal, graph.Subject) - lto2 := NewLinksTo(ts, fixed_pred, graph.Predicate) - and := NewAnd() - and.AddSubIterator(lto1) - and.AddSubIterator(lto2) - hasa := NewHasA(ts, and, graph.Object) - OutputQueryShapeForIterator(hasa, ts, &queryShape) + hasa2 := hasaWithTag(ts, "tag2", "fun") + hasa2.AddTag("hasa2") + andInternal.AddSubIterator(hasa2) - Convey("It should have seven nodes and three links", func() { - nodes := queryShape["nodes"].([]Node) - links := queryShape["links"].([]Link) - So(len(nodes), ShouldEqual, 7) - So(len(links), ShouldEqual, 3) - }) + pred := ts.FixedIterator() + pred.Add(ts.ValueOf("name")) - Convey("Three of the nodes are link nodes, four aren't", func() { - nodes := queryShape["nodes"].([]Node) - count := 0 - for _, node := range nodes { - if node.IsLinkNode { - count++ - } - } - So(count, ShouldEqual, 3) - }) + and := NewAnd() + and.AddSubIterator(NewLinksTo(ts, andInternal, graph.Subject)) + and.AddSubIterator(NewLinksTo(ts, pred, graph.Predicate)) - Convey("These nodes should be correctly tagged", nil) - - }) + shape = make(map[string]interface{}) + OutputQueryShapeForIterator(NewHasA(ts, and, graph.Object), ts, shape) + links = shape["links"].([]Link) + if len(links) != 3 { + t.Errorf("Failed to find the correct number of links, got:%d expect:3", len(links)) + } + nodes = shape["nodes"].([]Node) + if len(nodes) != 7 { + t.Errorf("Failed to find the correct number of nodes, got:%d expect:7", len(nodes)) + } + var n int + for _, node := range nodes { + if node.IsLinkNode { + n++ + } + } + if n != 3 { + t.Errorf("Failed to find the correct number of link nodes, got:%d expect:3", n) + } } diff --git a/graph/iterator/value_comparison_iterator_test.go b/graph/iterator/value_comparison_iterator_test.go index 100e639..94555d7 100644 --- a/graph/iterator/value_comparison_iterator_test.go +++ b/graph/iterator/value_comparison_iterator_test.go @@ -20,35 +20,14 @@ import ( "github.com/google/cayley/graph" ) -func SetupMockTripleStore(nameMap map[string]int) *TestTripleStore { - ts := new(TestTripleStore) - for k, v := range nameMap { - ts.On("ValueOf", k).Return(v) - ts.On("NameOf", v).Return(k) +var simpleStore = &store{data: []string{"0", "1", "2", "3", "4", "5"}} + +func simpleFixedIterator() *Fixed { + f := newFixed() + for i := 0; i < 5; i++ { + f.Add(i) } - return ts -} - -func SimpleValueTripleStore() *TestTripleStore { - ts := SetupMockTripleStore(map[string]int{ - "0": 0, - "1": 1, - "2": 2, - "3": 3, - "4": 4, - "5": 5, - }) - return ts -} - -func BuildFixedIterator() *Fixed { - fixed := newFixed() - fixed.Add(0) - fixed.Add(1) - fixed.Add(2) - fixed.Add(3) - fixed.Add(4) - return fixed + return f } func checkIteratorContains(ts graph.TripleStore, it graph.Iterator, expected []string, t *testing.T) { @@ -82,36 +61,36 @@ func checkIteratorContains(ts graph.TripleStore, it graph.Iterator, expected []s } func TestWorkingIntValueComparison(t *testing.T) { - ts := SimpleValueTripleStore() - fixed := BuildFixedIterator() + ts := simpleStore + fixed := simpleFixedIterator() vc := NewComparison(fixed, kCompareLT, int64(3), ts) checkIteratorContains(ts, vc, []string{"0", "1", "2"}, t) } func TestFailingIntValueComparison(t *testing.T) { - ts := SimpleValueTripleStore() - fixed := BuildFixedIterator() + ts := simpleStore + fixed := simpleFixedIterator() vc := NewComparison(fixed, kCompareLT, int64(0), ts) checkIteratorContains(ts, vc, []string{}, t) } func TestWorkingGT(t *testing.T) { - ts := SimpleValueTripleStore() - fixed := BuildFixedIterator() + ts := simpleStore + fixed := simpleFixedIterator() vc := NewComparison(fixed, kCompareGT, int64(2), ts) checkIteratorContains(ts, vc, []string{"3", "4"}, t) } func TestWorkingGTE(t *testing.T) { - ts := SimpleValueTripleStore() - fixed := BuildFixedIterator() + ts := simpleStore + fixed := simpleFixedIterator() vc := NewComparison(fixed, kCompareGTE, int64(2), ts) checkIteratorContains(ts, vc, []string{"2", "3", "4"}, t) } func TestVCICheck(t *testing.T) { - ts := SimpleValueTripleStore() - fixed := BuildFixedIterator() + ts := simpleStore + fixed := simpleFixedIterator() vc := NewComparison(fixed, kCompareGTE, int64(2), ts) if vc.Check(1) { t.Error("1 is less than 2, should be GTE") diff --git a/graph/leveldb/leveldb_test.go b/graph/leveldb/leveldb_test.go index 6912bd7..57821a1 100644 --- a/graph/leveldb/leveldb_test.go +++ b/graph/leveldb/leveldb_test.go @@ -17,11 +17,10 @@ package leveldb import ( "io/ioutil" "os" + "reflect" "sort" "testing" - . "github.com/smartystreets/goconvey/convey" - "github.com/google/cayley/graph" "github.com/google/cayley/graph/iterator" ) @@ -43,394 +42,400 @@ func makeTripleSet() []*graph.Triple { return tripleSet } -func extractTripleFromIterator(ts graph.TripleStore, it graph.Iterator) []string { - var output []string +func iteratedTriples(ts graph.TripleStore, it graph.Iterator) []*graph.Triple { + var res ordered for { val, ok := it.Next() if !ok { break } - output = append(output, ts.Triple(val).String()) + res = append(res, ts.Triple(val)) } - return output + sort.Sort(res) + return res } -func extractValuesFromIterator(ts graph.TripleStore, it graph.Iterator) []string { - var output []string +type ordered []*graph.Triple + +func (o ordered) Len() int { return len(o) } +func (o ordered) Less(i, j int) bool { + switch { + case o[i].Subject < o[j].Subject, + + o[i].Subject == o[j].Subject && + o[i].Predicate < o[j].Predicate, + + o[i].Subject == o[j].Subject && + o[i].Predicate == o[j].Predicate && + o[i].Object < o[j].Object, + + o[i].Subject == o[j].Subject && + o[i].Predicate == o[j].Predicate && + o[i].Object == o[j].Object && + o[i].Provenance < o[j].Provenance: + + return true + + default: + return false + } +} +func (o ordered) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +func iteratedNames(ts graph.TripleStore, it graph.Iterator) []string { + var res []string for { val, ok := it.Next() if !ok { break } - output = append(output, ts.NameOf(val)) + res = append(res, ts.NameOf(val)) } - return output + sort.Strings(res) + return res } func TestCreateDatabase(t *testing.T) { + tmpDir, err := ioutil.TempDir(os.TempDir(), "cayley_test") + if err != nil { + t.Fatalf("Could not create working directory: %v", err) + } + t.Log(tmpDir) - Convey("Given a database path", t, func() { - tmpDir, err := ioutil.TempDir(os.TempDir(), "cayley_test") - t.Log(tmpDir) - if err != nil { - t.Fatal("Cannot use ioutil.", err) - } + if created := CreateNewLevelDB(tmpDir); !created { + t.Fatal("Failed to create LevelDB database.") + } - Convey("Creates a database", func() { - ok := CreateNewLevelDB(tmpDir) - So(ok, ShouldBeTrue) - Convey("And has good defaults for a new database", func() { - ts := NewTripleStore(tmpDir, nil) - So(ts, ShouldNotBeNil) - So(ts.Size(), ShouldEqual, 0) - ts.Close() - }) - }) + ts := NewTripleStore(tmpDir, nil) + if ts == nil { + t.Error("Failed to create leveldb TripleStore.") + } + if s := ts.Size(); s != 0 { + t.Errorf("Unexpected size, got:%d expected:0", s) + } + ts.Close() - Convey("Fails if it cannot create the database", func() { - ok := CreateNewLevelDB("/dev/null/some terrible path") - So(ok, ShouldBeFalse) - So(func() { NewTripleStore("/dev/null/some terrible path", nil) }, ShouldPanic) - }) - - Reset(func() { - os.RemoveAll(tmpDir) - }) - - }) + if created := CreateNewLevelDB("/dev/null/some terrible path"); created { + t.Errorf("Created LevelDB database for bad path.") + } + // TODO(kortschak) Invalidate this test by using error returns rather than panics. + var panicked bool + func() { + defer func() { + r := recover() + panicked = r != nil + }() + NewTripleStore("/dev/null/some terrible path", nil) + }() + if !panicked { + t.Error("NewTripleStore failed to panic with bad path.") + } + os.RemoveAll(tmpDir) } func TestLoadDatabase(t *testing.T) { + tmpDir, err := ioutil.TempDir(os.TempDir(), "cayley_test") + if err != nil { + t.Fatalf("Could not create working directory: %v", err) + } + defer os.RemoveAll(tmpDir) + t.Log(tmpDir) + + if created := CreateNewLevelDB(tmpDir); !created { + t.Fatal("Failed to create LevelDB database.") + } + var ts *TripleStore - Convey("Given a created database path", t, func() { - tmpDir, _ := ioutil.TempDir(os.TempDir(), "cayley_test") - t.Log(tmpDir) - ok := CreateNewLevelDB(tmpDir) - So(ok, ShouldBeTrue) - ts = NewTripleStore(tmpDir, nil) + ts = NewTripleStore(tmpDir, nil) + if ts == nil { + t.Error("Failed to create leveldb TripleStore.") + } - Convey("Can load a single triple", func() { - ts.AddTriple(&graph.Triple{"Something", "points_to", "Something Else", "context"}) - So(ts.NameOf(ts.ValueOf("Something")), ShouldEqual, "Something") - So(ts.Size(), ShouldEqual, 1) - }) + ts.AddTriple(&graph.Triple{"Something", "points_to", "Something Else", "context"}) + for _, pq := range []string{"Something", "points_to", "Something Else", "context"} { + if got := ts.NameOf(ts.ValueOf(pq)); got != pq { + t.Errorf("Failed to roundtrip %q, got:%q expect:%q", pq, got, pq) + } + } + if s := ts.Size(); s != 1 { + t.Errorf("Unexpected triplestore size, got:%d expect:1", s) + } + ts.Close() - Convey("Can load many triples", func() { + if created := CreateNewLevelDB(tmpDir); !created { + t.Fatal("Failed to create LevelDB database.") + } + ts = NewTripleStore(tmpDir, nil) + if ts == nil { + t.Error("Failed to create leveldb TripleStore.") + } - ts.AddTripleSet(makeTripleSet()) - So(ts.Size(), ShouldEqual, 11) - So(ts.GetSizeFor(ts.ValueOf("B")), ShouldEqual, 5) + ts.AddTripleSet(makeTripleSet()) + if s := ts.Size(); s != 11 { + t.Errorf("Unexpected triplestore size, got:%d expect:11", s) + } + if s := ts.GetSizeFor(ts.ValueOf("B")); s != 5 { + t.Errorf("Unexpected triplestore size, got:%d expect:5", s) + } - Convey("Can delete triples", func() { - ts.RemoveTriple(&graph.Triple{"A", "follows", "B", ""}) - So(ts.Size(), ShouldEqual, 10) - So(ts.GetSizeFor(ts.ValueOf("B")), ShouldEqual, 4) - }) - }) - - Reset(func() { - ts.Close() - os.RemoveAll(tmpDir) - }) - - }) + ts.RemoveTriple(&graph.Triple{"A", "follows", "B", ""}) + if s := ts.Size(); s != 10 { + t.Errorf("Unexpected triplestore size after RemoveTriple, got:%d expect:10", s) + } + if s := ts.GetSizeFor(ts.ValueOf("B")); s != 4 { + t.Errorf("Unexpected triplestore size, got:%d expect:4", s) + } + ts.Close() } func TestIterator(t *testing.T) { + tmpDir, err := ioutil.TempDir(os.TempDir(), "cayley_test") + if err != nil { + t.Fatalf("Could not create working directory: %v", err) + } + defer os.RemoveAll(tmpDir) + t.Log(tmpDir) + + if created := CreateNewLevelDB(tmpDir); !created { + t.Fatal("Failed to create LevelDB database.") + } + var ts *TripleStore + ts = NewTripleStore(tmpDir, nil) + ts.AddTripleSet(makeTripleSet()) + var it graph.Iterator - Convey("Given a prepared database", t, func() { - tmpDir, _ := ioutil.TempDir(os.TempDir(), "cayley_test") - t.Log(tmpDir) - defer os.RemoveAll(tmpDir) - ok := CreateNewLevelDB(tmpDir) - So(ok, ShouldBeTrue) - ts = NewTripleStore(tmpDir, nil) - ts.AddTripleSet(makeTripleSet()) - var it graph.Iterator + it = ts.NodesAllIterator() + if it == nil { + t.Fatal("Got nil iterator.") + } - Convey("Can create an all iterator for nodes", func() { - it = ts.NodesAllIterator() - So(it, ShouldNotBeNil) + size, exact := it.Size() + if size <= 0 || size >= 20 { + t.Errorf("Unexpected size, got:%d expect:(0, 20)", size) + } + if exact { + t.Errorf("Got unexpected exact result.") + } + if typ := it.Type(); typ != graph.All { + t.Errorf("Unexpected iterator type, got:%v expect:%v", typ, graph.All) + } + optIt, changed := it.Optimize() + if changed || optIt != it { + t.Errorf("Optimize unexpectedly changed iterator.") + } - Convey("Has basics", func() { - size, accurate := it.Size() - So(size, ShouldBeBetween, 0, 20) - So(accurate, ShouldBeFalse) - So(it.Type(), ShouldEqual, graph.All) - re_it, ok := it.Optimize() - So(ok, ShouldBeFalse) - So(re_it, ShouldPointTo, it) - }) + expect := []string{ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "follows", + "status", + "cool", + "status_graph", + } + sort.Strings(expect) + for i := 0; i < 2; i++ { + got := iteratedNames(ts, it) + sort.Strings(got) + if !reflect.DeepEqual(got, expect) { + t.Errorf("Unexpected iterated result on repeat %d, got:%v expect:%v", i, got, expect) + } + it.Reset() + } - Convey("Iterates all nodes", func() { - expected := []string{ - "A", - "B", - "C", - "D", - "E", - "F", - "G", - "follows", - "status", - "cool", - "status_graph", - } - sort.Strings(expected) - actual := extractValuesFromIterator(ts, it) - sort.Strings(actual) - So(actual, ShouldResemble, expected) - it.Reset() - actual = extractValuesFromIterator(ts, it) - sort.Strings(actual) - So(actual, ShouldResemble, expected) + for _, pq := range expect { + if !it.Check(ts.ValueOf(pq)) { + t.Errorf("Failed to find and check %q correctly", pq) + } + } + // FIXME(kortschak) Why does this fail? + /* + for _, pq := range []string{"baller"} { + if it.Check(ts.ValueOf(pq)) { + t.Errorf("Failed to check %q correctly", pq) + } + } + */ + it.Reset() - }) - - Convey("Contains a couple nodes", func() { - So(it.Check(ts.ValueOf("A")), ShouldBeTrue) - So(it.Check(ts.ValueOf("cool")), ShouldBeTrue) - //So(it.Check(ts.ValueOf("baller")), ShouldBeFalse) - }) - - Reset(func() { - it.Reset() - }) - }) - - Convey("Can create an all iterator for edges", func() { - it := ts.TriplesAllIterator() - So(it, ShouldNotBeNil) - Convey("Has basics", func() { - size, accurate := it.Size() - So(size, ShouldBeBetween, 0, 20) - So(accurate, ShouldBeFalse) - So(it.Type(), ShouldEqual, graph.All) - re_it, ok := it.Optimize() - So(ok, ShouldBeFalse) - So(re_it, ShouldPointTo, it) - }) - - Convey("Iterates an edge", func() { - edge_val, _ := it.Next() - triple := ts.Triple(edge_val) - set := makeTripleSet() - var string_set []string - for _, t := range set { - string_set = append(string_set, t.String()) - } - So(triple.String(), ShouldBeIn, string_set) - }) - - Reset(func() { - ts.Close() - }) - }) - }) + it = ts.TriplesAllIterator() + edge, _ := it.Next() + triple := ts.Triple(edge) + set := makeTripleSet() + var ok bool + for _, t := range set { + if t.String() == triple.String() { + ok = true + break + } + } + if !ok { + t.Errorf("Failed to find %q during iteration, got:%q", triple, set) + } + ts.Close() } func TestSetIterator(t *testing.T) { - var ts *TripleStore - var tmpDir string - Convey("Given a prepared database", t, func() { - tmpDir, _ = ioutil.TempDir(os.TempDir(), "cayley_test") - t.Log(tmpDir) - defer os.RemoveAll(tmpDir) - ok := CreateNewLevelDB(tmpDir) - So(ok, ShouldBeTrue) - ts = NewTripleStore(tmpDir, nil) - ts.AddTripleSet(makeTripleSet()) - var it graph.Iterator + tmpDir, _ := ioutil.TempDir(os.TempDir(), "cayley_test") + t.Log(tmpDir) + defer os.RemoveAll(tmpDir) + ok := CreateNewLevelDB(tmpDir) + if !ok { + t.Fatalf("Failed to create working directory") + } - Convey("Can create a subject iterator", func() { - it = ts.TripleIterator(graph.Subject, ts.ValueOf("C")) + ts := NewTripleStore(tmpDir, nil) + defer ts.Close() - Convey("Containing the right things", func() { - expected := []string{ - (&graph.Triple{"C", "follows", "B", ""}).String(), - (&graph.Triple{"C", "follows", "D", ""}).String(), - } - actual := extractTripleFromIterator(ts, it) - sort.Strings(actual) - sort.Strings(expected) - So(actual, ShouldResemble, expected) - }) + ts.AddTripleSet(makeTripleSet()) - Convey("And checkable", func() { - and := iterator.NewAnd() - and.AddSubIterator(ts.TriplesAllIterator()) - and.AddSubIterator(it) + expect := []*graph.Triple{ + {"C", "follows", "B", ""}, + {"C", "follows", "D", ""}, + } + sort.Sort(ordered(expect)) - expected := []string{ - (&graph.Triple{"C", "follows", "B", ""}).String(), - (&graph.Triple{"C", "follows", "D", ""}).String(), - } - actual := extractTripleFromIterator(ts, and) - sort.Strings(actual) - sort.Strings(expected) - So(actual, ShouldResemble, expected) - }) - Reset(func() { - it.Reset() - }) + // Subject iterator. + it := ts.TripleIterator(graph.Subject, ts.ValueOf("C")) - }) + if got := iteratedTriples(ts, it); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to get expected results, got:%v expect:%v", got, expect) + } + it.Reset() - Convey("Can create an object iterator", func() { - it = ts.TripleIterator(graph.Object, ts.ValueOf("F")) + and := iterator.NewAnd() + and.AddSubIterator(ts.TriplesAllIterator()) + and.AddSubIterator(it) - Convey("Containing the right things", func() { - expected := []string{ - (&graph.Triple{"B", "follows", "F", ""}).String(), - (&graph.Triple{"E", "follows", "F", ""}).String(), - } - actual := extractTripleFromIterator(ts, it) - sort.Strings(actual) - sort.Strings(expected) - So(actual, ShouldResemble, expected) - }) + if got := iteratedTriples(ts, and); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect) + } - Convey("Mutually and-checkable", func() { - and := iterator.NewAnd() - and.AddSubIterator(ts.TripleIterator(graph.Subject, ts.ValueOf("B"))) - and.AddSubIterator(it) + // Object iterator. + it = ts.TripleIterator(graph.Object, ts.ValueOf("F")) - expected := []string{ - (&graph.Triple{"B", "follows", "F", ""}).String(), - } - actual := extractTripleFromIterator(ts, and) - sort.Strings(actual) - sort.Strings(expected) - So(actual, ShouldResemble, expected) - }) + expect = []*graph.Triple{ + {"B", "follows", "F", ""}, + {"E", "follows", "F", ""}, + } + sort.Sort(ordered(expect)) + if got := iteratedTriples(ts, it); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to get expected results, got:%v expect:%v", got, expect) + } - }) + and = iterator.NewAnd() + and.AddSubIterator(ts.TripleIterator(graph.Subject, ts.ValueOf("B"))) + and.AddSubIterator(it) - Convey("Can create a predicate iterator", func() { - it = ts.TripleIterator(graph.Predicate, ts.ValueOf("status")) + expect = []*graph.Triple{ + {"B", "follows", "F", ""}, + } + if got := iteratedTriples(ts, and); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect) + } - Convey("Containing the right things", func() { - expected := []string{ - (&graph.Triple{"B", "status", "cool", "status_graph"}).String(), - (&graph.Triple{"D", "status", "cool", "status_graph"}).String(), - (&graph.Triple{"G", "status", "cool", "status_graph"}).String(), - } - actual := extractTripleFromIterator(ts, it) - sort.Strings(actual) - sort.Strings(expected) - So(actual, ShouldResemble, expected) - }) + // Predicate iterator. + it = ts.TripleIterator(graph.Predicate, ts.ValueOf("status")) - }) + expect = []*graph.Triple{ + {"B", "status", "cool", "status_graph"}, + {"D", "status", "cool", "status_graph"}, + {"G", "status", "cool", "status_graph"}, + } + sort.Sort(ordered(expect)) + if got := iteratedTriples(ts, it); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to get expected results from predicate iterator, got:%v expect:%v", got, expect) + } - Convey("Can create a provenance iterator", func() { - it = ts.TripleIterator(graph.Provenance, ts.ValueOf("status_graph")) + // Provenance iterator. + it = ts.TripleIterator(graph.Provenance, ts.ValueOf("status_graph")) - Convey("Containing the right things", func() { - expected := []string{ - (&graph.Triple{"B", "status", "cool", "status_graph"}).String(), - (&graph.Triple{"D", "status", "cool", "status_graph"}).String(), - (&graph.Triple{"G", "status", "cool", "status_graph"}).String(), - } - actual := extractTripleFromIterator(ts, it) - sort.Strings(actual) - sort.Strings(expected) - So(actual, ShouldResemble, expected) - }) + expect = []*graph.Triple{ + {"B", "status", "cool", "status_graph"}, + {"D", "status", "cool", "status_graph"}, + {"G", "status", "cool", "status_graph"}, + } + sort.Sort(ordered(expect)) + if got := iteratedTriples(ts, it); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to get expected results from predicate iterator, got:%v expect:%v", got, expect) + } + it.Reset() - Convey("Can be cross-checked", func() { - and := iterator.NewAnd() - // Order is important - and.AddSubIterator(ts.TripleIterator(graph.Subject, ts.ValueOf("B"))) - and.AddSubIterator(it) + // Order is important + and = iterator.NewAnd() + and.AddSubIterator(ts.TripleIterator(graph.Subject, ts.ValueOf("B"))) + and.AddSubIterator(it) - expected := []string{ - (&graph.Triple{"B", "status", "cool", "status_graph"}).String(), - } - actual := extractTripleFromIterator(ts, and) - So(actual, ShouldResemble, expected) - }) + expect = []*graph.Triple{ + {"B", "status", "cool", "status_graph"}, + } + if got := iteratedTriples(ts, and); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect) + } + it.Reset() - Convey("Can check against other iterators", func() { - and := iterator.NewAnd() - // Order is important - and.AddSubIterator(it) - and.AddSubIterator(ts.TripleIterator(graph.Subject, ts.ValueOf("B"))) - - expected := []string{ - (&graph.Triple{"B", "status", "cool", "status_graph"}).String(), - } - actual := extractTripleFromIterator(ts, and) - So(actual, ShouldResemble, expected) - }) - Reset(func() { - it.Reset() - }) - - }) - - Reset(func() { - ts.Close() - }) - - }) + // Order is important + and = iterator.NewAnd() + and.AddSubIterator(it) + and.AddSubIterator(ts.TripleIterator(graph.Subject, ts.ValueOf("B"))) + expect = []*graph.Triple{ + {"B", "status", "cool", "status_graph"}, + } + if got := iteratedTriples(ts, and); !reflect.DeepEqual(got, expect) { + t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect) + } } func TestOptimize(t *testing.T) { - var ts *TripleStore - var lto graph.Iterator - var tmpDir string + tmpDir, _ := ioutil.TempDir(os.TempDir(), "cayley_test") + t.Log(tmpDir) + defer os.RemoveAll(tmpDir) + ok := CreateNewLevelDB(tmpDir) + if !ok { + t.Fatalf("Failed to create working directory") + } + ts := NewTripleStore(tmpDir, nil) + ts.AddTripleSet(makeTripleSet()) - Convey("Given a prepared database", t, func() { - tmpDir, _ = ioutil.TempDir(os.TempDir(), "cayley_test") - t.Log(tmpDir) - defer os.RemoveAll(tmpDir) - ok := CreateNewLevelDB(tmpDir) - So(ok, ShouldBeTrue) - ts = NewTripleStore(tmpDir, nil) - ts.AddTripleSet(makeTripleSet()) + // With an linksto-fixed pair + fixed := ts.FixedIterator() + fixed.Add(ts.ValueOf("F")) + fixed.AddTag("internal") + lto := iterator.NewLinksTo(ts, fixed, graph.Object) - Convey("With an linksto-fixed pair", func() { - fixed := ts.FixedIterator() - fixed.Add(ts.ValueOf("F")) - fixed.AddTag("internal") - lto = iterator.NewLinksTo(ts, fixed, graph.Object) + oldIt := lto.Clone() + newIt, ok := lto.Optimize() + if !ok { + t.Errorf("Failed to optimize iterator") + } + if newIt.Type() != Type() { + t.Errorf("Optimized iterator type does not match original, got:%v expect:%v", newIt.Type(), Type()) + } - Convey("Creates an appropriate iterator", func() { - oldIt := lto.Clone() - newIt, ok := lto.Optimize() - So(ok, ShouldBeTrue) - So(newIt.Type(), ShouldEqual, Type()) - - Convey("Containing the right things", func() { - afterOp := extractTripleFromIterator(ts, newIt) - beforeOp := extractTripleFromIterator(ts, oldIt) - sort.Strings(afterOp) - sort.Strings(beforeOp) - So(afterOp, ShouldResemble, beforeOp) - }) - - Convey("With the correct tags", func() { - oldIt.Next() - newIt.Next() - oldResults := make(map[string]graph.Value) - oldIt.TagResults(oldResults) - newResults := make(map[string]graph.Value) - oldIt.TagResults(newResults) - So(newResults, ShouldResemble, oldResults) - }) - - }) - - }) - - }) + newTriples := iteratedTriples(ts, newIt) + oldTriples := iteratedTriples(ts, oldIt) + if !reflect.DeepEqual(newTriples, oldTriples) { + t.Errorf("Optimized iteration does not match original") + } + oldIt.Next() + oldResults := make(map[string]graph.Value) + oldIt.TagResults(oldResults) + newIt.Next() + newResults := make(map[string]graph.Value) + newIt.TagResults(newResults) + if !reflect.DeepEqual(newResults, oldResults) { + t.Errorf("Discordant tag results, new:%v old:%v", newResults, oldResults) + } } diff --git a/query/gremlin/finals.go b/query/gremlin/finals.go index d38512e..7ad0886 100644 --- a/query/gremlin/finals.go +++ b/query/gremlin/finals.go @@ -239,7 +239,7 @@ func runIteratorWithCallback(it graph.Iterator, ses *Session, callback otto.Valu func runIteratorOnSession(it graph.Iterator, ses *Session) { if ses.lookingForQueryShape { - iterator.OutputQueryShapeForIterator(it, ses.ts, &(ses.queryShape)) + iterator.OutputQueryShapeForIterator(it, ses.ts, ses.queryShape) return } it, _ = it.Optimize() diff --git a/query/mql/session.go b/query/mql/session.go index 056389e..e5944d4 100644 --- a/query/mql/session.go +++ b/query/mql/session.go @@ -51,7 +51,7 @@ func (m *Session) GetQuery(input string, output_struct chan map[string]interface m.currentQuery = NewQuery(m) m.currentQuery.BuildIteratorTree(mqlQuery) output := make(map[string]interface{}) - iterator.OutputQueryShapeForIterator(m.currentQuery.it, m.ts, &output) + iterator.OutputQueryShapeForIterator(m.currentQuery.it, m.ts, output) nodes := output["nodes"].([]iterator.Node) new_nodes := make([]iterator.Node, 0) for _, n := range nodes {