blob: f880bc565e5007c0fbd92c8f084257f43249fb6e [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package memory
import (
"bytes"
"testing"
"go.chromium.org/luci/gae/service/datastore"
"go.chromium.org/luci/common/data/cmpbin"
. "github.com/smartystreets/goconvey/convey"
)
func mkNum(n int64) []byte {
buf := &bytes.Buffer{}
_, err := cmpbin.WriteInt(buf, n)
memoryCorruption(err)
return buf.Bytes()
}
func readNum(data []byte) int64 {
ret, _, err := cmpbin.ReadInt(bytes.NewBuffer(data))
memoryCorruption(err)
return ret
}
func countItems(mc memCollection) int {
count := 0
mc.ForEachItem(func(_, _ []byte) bool {
count++
return true
})
return count
}
func TestIterator(t *testing.T) {
t.Parallel()
s := newMemStore()
c := s.GetOrCreateCollection("zup")
prev := []byte{}
for i := 5; i < 100; i++ {
data := mkNum(int64(i))
c.Set(data, prev)
prev = data
}
c = s.Snapshot().GetCollection("zup")
iterCB := func(it *iterator, cb func(k, v []byte)) {
for ent := it.next(); ent != nil; ent = it.next() {
cb(ent.key, ent.value)
}
}
get := func(c C, t *iterator) interface{} {
if ent := t.next(); ent != nil {
return readNum(ent.key)
}
return nil
}
skipGet := func(c C, t *iterator, skipTo int64) interface{} {
t.skip(mkNum(skipTo))
return get(c, t)
}
didIterate := func(t *iterator) (did bool) {
iterCB(t, func(k, v []byte) {
did = true
})
return
}
Convey("Test iterator", t, func() {
Convey("start at nil", func(ctx C) {
t := (&iterDefinition{c: c}).mkIter()
So(get(ctx, t), ShouldEqual, 5)
So(get(ctx, t), ShouldEqual, 6)
So(get(ctx, t), ShouldEqual, 7)
Convey("And can skip", func(ctx C) {
So(skipGet(ctx, t, 10), ShouldEqual, 10)
So(get(ctx, t), ShouldEqual, 11)
Convey("But not forever", func(ctx C) {
t.skip(mkNum(200))
So(didIterate(t), ShouldBeFalse)
So(didIterate(t), ShouldBeFalse)
})
})
Convey("Can iterate explicitly", func(ctx C) {
So(skipGet(ctx, t, 7), ShouldEqual, 8)
So(skipGet(ctx, t, 8), ShouldEqual, 9)
// Giving the immediately next key doesn't cause an internal reset.
So(skipGet(ctx, t, 10), ShouldEqual, 10)
})
Convey("Going backwards is ignored", func(ctx C) {
So(skipGet(ctx, t, 3), ShouldEqual, 8)
So(get(ctx, t), ShouldEqual, 9)
So(skipGet(ctx, t, 20), ShouldEqual, 20)
So(get(ctx, t), ShouldEqual, 21)
})
Convey("will stop at the end of the list", func(ctx C) {
So(skipGet(ctx, t, 95), ShouldEqual, 95)
So(get(ctx, t), ShouldEqual, 96)
So(get(ctx, t), ShouldEqual, 97)
So(get(ctx, t), ShouldEqual, 98)
So(get(ctx, t), ShouldEqual, 99)
So(get(ctx, t), ShouldBeNil)
So(get(ctx, t), ShouldBeNil)
})
})
Convey("can have caps on both sides", func(ctx C) {
t := (&iterDefinition{c: c, start: mkNum(20), end: mkNum(25)}).mkIter()
So(get(ctx, t), ShouldEqual, 20)
So(get(ctx, t), ShouldEqual, 21)
So(get(ctx, t), ShouldEqual, 22)
So(get(ctx, t), ShouldEqual, 23)
So(get(ctx, t), ShouldEqual, 24)
So(didIterate(t), ShouldBeFalse)
})
Convey("can skip over starting cap", func(ctx C) {
t := (&iterDefinition{c: c, start: mkNum(20), end: mkNum(25)}).mkIter()
So(skipGet(ctx, t, 22), ShouldEqual, 22)
So(get(ctx, t), ShouldEqual, 23)
So(get(ctx, t), ShouldEqual, 24)
So(didIterate(t), ShouldBeFalse)
})
})
}
func TestMultiIteratorSimple(t *testing.T) {
t.Parallel()
// Simulate an index with 2 columns (int and int).
vals := [][]int64{
{1, 0},
{1, 2},
{1, 4},
{1, 7},
{1, 9},
{3, 10},
{3, 11},
}
valBytes := make([][]byte, len(vals))
for i, nms := range vals {
numbs := make([][]byte, len(nms))
for j, n := range nms {
numbs[j] = mkNum(n)
}
valBytes[i] = cmpbin.ConcatBytes(numbs...)
}
otherVals := [][]int64{
{3, 0},
{4, 10},
{19, 7},
{20, 2},
{20, 3},
{20, 4},
{20, 8},
{20, 11},
}
otherValBytes := make([][]byte, len(otherVals))
for i, nms := range otherVals {
numbs := make([][]byte, len(nms))
for i, n := range nms {
numbs[i] = mkNum(n)
}
otherValBytes[i] = cmpbin.ConcatBytes(numbs...)
}
Convey("Test MultiIterator", t, func() {
s := newMemStore()
c := s.GetOrCreateCollection("zup1")
for _, row := range valBytes {
c.Set(row, []byte{})
}
c2 := s.GetOrCreateCollection("zup2")
for _, row := range otherValBytes {
c2.Set(row, []byte{})
}
c = s.Snapshot().GetCollection("zup1")
c2 = s.Snapshot().GetCollection("zup2")
Convey("can join the same collection twice", func() {
// get just the (1, *)
// starting at (1, 2) (i.e. >= 2)
// ending at (1, 4) (i.e. < 7)
defs := []*iterDefinition{
{c: c, prefix: mkNum(1), prefixLen: len(mkNum(1)), start: mkNum(2), end: mkNum(7)},
{c: c, prefix: mkNum(1), prefixLen: len(mkNum(1)), start: mkNum(2), end: mkNum(7)},
}
i := 1
So(multiIterate(defs, func(suffix []byte) error {
So(readNum(suffix), ShouldEqual, vals[i][1])
i++
return nil
}), shouldBeSuccessful)
So(i, ShouldEqual, 3)
})
Convey("can make empty iteration", func() {
// get just the (20, *) (doesn't exist)
defs := []*iterDefinition{
{c: c, prefix: mkNum(20)},
{c: c, prefix: mkNum(20)},
}
i := 0
So(multiIterate(defs, func(suffix []byte) error {
panic("never")
}), shouldBeSuccessful)
So(i, ShouldEqual, 0)
})
Convey("can join (other, val, val)", func() {
// 'other' must start with 20, 'vals' must start with 1
// no range constraints
defs := []*iterDefinition{
{c: c2, prefix: mkNum(20)},
{c: c, prefix: mkNum(1)},
{c: c, prefix: mkNum(1)},
}
expect := []int64{2, 4}
i := 0
So(multiIterate(defs, func(suffix []byte) error {
So(readNum(suffix), ShouldEqual, expect[i])
i++
return nil
}), shouldBeSuccessful)
})
Convey("Can stop early", func() {
defs := []*iterDefinition{
{c: c, prefix: mkNum(1), prefixLen: len(mkNum(1))},
{c: c, prefix: mkNum(1), prefixLen: len(mkNum(1))},
}
i := 0
So(multiIterate(defs, func(suffix []byte) error {
So(readNum(suffix), ShouldEqual, vals[i][1])
i++
return nil
}), shouldBeSuccessful)
So(i, ShouldEqual, 5)
i = 0
So(multiIterate(defs, func(suffix []byte) error {
So(readNum(suffix), ShouldEqual, vals[i][1])
i++
return datastore.Stop
}), shouldBeSuccessful)
So(i, ShouldEqual, 1)
})
})
}