| OLD | NEW |
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package bigtable | 5 package bigtable |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bytes" | 8 "bytes" |
| 9 "fmt" | |
| 10 "strconv" | 9 "strconv" |
| 11 "testing" | 10 "testing" |
| 12 "time" | |
| 13 | 11 |
| 14 "github.com/luci/gkvlite" | |
| 15 "github.com/luci/luci-go/common/config" | 12 "github.com/luci/luci-go/common/config" |
| 16 "github.com/luci/luci-go/common/data/recordio" | 13 "github.com/luci/luci-go/common/data/recordio" |
| 17 "github.com/luci/luci-go/logdog/common/storage" | 14 "github.com/luci/luci-go/logdog/common/storage" |
| 18 "github.com/luci/luci-go/logdog/common/types" | 15 "github.com/luci/luci-go/logdog/common/types" |
| 19 "golang.org/x/net/context" | 16 "golang.org/x/net/context" |
| 20 | 17 |
| 21 . "github.com/luci/luci-go/common/testing/assertions" | 18 . "github.com/luci/luci-go/common/testing/assertions" |
| 22 . "github.com/smartystreets/goconvey/convey" | 19 . "github.com/smartystreets/goconvey/convey" |
| 23 ) | 20 ) |
| 24 | 21 |
| 25 // btTableTest is an in-memory implementation of btTable interface for testing. | |
| 26 // | |
| 27 // This is a simple implementation; not an efficient one. | |
| 28 type btTableTest struct { | |
| 29 s *gkvlite.Store | |
| 30 c *gkvlite.Collection | |
| 31 | |
| 32 // err, if true, is the error immediately returned by functions. | |
| 33 err error | |
| 34 | |
| 35 // maxLogAge is the currently-configured maximum log age. | |
| 36 maxLogAge time.Duration | |
| 37 } | |
| 38 | |
| 39 func (t *btTableTest) close() { | |
| 40 if t.s != nil { | |
| 41 t.s.Close() | |
| 42 t.s = nil | |
| 43 } | |
| 44 } | |
| 45 | |
| 46 func (t *btTableTest) collection() *gkvlite.Collection { | |
| 47 if t.s == nil { | |
| 48 var err error | |
| 49 t.s, err = gkvlite.NewStore(nil) | |
| 50 if err != nil { | |
| 51 panic(err) | |
| 52 } | |
| 53 t.c = t.s.MakePrivateCollection(bytes.Compare) | |
| 54 } | |
| 55 return t.c | |
| 56 } | |
| 57 | |
| 58 func (t *btTableTest) putLogData(c context.Context, rk *rowKey, d []byte) error
{ | |
| 59 if t.err != nil { | |
| 60 return t.err | |
| 61 } | |
| 62 | |
| 63 // Record/count sanity check. | |
| 64 records, err := recordio.Split(d) | |
| 65 if err != nil { | |
| 66 return err | |
| 67 } | |
| 68 if int64(len(records)) != rk.count { | |
| 69 return fmt.Errorf("count mismatch (%d != %d)", len(records), rk.
count) | |
| 70 } | |
| 71 | |
| 72 enc := []byte(rk.encode()) | |
| 73 coll := t.collection() | |
| 74 if item, _ := coll.Get(enc); item != nil { | |
| 75 return storage.ErrExists | |
| 76 } | |
| 77 | |
| 78 clone := make([]byte, len(d)) | |
| 79 copy(clone, d) | |
| 80 if err := coll.Set(enc, clone); err != nil { | |
| 81 panic(err) | |
| 82 } | |
| 83 return nil | |
| 84 } | |
| 85 | |
| 86 func (t *btTableTest) getLogData(c context.Context, rk *rowKey, limit int, keysO
nly bool, cb btGetCallback) error { | |
| 87 if t.err != nil { | |
| 88 return t.err | |
| 89 } | |
| 90 | |
| 91 enc := []byte(rk.encode()) | |
| 92 prefix := rk.pathPrefix() | |
| 93 var ierr error | |
| 94 err := t.collection().VisitItemsAscend(enc, !keysOnly, func(i *gkvlite.I
tem) bool { | |
| 95 var drk *rowKey | |
| 96 drk, ierr = decodeRowKey(string(i.Key)) | |
| 97 if ierr != nil { | |
| 98 return false | |
| 99 } | |
| 100 if drk.pathPrefix() != prefix { | |
| 101 return false | |
| 102 } | |
| 103 | |
| 104 rowData := i.Val | |
| 105 if keysOnly { | |
| 106 rowData = nil | |
| 107 } | |
| 108 | |
| 109 if ierr = cb(drk, rowData); ierr != nil { | |
| 110 if ierr == errStop { | |
| 111 ierr = nil | |
| 112 } | |
| 113 return false | |
| 114 } | |
| 115 | |
| 116 if limit > 0 { | |
| 117 limit-- | |
| 118 if limit == 0 { | |
| 119 return false | |
| 120 } | |
| 121 } | |
| 122 | |
| 123 return true | |
| 124 }) | |
| 125 if err != nil { | |
| 126 panic(err) | |
| 127 } | |
| 128 return ierr | |
| 129 } | |
| 130 | |
| 131 func (t *btTableTest) setMaxLogAge(c context.Context, d time.Duration) error { | |
| 132 if t.err != nil { | |
| 133 return t.err | |
| 134 } | |
| 135 t.maxLogAge = d | |
| 136 return nil | |
| 137 } | |
| 138 | |
| 139 func (t *btTableTest) dataMap() map[string][]byte { | |
| 140 result := map[string][]byte{} | |
| 141 | |
| 142 err := t.collection().VisitItemsAscend([]byte(nil), true, func(i *gkvlit
e.Item) bool { | |
| 143 result[string(i.Key)] = i.Val | |
| 144 return true | |
| 145 }) | |
| 146 if err != nil { | |
| 147 panic(err) | |
| 148 } | |
| 149 return result | |
| 150 } | |
| 151 | |
| 152 func mustGetIndex(e *storage.Entry) types.MessageIndex { | 22 func mustGetIndex(e *storage.Entry) types.MessageIndex { |
| 153 idx, err := e.GetStreamIndex() | 23 idx, err := e.GetStreamIndex() |
| 154 if err != nil { | 24 if err != nil { |
| 155 panic(err) | 25 panic(err) |
| 156 } | 26 } |
| 157 return idx | 27 return idx |
| 158 } | 28 } |
| 159 | 29 |
| 160 func TestStorage(t *testing.T) { | 30 func TestStorage(t *testing.T) { |
| 161 t.Parallel() | 31 t.Parallel() |
| 162 | 32 |
| 163 Convey(`A BigTable storage instance bound to a testing BigTable instance
`, t, func() { | 33 Convey(`A BigTable storage instance bound to a testing BigTable instance
`, t, func() { |
| 164 » » bt := btTableTest{} | 34 » » s := NewMemoryInstance(context.Background(), Options{}) |
| 165 » » defer bt.close() | |
| 166 | |
| 167 » » s := newBTStorage(context.Background(), Options{ | |
| 168 » » » Project: "test-project", | |
| 169 » » » Instance: "test-instance", | |
| 170 » » » LogTable: "test-log-table", | |
| 171 » » }, nil, nil) | |
| 172 | |
| 173 » » s.raw = &bt | |
| 174 defer s.Close() | 35 defer s.Close() |
| 175 | 36 |
| 176 project := config.ProjectName("test-project") | 37 project := config.ProjectName("test-project") |
| 177 get := func(path string, index int, limit int, keysOnly bool) ([
]string, error) { | 38 get := func(path string, index int, limit int, keysOnly bool) ([
]string, error) { |
| 178 req := storage.GetRequest{ | 39 req := storage.GetRequest{ |
| 179 Project: project, | 40 Project: project, |
| 180 Path: types.StreamPath(path), | 41 Path: types.StreamPath(path), |
| 181 Index: types.MessageIndex(index), | 42 Index: types.MessageIndex(index), |
| 182 Limit: limit, | 43 Limit: limit, |
| 183 KeysOnly: keysOnly, | 44 KeysOnly: keysOnly, |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 223 panic(err) | 84 panic(err) |
| 224 } | 85 } |
| 225 } | 86 } |
| 226 | 87 |
| 227 return buf.Bytes() | 88 return buf.Bytes() |
| 228 } | 89 } |
| 229 | 90 |
| 230 Convey(`With an artificial maximum BigTable row size of two reco
rds`, func() { | 91 Convey(`With an artificial maximum BigTable row size of two reco
rds`, func() { |
| 231 // Artificially constrain row size. 4 = 2*{size/1, data/
1} RecordIO | 92 // Artificially constrain row size. 4 = 2*{size/1, data/
1} RecordIO |
| 232 // entries. | 93 // entries. |
| 233 » » » s.maxRowSize = 4 | 94 » » » s.SetMaxRowSize(4) |
| 234 | 95 |
| 235 Convey(`Will split row data that overflows the table int
o multiple rows.`, func() { | 96 Convey(`Will split row data that overflows the table int
o multiple rows.`, func() { |
| 236 So(put("A", 0, "0", "1", "2", "3"), ShouldBeNil) | 97 So(put("A", 0, "0", "1", "2", "3"), ShouldBeNil) |
| 237 | 98 |
| 238 » » » » So(bt.dataMap(), ShouldResemble, map[string][]by
te{ | 99 » » » » So(s.DataMap(), ShouldResemble, map[string][]byt
e{ |
| 239 ekey("A", 1, 2): records("0", "1"), | 100 ekey("A", 1, 2): records("0", "1"), |
| 240 ekey("A", 3, 2): records("2", "3"), | 101 ekey("A", 3, 2): records("2", "3"), |
| 241 }) | 102 }) |
| 242 }) | 103 }) |
| 243 | 104 |
| 244 Convey(`Loading a single row data beyond the maximum row
size will fail.`, func() { | 105 Convey(`Loading a single row data beyond the maximum row
size will fail.`, func() { |
| 245 So(put("A", 0, "0123"), ShouldErrLike, "single r
ow entry exceeds maximum size") | 106 So(put("A", 0, "0123"), ShouldErrLike, "single r
ow entry exceeds maximum size") |
| 246 }) | 107 }) |
| 247 }) | 108 }) |
| 248 | 109 |
| 249 Convey(`With row data: A{0, 1, 2, 3, 4}, B{10, 12, 13}`, func()
{ | 110 Convey(`With row data: A{0, 1, 2, 3, 4}, B{10, 12, 13}`, func()
{ |
| 250 So(put("A", 0, "0", "1", "2"), ShouldBeNil) | 111 So(put("A", 0, "0", "1", "2"), ShouldBeNil) |
| 251 So(put("A", 3, "3", "4"), ShouldBeNil) | 112 So(put("A", 3, "3", "4"), ShouldBeNil) |
| 252 So(put("B", 10, "10"), ShouldBeNil) | 113 So(put("B", 10, "10"), ShouldBeNil) |
| 253 So(put("B", 12, "12", "13"), ShouldBeNil) | 114 So(put("B", 12, "12", "13"), ShouldBeNil) |
| 254 | 115 |
| 255 Convey(`Testing "Put"...`, func() { | 116 Convey(`Testing "Put"...`, func() { |
| 256 Convey(`Loads the row data.`, func() { | 117 Convey(`Loads the row data.`, func() { |
| 257 » » » » » So(bt.dataMap(), ShouldResemble, map[str
ing][]byte{ | 118 » » » » » So(s.DataMap(), ShouldResemble, map[stri
ng][]byte{ |
| 258 ekey("A", 2, 3): records("0", "
1", "2"), | 119 ekey("A", 2, 3): records("0", "
1", "2"), |
| 259 ekey("A", 4, 2): records("3", "
4"), | 120 ekey("A", 4, 2): records("3", "
4"), |
| 260 ekey("B", 10, 1): records("10"), | 121 ekey("B", 10, 1): records("10"), |
| 261 ekey("B", 13, 2): records("12",
"13"), | 122 ekey("B", 13, 2): records("12",
"13"), |
| 262 }) | 123 }) |
| 263 }) | 124 }) |
| 264 }) | 125 }) |
| 265 | 126 |
| 266 Convey(`Testing "Get"...`, func() { | 127 Convey(`Testing "Get"...`, func() { |
| 267 Convey(`Can fetch the full row, "A".`, func() { | 128 Convey(`Can fetch the full row, "A".`, func() { |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 355 }) | 216 }) |
| 356 | 217 |
| 357 Convey(`A tail request for "INVALID" errors NOT
FOUND.`, func() { | 218 Convey(`A tail request for "INVALID" errors NOT
FOUND.`, func() { |
| 358 _, err := tail("INVALID") | 219 _, err := tail("INVALID") |
| 359 So(err, ShouldEqual, storage.ErrDoesNotE
xist) | 220 So(err, ShouldEqual, storage.ErrDoesNotE
xist) |
| 360 }) | 221 }) |
| 361 }) | 222 }) |
| 362 }) | 223 }) |
| 363 }) | 224 }) |
| 364 } | 225 } |
| OLD | NEW |