| Index: go/src/infra/gae/libs/gae/memory/plist_test.go
|
| diff --git a/go/src/infra/gae/libs/gae/memory/plist_test.go b/go/src/infra/gae/libs/gae/memory/plist_test.go
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..592b60061d58def873b3ed8c46c02903d1af7517
|
| --- /dev/null
|
| +++ b/go/src/infra/gae/libs/gae/memory/plist_test.go
|
| @@ -0,0 +1,392 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package memory
|
| +
|
| +import (
|
| + "infra/gae/libs/gae"
|
| + "testing"
|
| + "time"
|
| +
|
| + "github.com/luci/gkvlite"
|
| + . "github.com/smartystreets/goconvey/convey"
|
| +)
|
| +
|
| +func init() {
|
| + indexCreationDeterministic = true
|
| +}
|
| +
|
| +var fakeKey = key("knd", 10, key("parentKind", "sid"))
|
| +
|
| +func TestCollated(t *testing.T) {
|
| + t.Parallel()
|
| +
|
| + Convey("TestCollated", t, func() {
|
| + Convey("nil list", func() {
|
| + pm := (gae.DSPropertyMap)(nil)
|
| + sip := partiallySerialize(pm)
|
| + So(sip, ShouldBeNil)
|
| +
|
| + Convey("nil collated", func() {
|
| + Convey("defaultIndicies", func() {
|
| + idxs := defaultIndicies("knd", pm)
|
| + So(len(idxs), ShouldEqual, 1)
|
| + So(idxs[0].String(), ShouldEqual, "B:knd")
|
| + })
|
| + Convey("indexEntries", func() {
|
| + s := sip.indexEntries(fakeKey, defaultIndicies("knd", pm))
|
| + numItems, _ := s.GetCollection("idx").GetTotals()
|
| + So(numItems, ShouldEqual, 1)
|
| + itm := s.GetCollection("idx").MinItem(false)
|
| + So(itm.Key, ShouldResemble, cat(indx("knd")))
|
| + numItems, _ = s.GetCollection("idx:ns:" + string(itm.Key)).GetTotals()
|
| + So(numItems, ShouldEqual, 1)
|
| + })
|
| + })
|
| + })
|
| +
|
| + Convey("list", func() {
|
| + pm := gae.DSPropertyMap{
|
| + "wat": {prop("thing", true), prop("hat"), prop(100)},
|
| + "nerd": {prop(103.7)},
|
| + "spaz": {prop(false, true)},
|
| + }
|
| + sip := partiallySerialize(pm)
|
| + So(len(sip), ShouldEqual, 2)
|
| +
|
| + Convey("single collated", func() {
|
| + Convey("indexableMap", func() {
|
| + So(sip, ShouldResemble, serializedIndexablePmap{
|
| + "wat": {
|
| + cat(gae.DSPTInt, 100),
|
| + cat(gae.DSPTString, "hat"),
|
| + // 'thing' is skipped, because it's not NoIndex
|
| + },
|
| + "nerd": {
|
| + cat(gae.DSPTFloat, 103.7),
|
| + },
|
| + })
|
| + })
|
| + Convey("defaultIndicies", func() {
|
| + idxs := defaultIndicies("knd", pm)
|
| + So(len(idxs), ShouldEqual, 5)
|
| + So(idxs[0].String(), ShouldEqual, "B:knd")
|
| + So(idxs[1].String(), ShouldEqual, "B:knd/-nerd")
|
| + So(idxs[2].String(), ShouldEqual, "B:knd/-wat")
|
| + So(idxs[3].String(), ShouldEqual, "B:knd/nerd")
|
| + So(idxs[4].String(), ShouldEqual, "B:knd/wat")
|
| + })
|
| + })
|
| + })
|
| + })
|
| +}
|
| +
|
| +var rgenComplexTime = time.Date(
|
| + 1986, time.October, 26, 1, 20, 00, 00, time.UTC)
|
| +var rgenComplexKey = key("kind", "id")
|
| +
|
| +var rowGenTestCases = []struct {
|
| + name string
|
| + pmap gae.DSPropertyMap
|
| + withBuiltin bool
|
| + idxs []*qIndex
|
| +
|
| + // These are checked in TestIndexRowGen. nil to skip test case.
|
| + expected []serializedPvals
|
| +
|
| + // just the collections you want to assert. These are checked in
|
| + // TestIndexEntries. nil to skip test case.
|
| + collections map[string][]kv
|
| +}{
|
| + {
|
| + name: "simple including builtins",
|
| + pmap: gae.DSPropertyMap{
|
| + "wat": {prop("thing", true), prop("hat"), prop(100)},
|
| + "nerd": {prop(103.7)},
|
| + "spaz": {prop(false, true)},
|
| + },
|
| + withBuiltin: true,
|
| + idxs: []*qIndex{
|
| + indx("knd", "-wat", "nerd"),
|
| + },
|
| + expected: []serializedPvals{
|
| + {{}}, // B:knd
|
| + {icat(gae.DSPTFloat, 103.7)}, // B:knd/-nerd
|
| + {icat(gae.DSPTString, "hat"), icat(gae.DSPTInt, 100)}, // B:knd/-wat
|
| + {cat(gae.DSPTFloat, 103.7)}, // B:knd/nerd
|
| + {cat(gae.DSPTInt, 100), cat(gae.DSPTString, "hat")}, // B:knd/wat
|
| + { // B:knd/-wat/nerd
|
| + cat(icat(gae.DSPTString, "hat"), cat(gae.DSPTFloat, 103.7)),
|
| + cat(icat(gae.DSPTInt, 100), cat(gae.DSPTFloat, 103.7)),
|
| + },
|
| + },
|
| + collections: map[string][]kv{
|
| + "idx": {
|
| + // 0 == builtin, 1 == complex
|
| + {cat(byte(0), "knd", byte(1), 0), []byte{}},
|
| + {cat(byte(0), "knd", byte(1), 1, byte(0), "nerd"), []byte{}},
|
| + {cat(byte(0), "knd", byte(1), 1, byte(0), "wat"), []byte{}},
|
| + {cat(byte(0), "knd", byte(1), 1, byte(1), "nerd"), []byte{}},
|
| + {cat(byte(0), "knd", byte(1), 1, byte(1), "wat"), []byte{}},
|
| + {cat(byte(1), "knd", byte(1), 2, byte(1), "wat", byte(0), "nerd"), []byte{}},
|
| + },
|
| + "idx:ns:" + sat(indx("knd")): {
|
| + {cat(fakeKey), []byte{}},
|
| + },
|
| + "idx:ns:" + sat(indx("knd", "wat")): {
|
| + {cat(gae.DSPTInt, 100, fakeKey), []byte{}},
|
| + {cat(gae.DSPTString, "hat", fakeKey), cat(gae.DSPTInt, 100)},
|
| + },
|
| + "idx:ns:" + sat(indx("knd", "-wat")): {
|
| + {cat(icat(gae.DSPTString, "hat"), fakeKey), []byte{}},
|
| + {cat(icat(gae.DSPTInt, 100), fakeKey), icat(gae.DSPTString, "hat")},
|
| + },
|
| + },
|
| + },
|
| + {
|
| + name: "complex",
|
| + pmap: gae.DSPropertyMap{
|
| + "yerp": {prop("hat"), prop(73.9)},
|
| + "wat": {
|
| + prop(rgenComplexTime),
|
| + prop(gae.DSByteString("value")),
|
| + prop(rgenComplexKey)},
|
| + "spaz": {prop(nil), prop(false), prop(true)},
|
| + },
|
| + idxs: []*qIndex{
|
| + indx("knd", "-wat", "nerd", "spaz"), // doesn't match, so empty
|
| + indx("knd", "yerp", "-wat", "spaz"),
|
| + },
|
| + expected: []serializedPvals{
|
| + {}, // C:knd/-wat/nerd/spaz, no match
|
| + { // C:knd/yerp/-wat/spaz
|
| + // thank goodness the binary serialization only happens 1/val in the
|
| + // real code :).
|
| + cat(cat(gae.DSPTString, "hat"), icat(gae.DSPTKey, rgenComplexKey), cat(gae.DSPTNull)),
|
| + cat(cat(gae.DSPTString, "hat"), icat(gae.DSPTKey, rgenComplexKey), cat(gae.DSPTBoolFalse)),
|
| + cat(cat(gae.DSPTString, "hat"), icat(gae.DSPTKey, rgenComplexKey), cat(gae.DSPTBoolTrue)),
|
| + cat(cat(gae.DSPTString, "hat"), icat(gae.DSPTBytes, "value"), cat(gae.DSPTNull)),
|
| + cat(cat(gae.DSPTString, "hat"), icat(gae.DSPTBytes, "value"), cat(gae.DSPTBoolFalse)),
|
| + cat(cat(gae.DSPTString, "hat"), icat(gae.DSPTBytes, "value"), cat(gae.DSPTBoolTrue)),
|
| + cat(cat(gae.DSPTString, "hat"), icat(gae.DSPTTime, rgenComplexTime), cat(gae.DSPTNull)),
|
| + cat(cat(gae.DSPTString, "hat"), icat(gae.DSPTTime, rgenComplexTime), cat(gae.DSPTBoolFalse)),
|
| + cat(cat(gae.DSPTString, "hat"), icat(gae.DSPTTime, rgenComplexTime), cat(gae.DSPTBoolTrue)),
|
| +
|
| + cat(cat(gae.DSPTFloat, 73.9), icat(gae.DSPTKey, rgenComplexKey), cat(gae.DSPTNull)),
|
| + cat(cat(gae.DSPTFloat, 73.9), icat(gae.DSPTKey, rgenComplexKey), cat(gae.DSPTBoolFalse)),
|
| + cat(cat(gae.DSPTFloat, 73.9), icat(gae.DSPTKey, rgenComplexKey), cat(gae.DSPTBoolTrue)),
|
| + cat(cat(gae.DSPTFloat, 73.9), icat(gae.DSPTBytes, "value"), cat(gae.DSPTNull)),
|
| + cat(cat(gae.DSPTFloat, 73.9), icat(gae.DSPTBytes, "value"), cat(gae.DSPTBoolFalse)),
|
| + cat(cat(gae.DSPTFloat, 73.9), icat(gae.DSPTBytes, "value"), cat(gae.DSPTBoolTrue)),
|
| + cat(cat(gae.DSPTFloat, 73.9), icat(gae.DSPTTime, rgenComplexTime), cat(gae.DSPTNull)),
|
| + cat(cat(gae.DSPTFloat, 73.9), icat(gae.DSPTTime, rgenComplexTime), cat(gae.DSPTBoolFalse)),
|
| + cat(cat(gae.DSPTFloat, 73.9), icat(gae.DSPTTime, rgenComplexTime), cat(gae.DSPTBoolTrue)),
|
| + },
|
| + },
|
| + },
|
| + {
|
| + name: "ancestor",
|
| + pmap: gae.DSPropertyMap{
|
| + "wat": {prop("sup")},
|
| + },
|
| + idxs: []*qIndex{
|
| + indx("knd!", "wat"),
|
| + },
|
| + collections: map[string][]kv{
|
| + "idx:ns:" + sat(indx("knd!", "wat")): {
|
| + {cat(fakeKey.Parent(), gae.DSPTString, "sup", fakeKey), []byte{}},
|
| + {cat(fakeKey, gae.DSPTString, "sup", fakeKey), []byte{}},
|
| + },
|
| + },
|
| + },
|
| +}
|
| +
|
| +func TestIndexRowGen(t *testing.T) {
|
| + t.Parallel()
|
| +
|
| + Convey("Test Index Row Generation", t, func() {
|
| + for _, tc := range rowGenTestCases {
|
| + if tc.expected == nil {
|
| + Convey(tc.name, nil) // shows up as 'skipped'
|
| + continue
|
| + }
|
| +
|
| + Convey(tc.name, func() {
|
| + mvals := partiallySerialize(tc.pmap)
|
| + idxs := []*qIndex(nil)
|
| + if tc.withBuiltin {
|
| + idxs = append(defaultIndicies("coolKind", tc.pmap), tc.idxs...)
|
| + } else {
|
| + idxs = tc.idxs
|
| + }
|
| +
|
| + m := matcher{}
|
| + for i, idx := range idxs {
|
| + Convey(idx.String(), func() {
|
| + iGen, ok := m.match(idx, mvals)
|
| + if len(tc.expected[i]) > 0 {
|
| + So(ok, ShouldBeTrue)
|
| + j := 0
|
| + iGen.permute(func(row []byte) {
|
| + So([]byte(row), ShouldResemble, tc.expected[i][j])
|
| + j++
|
| + })
|
| + So(j, ShouldEqual, len(tc.expected[i]))
|
| + } else {
|
| + So(ok, ShouldBeFalse)
|
| + }
|
| + })
|
| + }
|
| + })
|
| + }
|
| + })
|
| +}
|
| +
|
| +func TestIndexEntries(t *testing.T) {
|
| + t.Parallel()
|
| +
|
| + Convey("Test indexEntriesWithBuiltins", t, func() {
|
| + for _, tc := range rowGenTestCases {
|
| + if tc.collections == nil {
|
| + Convey(tc.name, nil) // shows up as 'skipped'
|
| + continue
|
| + }
|
| +
|
| + Convey(tc.name, func() {
|
| + store := (*memStore)(nil)
|
| + if tc.withBuiltin {
|
| + store = indexEntriesWithBuiltins(fakeKey, tc.pmap, tc.idxs)
|
| + } else {
|
| + store = partiallySerialize(tc.pmap).indexEntries(fakeKey, tc.idxs)
|
| + }
|
| + for colName, vals := range tc.collections {
|
| + i := 0
|
| + store.GetCollection(colName).VisitItemsAscend(nil, true, func(itm *gkvlite.Item) bool {
|
| + So(itm.Key, ShouldResemble, vals[i].k)
|
| + So(itm.Val, ShouldResemble, vals[i].v)
|
| + i++
|
| + return true
|
| + })
|
| + So(i, ShouldEqual, len(vals))
|
| + }
|
| + })
|
| + }
|
| + })
|
| +}
|
| +
|
| +type dumbItem struct {
|
| + key gae.DSKey
|
| + props gae.DSPropertyMap
|
| +}
|
| +
|
| +var updateIndiciesTests = []struct {
|
| + name string
|
| + idxs []*qIndex
|
| + data []dumbItem
|
| + expected map[string][][]byte
|
| +}{
|
| + {
|
| + name: "basic",
|
| + data: []dumbItem{
|
| + {key("knd", 1), gae.DSPropertyMap{
|
| + "wat": {prop(10)},
|
| + "yerp": {prop(10)}},
|
| + },
|
| + {key("knd", 10), gae.DSPropertyMap{
|
| + "wat": {prop(1)},
|
| + "yerp": {prop(200)}},
|
| + },
|
| + {key("knd", 1), gae.DSPropertyMap{
|
| + "wat": {prop(10)},
|
| + "yerp": {prop(202)}},
|
| + },
|
| + },
|
| + expected: map[string][][]byte{
|
| + "idx:ns:" + sat(indx("knd", "wat")): {
|
| + cat(gae.DSPTInt, 1, key("knd", 10)),
|
| + cat(gae.DSPTInt, 10, key("knd", 1)),
|
| + },
|
| + "idx:ns:" + sat(indx("knd", "-wat")): {
|
| + cat(icat(gae.DSPTInt, 10), key("knd", 1)),
|
| + cat(icat(gae.DSPTInt, 1), key("knd", 10)),
|
| + },
|
| + "idx:ns:" + sat(indx("knd", "yerp")): {
|
| + cat(gae.DSPTInt, 200, key("knd", 10)),
|
| + cat(gae.DSPTInt, 202, key("knd", 1)),
|
| + },
|
| + },
|
| + },
|
| + {
|
| + name: "compound",
|
| + idxs: []*qIndex{indx("knd", "yerp", "-wat")},
|
| + data: []dumbItem{
|
| + {key("knd", 1), gae.DSPropertyMap{
|
| + "wat": {prop(10)},
|
| + "yerp": {prop(100)}},
|
| + },
|
| + {key("knd", 10), gae.DSPropertyMap{
|
| + "wat": {prop(1)},
|
| + "yerp": {prop(200)}},
|
| + },
|
| + {key("knd", 11), gae.DSPropertyMap{
|
| + "wat": {prop(20)},
|
| + "yerp": {prop(200)}},
|
| + },
|
| + {key("knd", 14), gae.DSPropertyMap{
|
| + "wat": {prop(20)},
|
| + "yerp": {prop(200)}},
|
| + },
|
| + {key("knd", 1), gae.DSPropertyMap{
|
| + "wat": {prop(10)},
|
| + "yerp": {prop(202)}},
|
| + },
|
| + },
|
| + expected: map[string][][]byte{
|
| + "idx:ns:" + sat(indx("knd", "yerp", "-wat")): {
|
| + cat(gae.DSPTInt, 200, icat(gae.DSPTInt, 20), key("knd", 11)),
|
| + cat(gae.DSPTInt, 200, icat(gae.DSPTInt, 20), key("knd", 14)),
|
| + cat(gae.DSPTInt, 200, icat(gae.DSPTInt, 1), key("knd", 10)),
|
| + cat(gae.DSPTInt, 202, icat(gae.DSPTInt, 10), key("knd", 1)),
|
| + },
|
| + },
|
| + },
|
| +}
|
| +
|
| +func TestUpdateIndicies(t *testing.T) {
|
| + t.Parallel()
|
| +
|
| + Convey("Test updateIndicies", t, func() {
|
| + for _, tc := range updateIndiciesTests {
|
| + Convey(tc.name, func() {
|
| + store := newMemStore()
|
| + idxColl := store.SetCollection("idx", nil)
|
| + for _, i := range tc.idxs {
|
| + idxColl.Set(cat(i), []byte{})
|
| + }
|
| +
|
| + tmpLoader := map[string]gae.DSPropertyMap{}
|
| + for _, itm := range tc.data {
|
| + ks := itm.key.String()
|
| + prev := tmpLoader[ks]
|
| + err := updateIndicies(store, itm.key, prev, itm.props)
|
| + So(err, ShouldBeNil)
|
| + tmpLoader[ks] = itm.props
|
| + }
|
| + tmpLoader = nil
|
| +
|
| + for colName, data := range tc.expected {
|
| + coll := store.GetCollection(colName)
|
| + So(coll, ShouldNotBeNil)
|
| + i := 0
|
| + coll.VisitItemsAscend(nil, false, func(itm *gkvlite.Item) bool {
|
| + So(data[i], ShouldResemble, itm.Key)
|
| + i++
|
| + return true
|
| + })
|
| + So(i, ShouldEqual, len(data))
|
| + }
|
| + })
|
| + }
|
| + })
|
| +}
|
|
|