| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package bigtable | 5 package bigtable |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bytes" | 8 "bytes" |
| 9 "errors" | 9 "errors" |
| 10 "testing" | 10 "testing" |
| 11 "time" | 11 "time" |
| 12 | 12 |
| 13 "github.com/luci/gkvlite" | 13 "github.com/luci/gkvlite" |
| 14 "github.com/luci/luci-go/common/logdog/types" | 14 "github.com/luci/luci-go/common/logdog/types" |
| 15 "github.com/luci/luci-go/common/recordio" |
| 15 "github.com/luci/luci-go/server/logdog/storage" | 16 "github.com/luci/luci-go/server/logdog/storage" |
| 16 "golang.org/x/net/context" | 17 "golang.org/x/net/context" |
| 17 "google.golang.org/cloud/bigtable" | 18 "google.golang.org/cloud/bigtable" |
| 18 | 19 |
| 20 . "github.com/luci/luci-go/common/testing/assertions" |
| 19 . "github.com/smartystreets/goconvey/convey" | 21 . "github.com/smartystreets/goconvey/convey" |
| 20 ) | 22 ) |
| 21 | 23 |
| 22 // btTableTest is an in-memory implementation of btTable interface for testing. | 24 // btTableTest is an in-memory implementation of btTable interface for testing. |
| 23 // | 25 // |
| 24 // This is a simple implementation; not an efficient one. | 26 // This is a simple implementation; not an efficient one. |
| 25 type btTableTest struct { | 27 type btTableTest struct { |
| 26 s *gkvlite.Store | 28 s *gkvlite.Store |
| 27 c *gkvlite.Collection | 29 c *gkvlite.Collection |
| 28 | 30 |
| (...skipping 27 matching lines...) Expand all Loading... |
| 56 if t.err != nil { | 58 if t.err != nil { |
| 57 return t.err | 59 return t.err |
| 58 } | 60 } |
| 59 | 61 |
| 60 enc := []byte(rk.encode()) | 62 enc := []byte(rk.encode()) |
| 61 coll := t.collection() | 63 coll := t.collection() |
| 62 if item, _ := coll.Get(enc); item != nil { | 64 if item, _ := coll.Get(enc); item != nil { |
| 63 return storage.ErrExists | 65 return storage.ErrExists |
| 64 } | 66 } |
| 65 | 67 |
| 66 » if err := coll.Set(enc, d); err != nil { | 68 » clone := make([]byte, len(d)) |
| 69 » copy(clone, d) |
| 70 » if err := coll.Set(enc, clone); err != nil { |
| 67 panic(err) | 71 panic(err) |
| 68 } | 72 } |
| 69 return nil | 73 return nil |
| 70 } | 74 } |
| 71 | 75 |
| 72 func (t *btTableTest) getLogData(c context.Context, rk *rowKey, limit int, keysO
nly bool, cb btGetCallback) error { | 76 func (t *btTableTest) getLogData(c context.Context, rk *rowKey, limit int, keysO
nly bool, cb btGetCallback) error { |
| 73 if t.err != nil { | 77 if t.err != nil { |
| 74 return t.err | 78 return t.err |
| 75 } | 79 } |
| 76 | 80 |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 130 return result | 134 return result |
| 131 } | 135 } |
| 132 | 136 |
| 133 func TestStorage(t *testing.T) { | 137 func TestStorage(t *testing.T) { |
| 134 t.Parallel() | 138 t.Parallel() |
| 135 | 139 |
| 136 Convey(`A BigTable storage instance bound to a testing BigTable instance
`, t, func() { | 140 Convey(`A BigTable storage instance bound to a testing BigTable instance
`, t, func() { |
| 137 bt := btTableTest{} | 141 bt := btTableTest{} |
| 138 defer bt.close() | 142 defer bt.close() |
| 139 | 143 |
| 140 » » s := btStorage{ | 144 » » s := newBTStorage(context.Background(), Options{ |
| 141 » » » Options: &Options{ | 145 » » » Project: "test-project", |
| 142 » » » » Project: "test-project", | 146 » » » Zone: "test-zone", |
| 143 » » » » Zone: "test-zone", | 147 » » » Cluster: "test-cluster", |
| 144 » » » » Cluster: "test-cluster", | 148 » » » LogTable: "test-log-table", |
| 145 » » » » LogTable: "test-log-table", | 149 » » }, nil, nil) |
| 146 » » » }, | 150 |
| 147 » » » ctx: context.Background(), | 151 » » s.raw = &bt |
| 148 » » » client: nil, | 152 » » defer s.Close() |
| 149 » » » table: &bt, | |
| 150 » » } | |
| 151 | 153 |
| 152 get := func(path string, index int, limit int) ([]string, error)
{ | 154 get := func(path string, index int, limit int) ([]string, error)
{ |
| 153 req := storage.GetRequest{ | 155 req := storage.GetRequest{ |
| 154 Path: types.StreamPath(path), | 156 Path: types.StreamPath(path), |
| 155 Index: types.MessageIndex(index), | 157 Index: types.MessageIndex(index), |
| 156 Limit: limit, | 158 Limit: limit, |
| 157 } | 159 } |
| 158 got := []string{} | 160 got := []string{} |
| 159 » » » err := s.Get(&req, func(idx types.MessageIndex, d []byte
) bool { | 161 » » » err := s.Get(req, func(idx types.MessageIndex, d []byte)
bool { |
| 160 got = append(got, string(d)) | 162 got = append(got, string(d)) |
| 161 return true | 163 return true |
| 162 }) | 164 }) |
| 163 return got, err | 165 return got, err |
| 164 } | 166 } |
| 165 | 167 |
| 166 » » put := func(path string, index int, d string) error { | 168 » » put := func(path string, index int, d ...string) error { |
| 167 » » » return s.Put(&storage.PutRequest{ | 169 » » » data := make([][]byte, len(d)) |
| 168 » » » » Path: types.StreamPath(path), | 170 » » » for i, v := range d { |
| 169 » » » » Index: types.MessageIndex(index), | 171 » » » » data[i] = []byte(v) |
| 170 » » » » Value: []byte(d), | 172 » » » } |
| 173 |
| 174 » » » return s.Put(storage.PutRequest{ |
| 175 » » » » Path: types.StreamPath(path), |
| 176 » » » » Index: types.MessageIndex(index), |
| 177 » » » » Values: data, |
| 171 }) | 178 }) |
| 172 } | 179 } |
| 173 | 180 |
| 174 » » Convey(`With row data: A{0, 1, 2}, B{10, 12, 13}`, func() { | 181 » » ekey := func(p string, v int64) string { |
| 175 » » » So(put("A", 0, "0"), ShouldBeNil) | 182 » » » return newRowKey(p, v).encode() |
| 176 » » » So(put("A", 1, "1"), ShouldBeNil) | 183 » » } |
| 177 » » » So(put("A", 2, "2"), ShouldBeNil) | 184 » » records := func(s ...string) []byte { |
| 185 » » » buf := bytes.Buffer{} |
| 186 » » » w := recordio.NewWriter(&buf) |
| 187 |
| 188 » » » for _, v := range s { |
| 189 » » » » if _, err := w.Write([]byte(v)); err != nil { |
| 190 » » » » » panic(err) |
| 191 » » » » } |
| 192 » » » » if err := w.Flush(); err != nil { |
| 193 » » » » » panic(err) |
| 194 » » » » } |
| 195 » » » } |
| 196 |
| 197 » » » return buf.Bytes() |
| 198 » » } |
| 199 |
| 200 » » Convey(`With an artificial maximum BigTable row size of two reco
rds`, func() { |
| 201 » » » // Artificially constrain row size. 4 = 2*{size/1, data/
1} RecordIO |
| 202 » » » // entries. |
| 203 » » » s.maxRowSize = 4 |
| 204 |
| 205 » » » Convey(`Will split row data that overflows the table int
o multiple rows.`, func() { |
| 206 » » » » So(put("A", 0, "0", "1", "2", "3"), ShouldBeNil) |
| 207 |
| 208 » » » » So(bt.dataMap(), ShouldResemble, map[string][]by
te{ |
| 209 » » » » » ekey("A", 1): records("0", "1"), |
| 210 » » » » » ekey("A", 3): records("2", "3"), |
| 211 » » » » }) |
| 212 » » » }) |
| 213 |
| 214 » » » Convey(`Loading a single row data beyond the maximum row
size will fail.`, func() { |
| 215 » » » » So(put("A", 0, "0123"), ShouldErrLike, "single r
ow entry exceeds maximum size") |
| 216 » » » }) |
| 217 » » }) |
| 218 |
| 219 » » Convey(`With row data: A{0, 1, 2, 3, 4}, B{10, 12, 13}`, func()
{ |
| 220 » » » So(put("A", 0, "0", "1", "2"), ShouldBeNil) |
| 221 » » » So(put("A", 3, "3", "4"), ShouldBeNil) |
| 178 So(put("B", 10, "10"), ShouldBeNil) | 222 So(put("B", 10, "10"), ShouldBeNil) |
| 179 » » » So(put("B", 12, "12"), ShouldBeNil) | 223 » » » So(put("B", 12, "12", "13"), ShouldBeNil) |
| 180 » » » So(put("B", 13, "13"), ShouldBeNil) | |
| 181 | |
| 182 » » » ekey := func(p string, v int64) string { | |
| 183 » » » » return newRowKey(p, v).encode() | |
| 184 » » » } | |
| 185 | 224 |
| 186 Convey(`Testing "Put"...`, func() { | 225 Convey(`Testing "Put"...`, func() { |
| 187 Convey(`Loads the row data.`, func() { | 226 Convey(`Loads the row data.`, func() { |
| 188 So(bt.dataMap(), ShouldResemble, map[str
ing][]byte{ | 227 So(bt.dataMap(), ShouldResemble, map[str
ing][]byte{ |
| 189 » » » » » » ekey("A", 0): []byte("0"), | 228 » » » » » » ekey("A", 2): records("0", "1",
"2"), |
| 190 » » » » » » ekey("A", 1): []byte("1"), | 229 » » » » » » ekey("A", 4): records("3", "4")
, |
| 191 » » » » » » ekey("A", 2): []byte("2"), | 230 » » » » » » ekey("B", 10): records("10"), |
| 192 » » » » » » ekey("B", 10): []byte("10"), | 231 » » » » » » ekey("B", 13): records("12", "13
"), |
| 193 » » » » » » ekey("B", 12): []byte("12"), | |
| 194 » » » » » » ekey("B", 13): []byte("13"), | |
| 195 }) | 232 }) |
| 196 }) | 233 }) |
| 197 }) | 234 }) |
| 198 | 235 |
| 199 Convey(`Testing "Get"...`, func() { | 236 Convey(`Testing "Get"...`, func() { |
| 200 Convey(`Can fetch the full row, "A".`, func() { | 237 Convey(`Can fetch the full row, "A".`, func() { |
| 201 got, err := get("A", 0, 0) | 238 got, err := get("A", 0, 0) |
| 202 So(err, ShouldBeNil) | 239 So(err, ShouldBeNil) |
| 203 » » » » » So(got, ShouldResemble, []string{"0", "1
", "2"}) | 240 » » » » » So(got, ShouldResemble, []string{"0", "1
", "2", "3", "4"}) |
| 241 » » » » }) |
| 242 |
| 243 » » » » Convey(`Will fetch A{1, 2, 3, 4} with when index
=1.`, func() { |
| 244 » » » » » got, err := get("A", 1, 0) |
| 245 » » » » » So(err, ShouldBeNil) |
| 246 » » » » » So(got, ShouldResemble, []string{"1", "2
", "3", "4"}) |
| 204 }) | 247 }) |
| 205 | 248 |
| 206 Convey(`Will fetch A{1, 2} with when index=1 and
limit=2.`, func() { | 249 Convey(`Will fetch A{1, 2} with when index=1 and
limit=2.`, func() { |
| 207 got, err := get("A", 1, 2) | 250 got, err := get("A", 1, 2) |
| 208 So(err, ShouldBeNil) | 251 So(err, ShouldBeNil) |
| 209 So(got, ShouldResemble, []string{"1", "2
"}) | 252 So(got, ShouldResemble, []string{"1", "2
"}) |
| 210 }) | 253 }) |
| 211 | 254 |
| 212 Convey(`Will fetch B{10, 12, 13} for B.`, func()
{ | 255 Convey(`Will fetch B{10, 12, 13} for B.`, func()
{ |
| 213 got, err := get("B", 0, 0) | 256 got, err := get("B", 0, 0) |
| (...skipping 13 matching lines...) Expand all Loading... |
| 227 So(got, ShouldResemble, []string{}) | 270 So(got, ShouldResemble, []string{}) |
| 228 }) | 271 }) |
| 229 }) | 272 }) |
| 230 | 273 |
| 231 Convey(`Testing "Tail"...`, func() { | 274 Convey(`Testing "Tail"...`, func() { |
| 232 tail := func(path string) (string, error) { | 275 tail := func(path string) (string, error) { |
| 233 got, _, err := s.Tail(types.StreamPath(p
ath)) | 276 got, _, err := s.Tail(types.StreamPath(p
ath)) |
| 234 return string(got), err | 277 return string(got), err |
| 235 } | 278 } |
| 236 | 279 |
| 237 » » » » Convey(`A tail request for "A" returns A{2, 1, 0
}.`, func() { | 280 » » » » Convey(`A tail request for "A" returns A{4}.`, f
unc() { |
| 238 got, err := tail("A") | 281 got, err := tail("A") |
| 239 So(err, ShouldBeNil) | 282 So(err, ShouldBeNil) |
| 240 » » » » » So(got, ShouldEqual, "2") | 283 » » » » » So(got, ShouldEqual, "4") |
| 241 }) | 284 }) |
| 242 | 285 |
| 243 Convey(`A tail request for "B" returns B{13}.`,
func() { | 286 Convey(`A tail request for "B" returns B{13}.`,
func() { |
| 244 got, err := tail("B") | 287 got, err := tail("B") |
| 245 So(err, ShouldBeNil) | 288 So(err, ShouldBeNil) |
| 246 So(got, ShouldEqual, "13") | 289 So(got, ShouldEqual, "13") |
| 247 }) | 290 }) |
| 248 | 291 |
| 249 Convey(`A tail request for "INVALID" errors NOT
FOUND.`, func() { | 292 Convey(`A tail request for "INVALID" errors NOT
FOUND.`, func() { |
| 250 _, err := tail("INVALID") | 293 _, err := tail("INVALID") |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 296 }) | 339 }) |
| 297 | 340 |
| 298 Convey(`With return an error if the configuration fails
to apply.`, func() { | 341 Convey(`With return an error if the configuration fails
to apply.`, func() { |
| 299 bt.err = errors.New("test error") | 342 bt.err = errors.New("test error") |
| 300 | 343 |
| 301 So(s.Config(cfg), ShouldEqual, bt.err) | 344 So(s.Config(cfg), ShouldEqual, bt.err) |
| 302 }) | 345 }) |
| 303 }) | 346 }) |
| 304 }) | 347 }) |
| 305 } | 348 } |
| OLD | NEW |