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

Side by Side Diff: go/src/infra/gae/libs/wrapper/memory/datastore_test.go

Issue 1152383003: Simple memory testing for gae/wrapper (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@better_context_lite
Patch Set: add go-slab dependency Created 5 years, 6 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 memory
6
7 import (
8 "fmt"
9 "infra/gae/libs/meta"
10 "infra/gae/libs/wrapper"
11 "testing"
12
13 . "github.com/smartystreets/goconvey/convey"
14 "golang.org/x/net/context"
15
16 "appengine/datastore"
17 )
18
19 func TestDatastoreKinder(t *testing.T) {
20 t.Parallel()
21
22 Convey("Datastore kinds and keys", t, func() {
23 c := Use(Enable(context.Background()))
24 ds := wrapper.GetDS(c)
25 So(ds, ShouldNotBeNil)
26
27 Convey("implements DSKinder", func() {
28 type Foo struct{}
29 So(ds.Kind(&Foo{}), ShouldEqual, "Foo")
30
31 Convey("which can be tweaked by DSKindSetter", func() {
32 ds.SetKindNameResolver(func(interface{}) string { return "spam" })
33 So(ds.Kind(&Foo{}), ShouldEqual, "spam")
34
35 Convey("and it retains the function so you can s tack them", func() {
36 cur := ds.KindNameResolver()
37 ds.SetKindNameResolver(func(o interface{ }) string { return "wat" + cur(o) })
38 So(ds.Kind(&Foo{}), ShouldEqual, "watspa m")
39 })
40 })
41 })
42
43 Convey("implements DSNewKeyer", func() {
44 Convey("NewKey", func() {
45 key := ds.NewKey("nerd", "stringID", 0, nil)
46 So(key, ShouldNotBeNil)
47 So(key.Kind(), ShouldEqual, "nerd")
48 So(key.StringID(), ShouldEqual, "stringID")
49 So(key.IntID(), ShouldEqual, 0)
50 So(key.Parent(), ShouldBeNil)
51 So(key.AppID(), ShouldEqual, "dev~my~app")
52 So(key.Namespace(), ShouldEqual, "")
53 So(key.String(), ShouldEqual, "/nerd,stringID")
54 So(key.Incomplete(), ShouldBeFalse)
55 So(KeyValid("", key, UserKeyOnly), ShouldBeTrue)
56
57 chkey := ds.NewKey("wat", "", 100, key)
58 So(chkey, ShouldNotBeNil)
59 So(chkey.Kind(), ShouldEqual, "wat")
60 So(chkey.StringID(), ShouldEqual, "")
61 So(chkey.IntID(), ShouldEqual, 100)
62 So(chkey.Parent(), ShouldEqual, key)
63 So(chkey.AppID(), ShouldEqual, "dev~my~app")
64 So(chkey.Namespace(), ShouldEqual, "")
65 So(chkey.String(), ShouldEqual, "/nerd,stringID/ wat,100")
66 So(key.Incomplete(), ShouldBeFalse)
67 So(KeyValid("", chkey, UserKeyOnly), ShouldBeTru e)
68
69 incompl := ds.NewKey("sup", "", 0, key)
70 So(incompl, ShouldNotBeNil)
71 So(incompl.Incomplete(), ShouldBeTrue)
72 So(KeyValid("", incompl, UserKeyOnly), ShouldBeT rue)
73 So(incompl.String(), ShouldEqual, "/nerd,stringI D/sup,0")
74
75 bad := ds.NewKey("nooo", "", 10, incompl)
76 So(bad, ShouldNotBeNil)
77 So(bad.Incomplete(), ShouldBeFalse)
78 So(KeyValid("", bad, UserKeyOnly), ShouldBeFalse )
79 So(bad.String(), ShouldEqual, "/nerd,stringID/su p,0/nooo,10")
80
81 So(rootKey(bad), ShouldEqual, key)
82
83 Convey("other key validation", func() {
84 So(KeyValid("", nil, UserKeyOnly), Shoul dBeFalse)
85
86 key := ds.NewKey("", "", 0, nil)
87 So(key, ShouldNotBeNil)
88
89 So(KeyValid("", key, UserKeyOnly), Shoul dBeFalse)
90
91 key = ds.NewKey("noop", "power level", 9 000, nil)
92 So(key, ShouldNotBeNil)
93
94 So(KeyValid("", key, UserKeyOnly), Shoul dBeFalse)
95 })
96 })
97
98 Convey("NewKeyObj", func() {
99 type Foo struct {
100 _knd string `goon:"kind,coool" `
101 ID int64 `goon:"id"`
102 Parent *datastore.Key `goon:"parent"`
103 }
104 f := &Foo{ID: 100}
105 k := ds.NewKeyObj(f)
106 So(k.String(), ShouldEqual, "/coool,100")
107
108 f.Parent = k
109 f._knd = "weevils"
110 f.ID = 19
111 k = ds.NewKeyObj(f)
112 So(k.String(), ShouldEqual, "/coool,100/weevils, 19")
113
114 Convey("panics when you do a dumb thing", func() {
115 type Foo struct {
116 ID []byte `goon:"id"`
117 }
118 So(func() { ds.NewKeyObj(&Foo{}) }, Shou ldPanic)
119 })
120 })
121
122 Convey("NewKeyObjError", func() {
123 type Foo struct {
124 ID []byte `goon:"id"`
125 }
126 _, err := ds.NewKeyObjError(&Foo{})
127 So(err.Error(), ShouldContainSubstring, "must be int64 or string")
128 })
129 })
130
131 })
132 }
133
134 func TestDatastoreSingleReadWriter(t *testing.T) {
135 t.Parallel()
136
137 Convey("Datastore single reads and writes", t, func() {
138 c := Use(Enable(context.Background()))
139 ds := wrapper.GetDS(c)
140 So(ds, ShouldNotBeNil)
141
142 Convey("implements DSSingleReadWriter", func() {
143 type Foo struct {
144 ID int64 `goon:"id" datastore:"-"`
145 Parent *datastore.Key `goon:"parent" datastore:" -"`
146 Val int
147 }
148
149 Convey("invalid keys break", func() {
150 k := ds.NewKeyObj(&Foo{})
151 f := &Foo{Parent: k}
152 So(ds.Get(f), ShouldEqual, datastore.ErrInvalidK ey)
153
154 _, err := ds.Put(f)
155 So(err, ShouldEqual, datastore.ErrInvalidKey)
156 })
157
158 Convey("getting objects that DNE is an error", func() {
159 So(ds.Get(&Foo{ID: 1}), ShouldEqual, datastore.E rrNoSuchEntity)
160 })
161
162 Convey("Can Put stuff", func() {
163 // with an incomplete key!
164 f := &Foo{Val: 10}
165 k, err := ds.Put(f)
166 So(err, ShouldBeNil)
167 So(k.String(), ShouldEqual, "/Foo,1")
168 So(ds.NewKeyObj(f), ShouldResemble, k)
169
170 Convey("and Get it back", func() {
171 newFoo := &Foo{ID: 1}
172 err := ds.Get(newFoo)
173 So(err, ShouldBeNil)
174 So(newFoo, ShouldResemble, f)
175
176 Convey("and we can Delete it", func() {
177 err := ds.Delete(ds.NewKey("Foo" , "", 1, nil))
178 So(err, ShouldBeNil)
179
180 err = ds.Get(newFoo)
181 So(err, ShouldEqual, datastore.E rrNoSuchEntity)
182 })
183 })
184 Convey("Deleteing with a bogus key is bad", func () {
185 err := ds.Delete(ds.NewKey("Foo", "wat", 100, nil))
186 So(err, ShouldEqual, datastore.ErrInvali dKey)
187 })
188 Convey("Deleteing a DNE entity is fine", func() {
189 err := ds.Delete(ds.NewKey("Foo", "wat", 0, nil))
190 So(err, ShouldBeNil)
191 })
192
193 Convey("serialization breaks in the normal ways" , func() {
194 type BadFoo struct {
195 _kind string `goon:"kind,Foo"`
196 ID int64 `goon:"id" datastor e:"-"`
197 Val uint8
198 }
199 _, err := ds.Put(&BadFoo{})
200 So(err.Error(), ShouldContainSubstring,
201 "unsupported struct field type: uint8")
202
203 err = ds.Get(&BadFoo{ID: 1})
204 So(err.Error(), ShouldContainSubstring,
205 "type mismatch: int versus uint8 ")
206 })
207
208 Convey("check that metadata works", func() {
209 val, _ := meta.GetEntityGroupVersion(c, k)
210 So(val, ShouldEqual, 1)
211
212 for i := 0; i < 10; i++ {
213 _, err = ds.Put(&Foo{Val: 10, Pa rent: k})
214 So(err, ShouldBeNil)
215 }
216 val, _ = meta.GetEntityGroupVersion(c, k )
217 So(val, ShouldEqual, 11)
218
219 Convey("ensure that group versions persi st across deletes", func() {
220 So(ds.Delete(k), ShouldBeNil)
221 for i := int64(1); i < 11; i++ {
222 So(ds.Delete(ds.NewKey(" Foo", "", i, k)), ShouldBeNil)
223 }
224 // TODO(riannucci): replace with a Count query instead of this cast
225 ents := ds.(*dsImpl).data.store. GetCollection("ents:")
226 num, _ := ents.GetTotals()
227 // /__entity_root_ids__,Foo
228 // /Foo,1/__entity_group__,1
229 // /Foo,1/__entity_group_ids__,1
230 So(num, ShouldEqual, 3)
231
232 version, err := curVersion(ents, groupMetaKey(k))
233 So(err, ShouldBeNil)
234 So(version, ShouldEqual, 22)
235
236 k, err := ds.Put(f)
237 So(err, ShouldBeNil)
238 val, _ := meta.GetEntityGroupVer sion(c, k)
239 So(val, ShouldEqual, 23)
240 })
241 })
242 })
243 })
244
245 Convey("implements DSTransactioner", func() {
246 type Foo struct {
247 ID int64 `goon:"id" datastore:"-"`
248 Parent *datastore.Key `goon:"parent" datastore:" -"`
249 Val int
250 }
251 Convey("Put", func() {
252 f := &Foo{Val: 10}
253 k, err := ds.Put(f)
254 So(err, ShouldBeNil)
255 So(k.String(), ShouldEqual, "/Foo,1")
256 So(ds.NewKeyObj(f), ShouldResemble, k)
257
258 Convey("can Put new entity groups", func() {
259 err := ds.RunInTransaction(func(c contex t.Context) error {
260 ds := wrapper.GetDS(c)
261 So(ds, ShouldNotBeNil)
262
263 f1 := &Foo{Val: 100}
264 k, err := ds.Put(f1)
265 So(err, ShouldBeNil)
266 So(k.String(), ShouldEqual, "/Fo o,2")
267
268 f2 := &Foo{Val: 200}
269 k, err = ds.Put(f2)
270 So(err, ShouldBeNil)
271 So(k.String(), ShouldEqual, "/Fo o,3")
272
273 return nil
274 }, &datastore.TransactionOptions{XG: tru e})
275 So(err, ShouldBeNil)
276
277 f := &Foo{ID: 2}
278 So(ds.Get(f), ShouldBeNil)
279 So(f.Val, ShouldEqual, 100)
280
281 f = &Foo{ID: 3}
282 So(ds.Get(f), ShouldBeNil)
283 So(f.Val, ShouldEqual, 200)
284 })
285
286 Convey("can Put new entities in a current group" , func() {
287 err := ds.RunInTransaction(func(c contex t.Context) error {
288 ds := wrapper.GetDS(c)
289 So(ds, ShouldNotBeNil)
290
291 f1 := &Foo{Val: 100, Parent: ds. NewKeyObj(f)}
292 k, err := ds.Put(f1)
293 So(err, ShouldBeNil)
294 So(k.String(), ShouldEqual, "/Fo o,1/Foo,1")
295
296 f2 := &Foo{Val: 200, Parent: ds. NewKeyObj(f)}
297 k, err = ds.Put(f2)
298 So(err, ShouldBeNil)
299 So(k.String(), ShouldEqual, "/Fo o,1/Foo,2")
300
301 return nil
302 }, nil)
303 So(err, ShouldBeNil)
304
305 f1 := &Foo{ID: 1, Parent: ds.NewKeyObj(& Foo{ID: 1})}
306 So(ds.Get(f1), ShouldBeNil)
307 So(f1.Val, ShouldEqual, 100)
308
309 f2 := &Foo{ID: 2, Parent: f1.Parent}
310 So(ds.Get(f2), ShouldBeNil)
311 So(f2.Val, ShouldEqual, 200)
312 })
313
314 Convey("Deletes work too", func() {
315 err := ds.RunInTransaction(func(c contex t.Context) error {
316 ds := wrapper.GetDS(c)
317 So(ds, ShouldNotBeNil)
318 So(ds.Delete(ds.NewKeyObj(f)), S houldBeNil)
319 return nil
320 }, nil)
321 So(err, ShouldBeNil)
322 So(ds.Get(f), ShouldEqual, datastore.Err NoSuchEntity)
323 })
324
325 Convey("A Get counts against your group count", func() {
326 err := ds.RunInTransaction(func(c contex t.Context) error {
327 ds := wrapper.GetDS(c)
328 f := &Foo{ID: 20}
329 So(ds.Get(f), ShouldEqual, datas tore.ErrNoSuchEntity)
330
331 f.ID = 1
332 So(ds.Get(f).Error(), ShouldCont ainSubstring, "cross-group")
333 return nil
334 }, nil)
335 So(err, ShouldBeNil)
336 })
337
338 Convey("Get takes a snapshot", func() {
339 err := ds.RunInTransaction(func(c contex t.Context) error {
340 txnDS := wrapper.GetDS(c)
341 So(txnDS, ShouldNotBeNil)
342
343 f := &Foo{ID: 1}
344 So(txnDS.Get(f), ShouldBeNil)
345 So(f.Val, ShouldEqual, 10)
346
347 // Don't ever do this in a real program unless you want to guarantee
348 // a failed transaction :)
349 f.Val = 11
350 _, err := ds.Put(f)
351 So(err, ShouldBeNil)
352
353 So(txnDS.Get(f), ShouldBeNil)
354 So(f.Val, ShouldEqual, 10)
355
356 return nil
357 }, nil)
358 So(err, ShouldBeNil)
359
360 f := &Foo{ID: 1}
361 So(ds.Get(f), ShouldBeNil)
362 So(f.Val, ShouldEqual, 11)
363
364 })
365
366 Convey("and snapshots are consistent even after Puts", func() {
367 err := ds.RunInTransaction(func(c contex t.Context) error {
368 txnDS := wrapper.GetDS(c)
369 So(txnDS, ShouldNotBeNil)
370
371 f := &Foo{ID: 1}
372 So(txnDS.Get(f), ShouldBeNil)
373 So(f.Val, ShouldEqual, 10)
374
375 // Don't ever do this in a real program unless you want to guarantee
376 // a failed transaction :)
377 f.Val = 11
378 _, err := ds.Put(f)
379 So(err, ShouldBeNil)
380
381 So(txnDS.Get(f), ShouldBeNil)
382 So(f.Val, ShouldEqual, 10)
383
384 f.Val = 20
385 _, err = txnDS.Put(f)
386 So(err, ShouldBeNil)
387
388 So(txnDS.Get(f), ShouldBeNil)
389 So(f.Val, ShouldEqual, 10) // st ill gets 10
390
391 return nil
392 }, nil)
393 So(err.Error(), ShouldContainSubstring, "concurrent")
394
395 f := &Foo{ID: 1}
396 So(ds.Get(f), ShouldBeNil)
397 So(f.Val, ShouldEqual, 11)
398 })
399
400 Convey("Reusing a transaction context is bad new s", func() {
401 txnDS := wrapper.Datastore(nil)
402 err := ds.RunInTransaction(func(c contex t.Context) error {
403 txnDS = wrapper.GetDS(c)
404 So(txnDS.Get(&Foo{ID: 1}), Shoul dBeNil)
405 return nil
406 }, nil)
407 So(err, ShouldBeNil)
408 So(txnDS.Get(&Foo{ID: 1}).Error(), Shoul dContainSubstring, "expired")
409 })
410
411 Convey("Nested transactions are rejected", func( ) {
412 err := ds.RunInTransaction(func(c contex t.Context) error {
413 err := wrapper.GetDS(c).RunInTra nsaction(func(c context.Context) error {
414 panic("noooo")
415 }, nil)
416 So(err.Error(), ShouldContainSub string, "nested transactions")
417 return nil
418 }, nil)
419 So(err, ShouldBeNil)
420 })
421
422 Convey("Concurrent transactions only accept one set of changes", func() {
423 // Note: I think this implementation is actually /slightly/ wrong.
424 // Accorting to my read of the docs for appengine, when you open a
425 // transaction it actually (essentially) holds a reference to the
426 // entire datastore. Our implementation takes a snapshot of the
427 // entity group as soon as something obs erves/affects it.
428 //
429 // That said... I'm not sure if there's really a semantic difference.
430 err := ds.RunInTransaction(func(c contex t.Context) error {
431 txnDS := wrapper.GetDS(c)
432 f := &Foo{ID: 1, Val: 21}
433 _, err = txnDS.Put(f)
434 So(err, ShouldBeNil)
435
436 err := ds.RunInTransaction(func( c context.Context) error {
437 txnDS := wrapper.GetDS(c )
438 f := &Foo{ID: 1, Val: 27 }
439 _, err := txnDS.Put(f)
440 So(err, ShouldBeNil)
441 return nil
442 }, nil)
443 So(err, ShouldBeNil)
444
445 return nil
446 }, nil)
447 So(err.Error(), ShouldContainSubstring, "concurrent")
448
449 f := &Foo{ID: 1}
450 So(ds.Get(f), ShouldBeNil)
451 So(f.Val, ShouldEqual, 27)
452 })
453
454 Convey("XG", func() {
455 Convey("Modifying two groups with XG=fal se is invalid", func() {
456 err := ds.RunInTransaction(func( c context.Context) error {
457 ds := wrapper.GetDS(c)
458 f := &Foo{ID: 1, Val: 20 0}
459 _, err := ds.Put(f)
460 So(err, ShouldBeNil)
461
462 f.ID = 2
463 _, err = ds.Put(f)
464 So(err.Error(), ShouldCo ntainSubstring, "cross-group")
465 return err
466 }, nil)
467 So(err.Error(), ShouldContainSub string, "cross-group")
468 })
469
470 Convey("Modifying >25 groups with XG=tru e is invald", func() {
471 err := ds.RunInTransaction(func( c context.Context) error {
472 ds := wrapper.GetDS(c)
473 for i := int64(1); i < 2 6; i++ {
474 f := &Foo{ID: i, Val: 200}
475 _, err := ds.Put (f)
476 So(err, ShouldBe Nil)
477 }
478 f := &Foo{ID: 27, Val: 2 00}
479 _, err := ds.Put(f)
480 So(err.Error(), ShouldCo ntainSubstring, "too many entity groups")
481 return err
482 }, &datastore.TransactionOptions {XG: true})
483 So(err.Error(), ShouldContainSub string, "too many entity groups")
484 })
485 })
486
487 Convey("Errors and panics", func() {
488 Convey("returning an error aborts", func () {
489 err := ds.RunInTransaction(func( c context.Context) error {
490 ds := wrapper.GetDS(c)
491 f := &Foo{ID: 1, Val: 20 0}
492 _, err := ds.Put(f)
493 So(err, ShouldBeNil)
494
495 return fmt.Errorf("thing y")
496 }, nil)
497 So(err.Error(), ShouldEqual, "th ingy")
498
499 f := &Foo{ID: 1}
500 So(ds.Get(f), ShouldBeNil)
501 So(f.Val, ShouldEqual, 10)
502 })
503
504 Convey("panicing aborts", func() {
505 So(func() {
506 ds.RunInTransaction(func (c context.Context) error {
507 ds := wrapper.Ge tDS(c)
508 f := &Foo{ID: 1, Val: 200}
509 _, err := ds.Put (f)
510 So(err, ShouldBe Nil)
511 panic("wheeeeee" )
512 }, nil)
513 }, ShouldPanic)
514
515 f := &Foo{ID: 1}
516 So(ds.Get(f), ShouldBeNil)
517 So(f.Val, ShouldEqual, 10)
518 })
519 })
520 })
521 })
522
523 })
524 }
OLDNEW
« no previous file with comments | « go/src/infra/gae/libs/wrapper/memory/datastore_data.go ('k') | go/src/infra/gae/libs/wrapper/memory/doc.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698