Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(174)

Side by Side Diff: filter/txnBuf/txnbuf_test.go

Issue 1309803004: Add transaction buffer filter. (Closed) Base URL: https://github.com/luci/gae.git@add_query_support
Patch Set: Fix builtin+ancestor+multi-eq+multiIterator bug, add more txnBuf test Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 package txnBuf
6
7 import (
8 "bytes"
9 "fmt"
10 "math/rand"
11 "testing"
12
13 "github.com/luci/gae/filter/count"
14 "github.com/luci/gae/impl/memory"
15 "github.com/luci/gae/service/datastore"
16 "github.com/luci/luci-go/common/cmpbin"
17 "github.com/luci/luci-go/common/errors"
18 . "github.com/luci/luci-go/common/testing/assertions"
19 . "github.com/smartystreets/goconvey/convey"
20 "golang.org/x/net/context"
21 )
22
23 type Foo struct {
24 ID int64 `gae:"$id"`
25 Parent *datastore.Key `gae:"$parent"`
26
27 Value []int64
28 ValueNI []byte `gae:",noindex"`
29 }
30
31 func toIntSlice(stuff []interface{}) []int64 {
32 vals, ok := stuff[0].([]int64)
33 if !ok {
34 vals = make([]int64, len(stuff))
35 for i := range vals {
36 vals[i] = int64(stuff[i].(int))
37 }
38 }
39 return vals
40 }
41
42 func toInt64(thing interface{}) int64 {
43 switch x := thing.(type) {
44 case int:
45 return int64(x)
46 case int64:
47 return x
48 default:
49 panic(fmt.Errorf("wat r it? %v", x))
50 }
51 }
52
53 func fooShouldHave(ds datastore.Interface) func(interface{}, ...interface{}) str ing {
54 return func(id interface{}, values ...interface{}) string {
55 f := &Foo{ID: toInt64(id)}
56 err := ds.Get(f)
57 if len(values) == 0 {
58 return ShouldEqual(err, datastore.ErrNoSuchEntity)
59 }
60
61 ret := ShouldBeNil(err)
62 if ret == "" {
63 if data, ok := values[0].([]byte); ok {
64 ret = ShouldResemble(f.ValueNI, data)
65 } else {
66 ret = ShouldResemble(f.Value, toIntSlice(values) )
67 }
68 }
69 return ret
70 }
71 }
72
73 func fooSetTo(ds datastore.Interface) func(interface{}, ...interface{}) string {
74 return func(id interface{}, values ...interface{}) string {
75 f := &Foo{ID: toInt64(id)}
76 if len(values) == 0 {
77 return ShouldBeNil(ds.Delete(ds.KeyForObj(f)))
78 }
79 if data, ok := values[0].([]byte); ok {
80 f.ValueNI = data
81 } else {
82 f.Value = toIntSlice(values)
83 }
84 return ShouldBeNil(ds.Put(f))
85 }
86 }
87
88 func TestTransactionBuffers(t *testing.T) {
89 t.Parallel()
90
91 cb := func(i int64) string {
92 buf := &bytes.Buffer{}
93 cmpbin.WriteInt(buf, i)
94 return buf.String()
95 }
96
97 rs := rand.NewSource(0)
98 dataMultiRoot := make([]*Foo, 20)
99 dataSingleRoot := make([]*Foo, 20)
100 root := datastore.MakeKey("dev~app", "", "Parent", 1)
101 nums := make([]string, 20)
102 for i := range dataMultiRoot {
103 id := int64(i + 1)
104 nums[i] = cb(id)
105
106 val := make([]int64, rs.Int63()%20)
107 for j := range val {
108 r := rs.Int63()
109 val[j] = r
110 }
111
112 dataMultiRoot[i] = &Foo{ID: id, Value: val}
113 dataSingleRoot[i] = &Foo{ID: id, Parent: root, Value: val}
114 }
115
116 hugeField := make([]byte, DefaultSizeBudget/8)
117 for i := range hugeField {
118 hugeField[i] = byte(i)
119 }
120
121 hugeData := make([]*Foo, 11)
122 for i := range hugeData {
123 hugeData[i] = &Foo{ID: int64(i + 1), ValueNI: hugeField}
124 }
125
126 mkds := func(data []*Foo) (under, over *count.DSCounter, ds datastore.In terface) {
127 c := memory.Use(context.Background())
128 ds = datastore.Get(c)
129 _, err := ds.AllocateIDs(ds.KeyForObj(data[0]), 100)
130 if err != nil {
131 panic(err)
132 }
133 if err := ds.PutMulti(data); err != nil {
134 panic(err)
135 }
136
137 c, under = count.FilterRDS(c)
138 c = FilterRDS(c)
139 c, over = count.FilterRDS(c)
140 ds = datastore.Get(c)
141 return
142 }
143
144 Convey("Get/Put/Delete", t, func() {
145 under, over, ds := mkds(dataMultiRoot)
146
147 So(under.PutMulti.Total(), ShouldEqual, 0)
148 So(over.PutMulti.Total(), ShouldEqual, 0)
149
150 Convey("Good", func() {
151 Convey("read-only", func() {
152 So(ds.RunInTransaction(func(c context.Context) e rror {
153 ds := datastore.Get(c)
154
155 So(4, fooShouldHave(ds), dataMultiRoot[3 ].Value)
156 return nil
157 }, nil), ShouldBeNil)
158 })
159
160 Convey("single-level read/write", func() {
161 So(ds.RunInTransaction(func(c context.Context) e rror {
162 ds := datastore.Get(c)
163
164 So(4, fooShouldHave(ds), dataMultiRoot[3 ].Value)
165
166 So(4, fooSetTo(ds), 1, 2, 3, 4)
167
168 So(3, fooSetTo(ds), 1, 2, 3, 4)
169
170 // look! it remembers :)
171 So(4, fooShouldHave(ds), 1, 2, 3, 4)
172 return nil
173 }, &datastore.TransactionOptions{XG: true}), Sho uldBeNil)
174
175 So(under.PutMulti.Total(), ShouldEqual, 1)
176
177 So(3, fooShouldHave(ds), 1, 2, 3, 4)
178 So(4, fooShouldHave(ds), 1, 2, 3, 4)
179 })
180
181 Convey("multi-level read/write", func() {
182 So(ds.RunInTransaction(func(c context.Context) e rror {
183 ds := datastore.Get(c)
184
185 So(3, fooShouldHave(ds), dataMultiRoot[2 ].Value)
186
187 So(3, fooSetTo(ds), 1, 2, 3, 4)
188 So(7, fooSetTo(ds))
189
190 // inner, failing, transaction
191 So(ds.RunInTransaction(func(c context.Co ntext) error {
192 ds := datastore.Get(c)
193
194 // we can see stuff written in t he outer txn
195 So(7, fooShouldHave(ds))
196 So(3, fooShouldHave(ds), 1, 2, 3 , 4)
197
198 So(3, fooSetTo(ds), 10, 20, 30, 40)
199
200 // disaster strikes!
201 return errors.New("whaaaa")
202 }, nil), ShouldErrLike, "whaaaa")
203
204 So(3, fooShouldHave(ds), 1, 2, 3, 4)
205
206 // inner, successful, transaction
207 So(ds.RunInTransaction(func(c context.Co ntext) error {
208 ds := datastore.Get(c)
209 So(3, fooShouldHave(ds), 1, 2, 3 , 4)
210 So(3, fooSetTo(ds), 10, 20, 30, 40)
211 return nil
212 }, nil), ShouldBeNil)
213
214 // now we see it
215 So(3, fooShouldHave(ds), 10, 20, 30, 40)
216 return nil
217 }, &datastore.TransactionOptions{XG: true}), Sho uldBeNil)
218
219 So(under.PutMulti.Total(), ShouldEqual, 1)
220 So(under.DeleteMulti.Total(), ShouldEqual, 1)
221
222 // 'over' Put operations are amplified because t he inner transaction
223 // commits go through the 'over' filter on the o uter transaction. So it's
224 // # Puts + # inner txns.
225 So(over.PutMulti.Total(), ShouldEqual, 5)
226
227 So(7, fooShouldHave(ds))
228 So(3, fooShouldHave(ds), 10, 20, 30, 40)
229 })
230
231 Convey("can allocate IDs from an inner transaction", fun c() {
232 nums := []int64{4, 8, 15, 16, 23, 42}
233 k := (*datastore.Key)(nil)
234 So(ds.RunInTransaction(func(c context.Context) e rror {
235 ds := datastore.Get(c)
236
237 So(ds.RunInTransaction(func(c context.Co ntext) error {
238 ds := datastore.Get(c)
239 f := &Foo{Value: nums}
240 So(ds.Put(f), ShouldBeNil)
241 k = ds.KeyForObj(f)
242 return nil
243 }, nil), ShouldBeNil)
244
245 So(k.IntID(), fooShouldHave(ds), nums)
246
247 return nil
248 }, nil), ShouldBeNil)
249
250 So(k.IntID(), fooShouldHave(ds), nums)
251 })
252
253 Convey("inner txn too big allows outer txn", func() {
254 So(ds.RunInTransaction(func(c context.Context) e rror {
255 ds := datastore.Get(c)
256
257 So(18, fooSetTo(ds), hugeField)
258
259 So(ds.RunInTransaction(func(c context.Co ntext) error {
260 ds := datastore.Get(c)
261 So(ds.PutMulti(hugeData), Should BeNil)
262 return nil
263 }, nil), ShouldErrLike, ErrTransactionTo oLarge)
264
265 return nil
266 }, &datastore.TransactionOptions{XG: true}), Sho uldBeNil)
267
268 So(18, fooShouldHave(ds), hugeField)
269 })
270
271 })
272
273 Convey("Bad", func() {
274
275 Convey("too many roots", func() {
276 So(ds.RunInTransaction(func(c context.Context) e rror {
277 ds := datastore.Get(c)
278
279 f := &Foo{ID: 7}
280 So(ds.Get(f), ShouldBeNil)
281 So(f, ShouldResemble, dataMultiRoot[6])
282
283 So(ds.RunInTransaction(func(c context.Co ntext) error {
284 return datastore.Get(c).Get(&Foo {ID: 6})
285 }, nil), ShouldErrLike, "too many entity groups")
286
287 f.Value = []int64{9}
288 So(ds.Put(f), ShouldBeNil)
289
290 return nil
291 }, nil), ShouldBeNil)
292
293 f := &Foo{ID: 7}
294 So(ds.Get(f), ShouldBeNil)
295 So(f.Value, ShouldResemble, []int64{9})
296 })
297
298 Convey("buffered errors never reach the datastore", func () {
299 So(ds.RunInTransaction(func(c context.Context) e rror {
300 ds := datastore.Get(c)
301
302 So(ds.Put(&Foo{ID: 1, Value: []int64{1, 2, 3, 4}}), ShouldBeNil)
303 return errors.New("boop")
304 }, nil), ShouldErrLike, "boop")
305 So(under.PutMulti.Total(), ShouldEqual, 0)
306 So(over.PutMulti.Successes(), ShouldEqual, 1)
307 })
308
309 })
310
311 })
312
313 Convey("Queries", t, func() {
314 Convey("Good", func() {
315 q := datastore.NewQuery("Foo").Ancestor(root)
316
317 Convey("normal", func() {
318 _, _, ds := mkds(dataSingleRoot)
319 ds.Testable().AddIndexes(&datastore.IndexDefinit ion{
320 Kind: "Foo",
321 Ancestor: true,
322 SortBy: []datastore.IndexColumn{
323 {Property: "Value"},
324 },
325 })
326
327 So(ds.RunInTransaction(func(c context.Context) e rror {
328 ds := datastore.Get(c)
329
330 q = q.Lt("Value", 400000000000000000)
331
332 vals := []*Foo{}
333 So(ds.GetAll(q, &vals), ShouldBeNil)
334 So(len(vals), ShouldEqual, 8)
335
336 f := &Foo{ID: 1, Parent: root}
337 So(ds.Get(f), ShouldBeNil)
338 f.Value = append(f.Value, 100)
339 So(ds.Put(f), ShouldBeNil)
340
341 // Wowee, zowee, merged queries!
342 vals2 := []*Foo{}
343 So(ds.GetAll(q, &vals2), ShouldBeNil)
344 So(len(vals2), ShouldEqual, 9)
345 So(vals2[0], ShouldResemble, f)
346
347 vals2 = []*Foo{}
348 So(ds.GetAll(q.Limit(2).Offset(1), &vals 2), ShouldBeNil)
349 So(len(vals2), ShouldEqual, 2)
350 So(vals2, ShouldResemble, vals[:2])
351
352 return nil
353 }, nil), ShouldBeNil)
354 })
355
356 Convey("keysOnly", func() {
357 _, _, ds := mkds([]*Foo{
358 {ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}},
359 {ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}},
360 {ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1}},
361 {ID: 5, Parent: root, Value: []int64{1, 70, 101}},
362 })
363
364 So(ds.RunInTransaction(func(c context.Context) e rror {
365 ds := datastore.Get(c)
366
367 q = q.Eq("Value", 1).KeysOnly(true)
368 vals := []*datastore.Key{}
369 So(ds.GetAll(q, &vals), ShouldBeNil)
370 So(len(vals), ShouldEqual, 3)
371 So(vals[2], ShouldResemble, ds.MakeKey(" Parent", 1, "Foo", 5))
372
373 // can remove keys
374 So(ds.Delete(ds.MakeKey("Parent", 1, "Fo o", 2)), ShouldBeNil)
375 vals = []*datastore.Key{}
376 So(ds.GetAll(q, &vals), ShouldBeNil)
377 So(len(vals), ShouldEqual, 2)
378
379 // and add new ones
380 So(ds.Put(&Foo{ID: 1, Parent: root, Valu e: []int64{1, 7, 100}}), ShouldBeNil)
381 So(ds.Put(&Foo{ID: 7, Parent: root, Valu e: []int64{20, 1}}), ShouldBeNil)
382 vals = []*datastore.Key{}
383 So(ds.GetAll(q, &vals), ShouldBeNil)
384 So(len(vals), ShouldEqual, 4)
385
386 So(vals[0].IntID(), ShouldEqual, 1)
387 So(vals[1].IntID(), ShouldEqual, 4)
388 So(vals[2].IntID(), ShouldEqual, 5)
389 So(vals[3].IntID(), ShouldEqual, 7)
390
391 return nil
392 }, nil), ShouldBeNil)
393 })
394
395 Convey("project", func() {
396 _, _, ds := mkds([]*Foo{
397 {ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}},
398 {ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}},
399 {ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1}},
400 {ID: 5, Parent: root, Value: []int64{1, 70, 101}},
401 })
402
403 ds.Testable().AddIndexes(&datastore.IndexDefinit ion{
404 Kind: "Foo",
405 Ancestor: true,
406 SortBy: []datastore.IndexColumn{
407 {Property: "Value"},
408 },
409 })
410
411 So(ds.RunInTransaction(func(c context.Context) e rror {
412 ds := datastore.Get(c)
413
414 q = q.Project("Value").Offset(4).Limit(1 0)
415
416 vals := []datastore.PropertyMap{}
417 So(ds.GetAll(q, &vals), ShouldBeNil)
418 So(len(vals), ShouldEqual, 10)
419
420 expect := []struct {
421 id int64
422 val int64
423 }{
424 {2, 3},
425 {3, 3},
426 {4, 3},
427 {2, 4},
428 {3, 4},
429 {2, 5},
430 {3, 5},
431 {4, 5},
432 {2, 6},
433 {3, 6},
434 }
435
436 for i, pm := range vals {
437 So(pm.GetMetaDefault("key", nil) , ShouldResemble, ds.MakeKey("Parent", 1, "Foo", expect[i].id))
438 So(pm["Value"][0].Value(), Shoul dEqual, expect[i].val)
439 }
440
441 // should remove 4 entries, but there ar e plenty more to fill
442 So(ds.Delete(ds.MakeKey("Parent", 1, "Fo o", 2)), ShouldBeNil)
443
444 vals = []datastore.PropertyMap{}
445 So(ds.GetAll(q, &vals), ShouldBeNil)
446 So(len(vals), ShouldEqual, 10)
447
448 expect = []struct {
449 id int64
450 val int64
451 }{
452 // note (3, 3) and (4, 3) are co rrectly missing because deleting
453 // 2 removed two entries which a re hidden by the Offset(4).
454 {3, 4},
455 {3, 5},
456 {4, 5},
457 {3, 6},
458 {3, 7},
459 {4, 7},
460 {3, 8},
461 {3, 9},
462 {4, 9},
463 {4, 11},
464 }
465
466 for i, pm := range vals {
467 So(pm.GetMetaDefault("key", nil) , ShouldResemble, ds.MakeKey("Parent", 1, "Foo", expect[i].id))
468 So(pm["Value"][0].Value(), Shoul dEqual, expect[i].val)
469 }
470
471 So(ds.Put(&Foo{ID: 1, Parent: root, Valu e: []int64{3, 9}}), ShouldBeNil)
472
473 vals = []datastore.PropertyMap{}
474 So(ds.GetAll(q, &vals), ShouldBeNil)
475 So(len(vals), ShouldEqual, 10)
476
477 expect = []struct {
478 id int64
479 val int64
480 }{
481 // 'invisible' {1, 3} entry bump s the {4, 3} into view.
482 {4, 3},
483 {3, 4},
484 {3, 5},
485 {4, 5},
486 {3, 6},
487 {3, 7},
488 {4, 7},
489 {3, 8},
490 {1, 9},
491 {3, 9},
492 {4, 9},
493 }
494
495 for i, pm := range vals {
496 So(pm.GetMetaDefault("key", nil) , ShouldResemble, ds.MakeKey("Parent", 1, "Foo", expect[i].id))
497 So(pm["Value"][0].Value(), Shoul dEqual, expect[i].val)
498 }
499
500 return nil
501 }, nil), ShouldBeNil)
502
503 })
504
505 Convey("project+distinct", func() {
506 _, _, ds := mkds([]*Foo{
507 {ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}},
508 {ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}},
509 {ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1}},
510 {ID: 5, Parent: root, Value: []int64{1, 70, 101}},
511 })
512
513 ds.Testable().AddIndexes(&datastore.IndexDefinit ion{
514 Kind: "Foo",
515 Ancestor: true,
516 SortBy: []datastore.IndexColumn{
517 {Property: "Value"},
518 },
519 })
520
521 So(ds.RunInTransaction(func(c context.Context) e rror {
522 ds := datastore.Get(c)
523
524 q = q.Project("Value").Distinct(true)
525
526 vals := []datastore.PropertyMap{}
527 So(ds.GetAll(q, &vals), ShouldBeNil)
528 So(len(vals), ShouldEqual, 13)
529
530 expect := []struct {
531 id int64
532 val int64
533 }{
534 {2, 1},
535 {2, 2},
536 {2, 3},
537 {2, 4},
538 {2, 5},
539 {2, 6},
540 {2, 7},
541 {3, 8},
542 {3, 9},
543 {4, 11},
544 {5, 70},
545 {4, 100},
546 {5, 101},
547 }
548
549 for i, pm := range vals {
550 So(pm["Value"][0].Value(), Shoul dEqual, expect[i].val)
551 So(pm.GetMetaDefault("key", nil) , ShouldResemble, ds.MakeKey("Parent", 1, "Foo", expect[i].id))
552 }
553
554 return nil
555 }, nil), ShouldBeNil)
556 })
557
558 Convey("dedup in action", func() {
559 data := []*Foo{
560 {ID: 2, Parent: root, Value: []int64{1, 2, 3, 4, 5, 6, 7}},
561 {ID: 3, Parent: root, Value: []int64{3, 4, 5, 6, 7, 8, 9}},
562 {ID: 4, Parent: root, Value: []int64{3, 5, 7, 9, 11, 100, 1, 2}},
563 {ID: 5, Parent: root, Value: []int64{1, 70, 101}},
564 }
565
566 _, _, ds := mkds(data)
567
568 q = q.Eq("Value", 2, 3)
569 vals := []*Foo{}
570 So(ds.GetAll(q, &vals), ShouldBeNil)
571 So(len(vals), ShouldEqual, 2)
572
573 So(ds.RunInTransaction(func(c context.Context) e rror {
574 ds := datastore.Get(c)
575
576 vals := []*Foo{}
577 So(ds.GetAll(q, &vals), ShouldBeNil)
578 So(len(vals), ShouldEqual, 2)
579
580 So(vals[0], ShouldResemble, data[0])
581 So(vals[1], ShouldResemble, data[2])
582
583 return nil
584 }, nil), ShouldBeNil)
585 })
586
587 Convey("project+extra orders", nil)
588
589 Convey("keysOnly+extra orders", nil)
590
591 Convey("inner reflects outer", nil)
592
593 Convey("committed inner reflects in outer", nil)
594
595 })
596
597 })
598
599 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698