// SetGlobalEnable is a convenience function for manipulating the GlobalConfig. // // It's meant to be called from admin handlers on your app to turn dscache // functionality on or off in emergencies. func SetGlobalEnable(c context.Context, memcacheEnabled bool) error { // always go to the default namespace c, err := info.Get(c).Namespace("") if err != nil { return err } return datastore.Get(c).RunInTransaction(func(c context.Context) error { ds := datastore.Get(c) cfg := &GlobalConfig{Enable: true} if err := ds.Get(cfg); err != nil && err != datastore.ErrNoSuchEntity { return err } if cfg.Enable == memcacheEnabled { return nil } cfg.Enable = memcacheEnabled if memcacheEnabled { // when going false -> true, wipe memcache. if err := memcache.Get(c).Flush(); err != nil { return err } } return ds.Put(cfg) }, nil) }
func TestRaceNonConflictingPuts(t *testing.T) { t.Parallel() ds := datastore.Get(Use(context.Background())) num := int32(0) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() err := ds.RunInTransaction(func(c context.Context) error { ds := datastore.Get(c) return ds.Put(pmap( "$kind", "Thing", Next, "Value", 100)) }, nil) if err != nil { t.Fatal("error during transaction", err) } atomic.AddInt32(&num, 1) }() } wg.Wait() if num != 100 { t.Fatal("expected 100 runs, got", num) } }
func TestRaceGetPut(t *testing.T) { t.Parallel() value := int32(0) num := int32(0) ds := datastore.Get(Use(context.Background())) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() err := ds.RunInTransaction(func(c context.Context) error { atomic.AddInt32(&num, 1) ds := datastore.Get(c) obj := pmap("$key", ds.MakeKey("Obj", 1)) if err := ds.Get(obj); err != nil && err != datastore.ErrNoSuchEntity { t.Fatal("error get", err) } cur := int64(0) if ps, ok := obj["Value"]; ok { cur = ps[0].Value().(int64) } cur++ obj["Value"] = []datastore.Property{prop(cur)} return ds.Put(obj) }, &datastore.TransactionOptions{Attempts: 200}) if err != nil { t.Fatal("error during transaction", err) } atomic.AddInt32(&value, 1) }() } wg.Wait() obj := pmap("$key", ds.MakeKey("Obj", 1)) if ds.Get(obj) != nil { t.FailNow() } t.Logf("Ran %d inner functions", num) if int64(value) != obj["Value"][0].Value().(int64) { t.Fatalf("value wrong value %d v %d", value, obj["Value"][0].Value().(int64)) } }
func registerVisitor(ctx context.Context) (int, error) { counter := VisitorCounter{ID: "frontend"} err := datastore.Get(ctx).RunInTransaction(func(c context.Context) error { ds := datastore.Get(c) err := ds.Get(&counter) if err != nil && err != datastore.ErrNoSuchEntity { return err } counter.Count++ return ds.Put(&counter) }, nil) return counter.Count, err }
func TestBrokenFeatures(t *testing.T) { t.Parallel() e := errors.New("default err") Convey("BrokenFeatures", t, func() { c := memory.Use(context.Background()) Convey("Can break ds", func() { Convey("without a default", func() { c, bf := FilterRDS(c, nil) ds := datastore.Get(c) vals := []datastore.PropertyMap{{ "$key": {datastore.MkPropertyNI(ds.NewKey("Wut", "", 1, nil))}, }} Convey("by specifying an error", func() { bf.BreakFeatures(e, "GetMulti", "PutMulti") So(ds.GetMulti(vals), ShouldEqual, e) Convey("and you can unbreak them as well", func() { bf.UnbreakFeatures("GetMulti") So(errors.SingleError(ds.GetMulti(vals)), ShouldEqual, datastore.ErrNoSuchEntity) Convey("no broken features at all is a shortcut", func() { bf.UnbreakFeatures("PutMulti") So(errors.SingleError(ds.GetMulti(vals)), ShouldEqual, datastore.ErrNoSuchEntity) }) }) }) Convey("Not specifying an error gets you a generic error", func() { bf.BreakFeatures(nil, "GetMulti") So(ds.GetMulti(vals).Error(), ShouldContainSubstring, `feature "GetMulti" is broken`) }) }) Convey("with a default", func() { c, bf := FilterRDS(c, e) ds := datastore.Get(c) vals := []datastore.PropertyMap{{ "$key": {datastore.MkPropertyNI(ds.NewKey("Wut", "", 1, nil))}, }} bf.BreakFeatures(nil, "GetMulti") So(ds.GetMulti(vals), ShouldEqual, e) }) }) }) }
func TestDatastoreKinder(t *testing.T) { t.Parallel() Convey("Datastore keys", t, func() { c := Use(context.Background()) ds := dsS.Get(c) So(ds, ShouldNotBeNil) Convey("implements DSNewKeyer", func() { Convey("NewKey", func() { key := ds.NewKey("nerd", "stringID", 0, nil) So(key, ShouldNotBeNil) So(key.Kind(), ShouldEqual, "nerd") So(key.StringID(), ShouldEqual, "stringID") So(key.IntID(), ShouldEqual, 0) So(key.Parent(), ShouldBeNil) So(key.AppID(), ShouldEqual, "dev~app") So(key.Namespace(), ShouldEqual, "") So(key.String(), ShouldEqual, "/nerd,stringID") So(key.Incomplete(), ShouldBeFalse) So(key.Valid(false, "dev~app", ""), ShouldBeTrue) }) }) }) }
func TestDatastoreQueries(t *testing.T) { Convey("Datastore Query suport", t, func() { c := Use(context.Background()) ds := dsS.Get(c) So(ds, ShouldNotBeNil) Convey("can create good queries", func() { q := ds.NewQuery("Foo").Filter("farnsworth >", 20).KeysOnly().Limit(10).Offset(39) // normally you can only get cursors from inside of the memory // implementation, so this construction is just for testing. start := queryCursor(bjoin( mkNum(2), serialize.ToBytes(dsS.IndexColumn{Property: "farnsworth"}), serialize.ToBytes(dsS.IndexColumn{Property: "__key__"}), serialize.ToBytes(prop(200)), serialize.ToBytes(prop(ds.NewKey("Foo", "id", 0, nil))))) So(start.String(), ShouldEqual, `gYAAZzFdTeeb3d9zOxsAAF-v221Xy32_AIGHyIgAAUc32-AFabMAAA==`) end := queryCursor(bjoin( mkNum(2), serialize.ToBytes(dsS.IndexColumn{Property: "farnsworth"}), serialize.ToBytes(dsS.IndexColumn{Property: "__key__"}), serialize.ToBytes(prop(3000)), serialize.ToBytes(prop(ds.NewKey("Foo", "zeta", 0, nil))))) q = q.Start(start).End(end) So(q, ShouldNotBeNil) So(q.(*queryImpl).err, ShouldBeNil) rq, err := q.(*queryImpl).reduce("", false) So(rq, ShouldNotBeNil) So(err, ShouldBeNil) }) Convey("ensures orders make sense", func() { q := ds.NewQuery("Cool") q = q.Filter("cat =", 19).Filter("bob =", 10).Order("bob").Order("bob") Convey("removes dups and equality orders", func() { q = q.Order("wat") qi := q.(*queryImpl) So(qi.err, ShouldBeNil) rq, err := qi.reduce("", false) So(err, ShouldBeNil) So(rq.suffixFormat, ShouldResemble, []dsS.IndexColumn{ {Property: "wat"}, {Property: "__key__"}}) }) Convey("if we equality-filter on __key__, that's just silly", func() { q = q.Order("wat").Filter("__key__ =", ds.NewKey("Foo", "wat", 0, nil)) _, err := q.(*queryImpl).reduce("", false) So(err, ShouldErrLike, "query equality filter on __key__ is silly") }) }) }) }
func ExampleFilterRDS() { // Set up your context using a base service implementation (memory or prod) c := memory.Use(context.Background()) // Apply the counter.FilterRDS c, counter := FilterRDS(c) // functions use ds from the context like normal... they don't need to know // that there are any filters at all. someCalledFunc := func(c context.Context) { ds := datastore.Get(c) vals := []datastore.PropertyMap{{ "FieldName": {datastore.MkProperty(100)}, "$key": {datastore.MkProperty(ds.NewKey("Kind", "", 1, nil))}}, } if err := ds.PutMulti(vals); err != nil { panic(err) } } // Using the other function. someCalledFunc(c) someCalledFunc(c) // Then we can see what happened! fmt.Printf("%d\n", counter.PutMulti.Successes()) // Output: // 2 }
func withTxnBuf(ctx context.Context, cb func(context.Context) error, opts *datastore.TransactionOptions) error { inf := info.Get(ctx) ns := inf.GetNamespace() parentState, _ := ctx.Value(dsTxnBufParent).(*txnBufState) roots := stringset.New(0) rootLimit := 1 if opts != nil && opts.XG { rootLimit = XGTransactionGroupLimit } sizeBudget := DefaultSizeBudget if parentState != nil { // TODO(riannucci): this is a bit wonky since it means that a child // transaction declaring XG=true will only get to modify 25 groups IF // they're same groups affected by the parent transactions. So instead of // respecting opts.XG for inner transactions, we just dup everything from // the parent transaction. roots = parentState.roots.Dup() rootLimit = parentState.rootLimit sizeBudget = parentState.sizeBudget - parentState.entState.total if sizeBudget < DefaultSizeThreshold { return ErrTransactionTooLarge } } bufDS, err := memory.NewDatastore(inf.FullyQualifiedAppID(), ns) if err != nil { return err } state := &txnBufState{ entState: &sizeTracker{}, bufDS: bufDS.Raw(), roots: roots, rootLimit: rootLimit, ns: ns, aid: inf.AppID(), parentDS: datastore.Get(context.WithValue(ctx, dsTxnBufHaveLock, true)).Raw(), sizeBudget: sizeBudget, } if err = cb(context.WithValue(ctx, dsTxnBufParent, state)); err != nil { return err } // no reason to unlock this ever. At this point it's toast. state.Lock() if parentState == nil { return commitToReal(state) } if err = parentState.canApplyLocked(state); err != nil { return err } parentState.commitLocked(state) return nil }
// Add adds a value to the current counter, and returns the old+new values. It // may cause a counter to come into existance. func (Example) Add(c context.Context, r *AddReq) (rsp *AddRsp, err error) { rsp = &AddRsp{} c = prod.Use(c) err = dstore.Get(c).RunInTransaction(func(c context.Context) error { ds := dstore.Get(c) ctr := &Counter{Name: r.Name} if err := ds.Get(ctr); err != nil && err != dstore.ErrNoSuchEntity { return err } rsp.Prev = ctr.Val ctr.Val += r.Delta rsp.Cur = ctr.Val return ds.Put(ctr) }, nil) return }
func mkds(data []*Foo) (under, over *count.DSCounter, ds datastore.Interface) { c := memory.UseWithAppID(context.Background(), "something~else") ds = datastore.Get(c) _, err := ds.AllocateIDs(ds.KeyForObj(data[0]), 100) if err != nil { panic(err) } if err := ds.PutMulti(data); err != nil { panic(err) } c, under = count.FilterRDS(c) c = FilterRDS(c) c, over = count.FilterRDS(c) ds = datastore.Get(c) return }
func testGetMeta(c context.Context, k *dsS.Key) int64 { ds := dsS.Get(c) mg := &MetaGroup{Parent: k.Root()} if err := ds.Get(mg); err != nil { panic(err) } return mg.Version }
func TestRace(t *testing.T) { t.Parallel() c := FilterRDS(memory.Use(context.Background())) ds := datastore.Get(c) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { id := int64(i + 1) wg.Add(1) go func() { defer wg.Done() err := ds.RunInTransaction(func(c context.Context) error { ds := datastore.Get(c) for i := 0; i < 100; i++ { err := ds.RunInTransaction(func(c context.Context) error { ds := datastore.Get(c) ctr := &Counter{ID: id} if err := ds.Get(ctr); err != nil && err != datastore.ErrNoSuchEntity { t.Fatal("bad Get", err) } ctr.Value++ return ds.Put(ctr) }, nil) if err != nil { t.Fatal("bad inner RIT", err) } } return nil }, nil) if err != nil { t.Fatal("bad outer RIT", err) } }() } wg.Wait() }
func TestHuge(t *testing.T) { t.Parallel() Convey("inner txn too big allows outer txn", t, func() { _, _, ds := mkds(dataMultiRoot) So(ds.RunInTransaction(func(c context.Context) error { ds := datastore.Get(c) So(18, fooSetTo(ds), hugeField) So(ds.RunInTransaction(func(c context.Context) error { ds := datastore.Get(c) So(ds.PutMulti(hugeData), ShouldBeNil) return nil }, nil), ShouldErrLike, ErrTransactionTooLarge) return nil }, &datastore.TransactionOptions{XG: true}), ShouldBeNil) So(18, fooShouldHave(ds), hugeField) }) Convey("outer txn too big prevents inner txn", t, func() { _, _, ds := mkds(dataMultiRoot) So(ds.RunInTransaction(func(c context.Context) error { ds := datastore.Get(c) So(ds.PutMulti(hugeData), ShouldBeNil) So(ds.RunInTransaction(func(c context.Context) error { panic("never!") }, nil), ShouldErrLike, ErrTransactionTooLarge) return nil }, &datastore.TransactionOptions{XG: true}), ShouldBeNil) So(1, fooShouldHave(ds), hugeField) }) }
// GetEntityGroupVersion returns the entity group version for the entity group // containing root. If the entity group doesn't exist, this function will return // zero and a nil error. func GetEntityGroupVersion(c context.Context, root dstore.Key) (int64, error) { ds := dstore.Get(c) egm := &EntityGroupMeta{Parent: dskey.Root(root)} err := ds.Get(egm) ret := egm.Version if err == dstore.ErrNoSuchEntity { // this is OK for callers. The version of the entity group is effectively 0 // in this case. err = nil } return ret, err }
func TestCompoundIndexes(t *testing.T) { t.Parallel() idxKey := func(def dsS.IndexDefinition) string { So(def, ShouldNotBeNil) return "idx::" + string(serialize.ToBytes(*def.PrepForIdxTable())) } numItms := func(c *memCollection) uint64 { ret, _ := c.GetTotals() return ret } Convey("Test Compound indexes", t, func() { type Model struct { ID int64 `gae:"$id"` Field1 []string Field2 []int64 } c := Use(context.Background()) ds := dsS.Get(c) t := ds.Testable().(*dsImpl) head := t.data.head So(ds.Put(&Model{1, []string{"hello", "world"}, []int64{10, 11}}), ShouldBeNil) idx := dsS.IndexDefinition{ Kind: "Model", SortBy: []dsS.IndexColumn{ {Property: "Field2"}, }, } coll := head.GetCollection(idxKey(idx)) So(coll, ShouldNotBeNil) So(numItms(coll), ShouldEqual, 2) idx.SortBy[0].Property = "Field1" coll = head.GetCollection(idxKey(idx)) So(coll, ShouldNotBeNil) So(numItms(coll), ShouldEqual, 2) idx.SortBy = append(idx.SortBy, dsS.IndexColumn{Property: "Field1"}) So(head.GetCollection(idxKey(idx)), ShouldBeNil) t.AddIndexes(&idx) coll = head.GetCollection(idxKey(idx)) So(coll, ShouldNotBeNil) So(numItms(coll), ShouldEqual, 4) }) }
// CAS does an atomic compare-and-swap on a counter. func (Example) CAS(c context.Context, r *CASReq) (err error) { success := false c = prod.Use(c) err = dstore.Get(c).RunInTransaction(func(c context.Context) error { ds := dstore.Get(c) ctr := &Counter{Name: r.Name} if err := ds.Get(ctr); err != nil { return err } if ctr.Val == r.OldVal { success = true ctr.Val = r.NewVal return ds.Put(ctr) } success = false return nil }, nil) if err == nil && !success { err = endpoints.ConflictError } return }
// NewDatastore creates a new standalone memory implementation of the datastore, // suitable for embedding for doing in-memory data organization. // // It's configured by default with the following settings: // * AutoIndex(true) // * Consistent(true) // * DisableSpecialEntities(true) // // These settings can of course be changed by using the Testable() interface. func NewDatastore(aid, ns string) (ds.Interface, error) { ctx := UseWithAppID(context.Background(), aid) ctx, err := info.Get(ctx).Namespace(ns) if err != nil { return nil, err } ret := ds.Get(ctx) t := ret.Testable() t.AutoIndex(true) t.Consistent(true) t.DisableSpecialEntities(true) return ret, nil }
// High level test for regression in how zero time is stored, // see https://codereview.chromium.org/1334043003/ func TestDefaultTimeField(t *testing.T) { t.Parallel() Convey("Default time.Time{} can be stored", t, func() { type Model struct { ID int64 `gae:"$id"` Time time.Time } ds := dsS.Get(Use(context.Background())) m := Model{ID: 1} So(ds.Put(&m), ShouldBeNil) // Reset to something non zero to ensure zero is fetched. m.Time = time.Now().UTC() So(ds.Get(&m), ShouldBeNil) So(m.Time.IsZero(), ShouldBeTrue) }) }
func TestGetEntityGroupVersion(t *testing.T) { t.Parallel() Convey("GetEntityGroupVersion", t, func() { c := memory.Use(context.Background()) c, fb := featureBreaker.FilterRDS(c, errors.New("INTERNAL_ERROR")) ds := dstore.Get(c) pm := dstore.PropertyMap{ "$key": {dstore.MkPropertyNI(ds.NewKey("A", "", 0, nil))}, "Val": {dstore.MkProperty(10)}, } So(ds.Put(pm), ShouldBeNil) aKey, ok := pm.GetMetaDefault("key", nil).(dstore.Key) So(ok, ShouldBeTrue) So(aKey, ShouldNotBeNil) v, err := GetEntityGroupVersion(c, aKey) So(err, ShouldBeNil) So(v, ShouldEqual, 1) So(ds.Delete(aKey), ShouldBeNil) v, err = GetEntityGroupVersion(c, ds.NewKey("madeUp", "thing", 0, aKey)) So(err, ShouldBeNil) So(v, ShouldEqual, 2) v, err = GetEntityGroupVersion(c, ds.NewKey("madeUp", "thing", 0, nil)) So(err, ShouldBeNil) So(v, ShouldEqual, 0) fb.BreakFeatures(nil, "GetMulti") v, err = GetEntityGroupVersion(c, aKey) So(err.Error(), ShouldContainSubstring, "INTERNAL_ERROR") }) }
// IsGloballyEnabled checks to see if this filter is enabled globally. // // This checks InstanceEnabledStatic, as well as polls the datastore entity // /dscache,1 (a GlobalConfig instance) // Once every GlobalEnabledCheckInterval. // // For correctness, any error encountered returns true. If this assumed false, // then Put operations might incorrectly invalidate the cache. func IsGloballyEnabled(c context.Context) bool { if !InstanceEnabledStatic { return false } now := clock.Now(c) globalEnabledLock.RLock() nextCheck := globalEnabledNextCheck enabledVal := globalEnabled globalEnabledLock.RUnlock() if now.Before(nextCheck) { return enabledVal } globalEnabledLock.Lock() defer globalEnabledLock.Unlock() // just in case we raced if now.Before(globalEnabledNextCheck) { return globalEnabled } // always go to the default namespace c, err := info.Get(c).Namespace("") if err != nil { return true } cfg := &GlobalConfig{Enable: true} if err := datastore.Get(c).Get(cfg); err != nil && err != datastore.ErrNoSuchEntity { return true } globalEnabled = cfg.Enable globalEnabledNextCheck = now.Add(GlobalEnabledCheckInterval) return globalEnabled }
// List returns a list of all the counters. Note that it's very poorly // implemented! It's completely unpaged. I don't care :). func (Example) List(c context.Context) (rsp *ListRsp, err error) { ds := dstore.Get(prod.Use(c)) rsp = &ListRsp{} err = ds.GetAll(ds.NewQuery("Counter"), &rsp.Counters) return }
func TestDSCache(t *testing.T) { t.Parallel() zeroTime, err := time.Parse("2006-01-02T15:04:05.999999999Z", "2006-01-02T15:04:05.999999999Z") if err != nil { panic(err) } Convey("Test dscache", t, func() { c := mathrand.Set(context.Background(), rand.New(rand.NewSource(1))) clk := testclock.New(zeroTime) c = clock.Set(c, clk) c = memory.Use(c) dsUnder := datastore.Get(c) mc := memcache.Get(c) shardsForKey := func(k *datastore.Key) int { last := k.LastTok() if last.Kind == "shardObj" { return int(last.IntID) } if last.Kind == "noCacheObj" { return 0 } return DefaultShards } numMemcacheItems := func() uint64 { stats, err := mc.Stats() So(err, ShouldBeNil) return stats.Items } Convey("enabled cases", func() { c = FilterRDS(c, shardsForKey) ds := datastore.Get(c) So(dsUnder, ShouldNotBeNil) So(ds, ShouldNotBeNil) So(mc, ShouldNotBeNil) Convey("basically works", func() { pm := datastore.PropertyMap{ "BigData": {datastore.MkProperty([]byte(""))}, "Value": {datastore.MkProperty("hi")}, } encoded := append([]byte{0}, serialize.ToBytes(pm)...) o := object{ID: 1, Value: "hi"} So(ds.Put(&o), ShouldBeNil) o = object{ID: 1} So(dsUnder.Get(&o), ShouldBeNil) So(o.Value, ShouldEqual, "hi") itm, err := mc.Get(MakeMemcacheKey(0, ds.KeyForObj(&o))) So(err, ShouldEqual, memcache.ErrCacheMiss) o = object{ID: 1} So(ds.Get(&o), ShouldBeNil) So(o.Value, ShouldEqual, "hi") itm, err = mc.Get(itm.Key()) So(err, ShouldBeNil) So(itm.Value(), ShouldResemble, encoded) Convey("now we don't need the datastore!", func() { o := object{ID: 1} // delete it, bypassing the cache filter. Don't do this in production // unless you want a crappy cache. So(dsUnder.Delete(ds.KeyForObj(&o)), ShouldBeNil) itm, err := mc.Get(MakeMemcacheKey(0, ds.KeyForObj(&o))) So(err, ShouldBeNil) So(itm.Value(), ShouldResemble, encoded) So(ds.Get(&o), ShouldBeNil) So(o.Value, ShouldEqual, "hi") }) Convey("deleting it properly records that fact, however", func() { o := object{ID: 1} So(ds.Delete(ds.KeyForObj(&o)), ShouldBeNil) itm, err := mc.Get(MakeMemcacheKey(0, ds.KeyForObj(&o))) So(err, ShouldEqual, memcache.ErrCacheMiss) So(ds.Get(&o), ShouldEqual, datastore.ErrNoSuchEntity) itm, err = mc.Get(itm.Key()) So(err, ShouldBeNil) So(itm.Value(), ShouldResemble, []byte{}) // this one hits memcache So(ds.Get(&o), ShouldEqual, datastore.ErrNoSuchEntity) }) }) Convey("compression works", func() { o := object{ID: 2, Value: `¯\_(ツ)_/¯`} data := make([]byte, 4000) for i := range data { const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()" data[i] = alpha[i%len(alpha)] } o.BigData = data So(ds.Put(&o), ShouldBeNil) So(ds.Get(&o), ShouldBeNil) itm, err := mc.Get(MakeMemcacheKey(0, ds.KeyForObj(&o))) So(err, ShouldBeNil) So(itm.Value()[0], ShouldEqual, ZlibCompression) So(len(itm.Value()), ShouldEqual, 653) // a bit smaller than 4k // ensure the next Get comes from the cache So(dsUnder.Delete(ds.KeyForObj(&o)), ShouldBeNil) o = object{ID: 2} So(ds.Get(&o), ShouldBeNil) So(o.Value, ShouldEqual, `¯\_(ツ)_/¯`) So(o.BigData, ShouldResemble, data) }) Convey("transactions", func() { Convey("work", func() { // populate an object @ ID1 So(ds.Put(&object{ID: 1, Value: "something"}), ShouldBeNil) So(ds.Get(&object{ID: 1}), ShouldBeNil) So(ds.Put(&object{ID: 2, Value: "nurbs"}), ShouldBeNil) So(ds.Get(&object{ID: 2}), ShouldBeNil) // memcache now has the wrong value (simulated race) So(dsUnder.Put(&object{ID: 1, Value: "else"}), ShouldBeNil) So(ds.RunInTransaction(func(c context.Context) error { ds := datastore.Get(c) o := &object{ID: 1} So(ds.Get(o), ShouldBeNil) So(o.Value, ShouldEqual, "else") o.Value = "txn" So(ds.Put(o), ShouldBeNil) So(ds.Delete(ds.KeyForObj(&object{ID: 2})), ShouldBeNil) return nil }, &datastore.TransactionOptions{XG: true}), ShouldBeNil) _, err := mc.Get(MakeMemcacheKey(0, ds.KeyForObj(&object{ID: 1}))) So(err, ShouldEqual, memcache.ErrCacheMiss) _, err = mc.Get(MakeMemcacheKey(0, ds.KeyForObj(&object{ID: 2}))) So(err, ShouldEqual, memcache.ErrCacheMiss) o := &object{ID: 1} So(ds.Get(o), ShouldBeNil) So(o.Value, ShouldEqual, "txn") }) Convey("errors don't invalidate", func() { // populate an object @ ID1 So(ds.Put(&object{ID: 1, Value: "something"}), ShouldBeNil) So(ds.Get(&object{ID: 1}), ShouldBeNil) So(numMemcacheItems(), ShouldEqual, 1) So(ds.RunInTransaction(func(c context.Context) error { ds := datastore.Get(c) o := &object{ID: 1} So(ds.Get(o), ShouldBeNil) So(o.Value, ShouldEqual, "something") o.Value = "txn" So(ds.Put(o), ShouldBeNil) return errors.New("OH NOES") }, nil).Error(), ShouldContainSubstring, "OH NOES") // memcache still has the original So(numMemcacheItems(), ShouldEqual, 1) So(dsUnder.Delete(ds.KeyForObj(&object{ID: 1})), ShouldBeNil) o := &object{ID: 1} So(ds.Get(o), ShouldBeNil) So(o.Value, ShouldEqual, "something") }) }) Convey("control", func() { Convey("per-model bypass", func() { type model struct { ID string `gae:"$id"` UseDSCache datastore.Toggle `gae:"$dscache.enable,false"` Value string } itms := []model{ {ID: "hi", Value: "something"}, {ID: "there", Value: "else", UseDSCache: datastore.On}, } So(ds.PutMulti(itms), ShouldBeNil) So(ds.GetMulti(itms), ShouldBeNil) So(numMemcacheItems(), ShouldEqual, 1) }) Convey("per-key shard count", func() { s := &shardObj{ID: 4, Value: "hi"} So(ds.Put(s), ShouldBeNil) So(ds.Get(s), ShouldBeNil) So(numMemcacheItems(), ShouldEqual, 1) for i := 0; i < 20; i++ { So(ds.Get(s), ShouldBeNil) } So(numMemcacheItems(), ShouldEqual, 4) }) Convey("per-key cache disablement", func() { n := &noCacheObj{ID: "nurbs", Value: true} So(ds.Put(n), ShouldBeNil) So(ds.Get(n), ShouldBeNil) So(numMemcacheItems(), ShouldEqual, 0) }) Convey("per-model expiration", func() { type model struct { ID int64 `gae:"$id"` DSCacheExp int64 `gae:"$dscache.expiration,7"` Value string } So(ds.Put(&model{ID: 1, Value: "mooo"}), ShouldBeNil) So(ds.Get(&model{ID: 1}), ShouldBeNil) itm, err := mc.Get(MakeMemcacheKey(0, ds.KeyForObj(&model{ID: 1}))) So(err, ShouldBeNil) clk.Add(10 * time.Second) _, err = mc.Get(itm.Key()) So(err, ShouldEqual, memcache.ErrCacheMiss) }) }) Convey("screw cases", func() { Convey("memcache contains bogus value (simulated failed AddMulti)", func() { o := &object{ID: 1, Value: "spleen"} So(ds.Put(o), ShouldBeNil) sekret := []byte("I am a banana") itm := mc.NewItem(MakeMemcacheKey(0, ds.KeyForObj(o))).SetValue(sekret) So(mc.Set(itm), ShouldBeNil) o = &object{ID: 1} So(ds.Get(o), ShouldBeNil) So(o.Value, ShouldEqual, "spleen") itm, err := mc.Get(itm.Key()) So(err, ShouldBeNil) So(itm.Flags(), ShouldEqual, ItemUKNONWN) So(itm.Value(), ShouldResemble, sekret) }) Convey("memcache contains bogus value (corrupt entry)", func() { o := &object{ID: 1, Value: "spleen"} So(ds.Put(o), ShouldBeNil) sekret := []byte("I am a banana") itm := (mc.NewItem(MakeMemcacheKey(0, ds.KeyForObj(o))). SetValue(sekret). SetFlags(uint32(ItemHasData))) So(mc.Set(itm), ShouldBeNil) o = &object{ID: 1} So(ds.Get(o), ShouldBeNil) So(o.Value, ShouldEqual, "spleen") itm, err := mc.Get(itm.Key()) So(err, ShouldBeNil) So(itm.Flags(), ShouldEqual, ItemHasData) So(itm.Value(), ShouldResemble, sekret) }) Convey("other entity has the lock", func() { o := &object{ID: 1, Value: "spleen"} So(ds.Put(o), ShouldBeNil) sekret := []byte("r@vmarod!#)%9T") itm := (mc.NewItem(MakeMemcacheKey(0, ds.KeyForObj(o))). SetValue(sekret). SetFlags(uint32(ItemHasLock))) So(mc.Set(itm), ShouldBeNil) o = &object{ID: 1} So(ds.Get(o), ShouldBeNil) So(o.Value, ShouldEqual, "spleen") itm, err := mc.Get(itm.Key()) So(err, ShouldBeNil) So(itm.Flags(), ShouldEqual, ItemHasLock) So(itm.Value(), ShouldResemble, sekret) }) Convey("massive entities can't be cached", func() { o := &object{ID: 1, Value: "spleen"} mr := mathrand.Get(c) numRounds := (internalValueSizeLimit / 8) * 2 buf := bytes.Buffer{} for i := 0; i < numRounds; i++ { So(binary.Write(&buf, binary.LittleEndian, mr.Int63()), ShouldBeNil) } o.BigData = buf.Bytes() So(ds.Put(o), ShouldBeNil) o.BigData = nil So(ds.Get(o), ShouldBeNil) itm, err := mc.Get(MakeMemcacheKey(0, ds.KeyForObj(o))) So(err, ShouldBeNil) // Is locked until the next put, forcing all access to the datastore. So(itm.Value(), ShouldResemble, []byte{}) So(itm.Flags(), ShouldEqual, ItemHasLock) o.BigData = []byte("hi :)") So(ds.Put(o), ShouldBeNil) So(ds.Get(o), ShouldBeNil) itm, err = mc.Get(itm.Key()) So(err, ShouldBeNil) So(itm.Flags(), ShouldEqual, ItemHasData) }) Convey("failure on Setting memcache locks is a hard stop", func() { c, fb := featureBreaker.FilterMC(c, nil) fb.BreakFeatures(nil, "SetMulti") ds := datastore.Get(c) So(ds.Put(&object{ID: 1}).Error(), ShouldContainSubstring, "SetMulti") }) Convey("failure on Setting memcache locks in a transaction is a hard stop", func() { c, fb := featureBreaker.FilterMC(c, nil) fb.BreakFeatures(nil, "SetMulti") ds := datastore.Get(c) So(ds.RunInTransaction(func(c context.Context) error { So(datastore.Get(c).Put(&object{ID: 1}), ShouldBeNil) // no problems here... memcache operations happen after the function // body quits. return nil }, nil).Error(), ShouldContainSubstring, "SetMulti") }) }) Convey("misc", func() { Convey("verify numShards caps at MaxShards", func() { sc := supportContext{shardsForKey: shardsForKey} So(sc.numShards(ds.KeyForObj(&shardObj{ID: 9001})), ShouldEqual, MaxShards) }) Convey("CompressionType.String", func() { So(NoCompression.String(), ShouldEqual, "NoCompression") So(ZlibCompression.String(), ShouldEqual, "ZlibCompression") So(CompressionType(100).String(), ShouldEqual, "UNKNOWN_CompressionType(100)") }) }) }) Convey("disabled cases", func() { defer func() { globalEnabled = true }() So(IsGloballyEnabled(c), ShouldBeTrue) So(SetGlobalEnable(c, false), ShouldBeNil) // twice is a nop So(SetGlobalEnable(c, false), ShouldBeNil) // but it takes 5 minutes to kick in So(IsGloballyEnabled(c), ShouldBeTrue) clk.Add(time.Minute*5 + time.Second) So(IsGloballyEnabled(c), ShouldBeFalse) So(mc.Set(mc.NewItem("test").SetValue([]byte("hi"))), ShouldBeNil) So(numMemcacheItems(), ShouldEqual, 1) So(SetGlobalEnable(c, true), ShouldBeNil) // memcache gets flushed as a side effect So(numMemcacheItems(), ShouldEqual, 0) // Still takes 5 minutes to kick in So(IsGloballyEnabled(c), ShouldBeFalse) clk.Add(time.Minute*5 + time.Second) So(IsGloballyEnabled(c), ShouldBeTrue) }) }) }
func TestTaskQueue(t *testing.T) { t.Parallel() Convey("TaskQueue", t, func() { now := time.Date(2000, time.January, 1, 1, 1, 1, 1, time.UTC) c, tc := testclock.UseTime(context.Background(), now) c = mathrand.Set(c, rand.New(rand.NewSource(clock.Now(c).UnixNano()))) c = Use(c) tq := tqS.Get(c) tqt := tq.Raw().Testable() So(tqt, ShouldNotBeNil) So(tq, ShouldNotBeNil) Convey("implements TQMultiReadWriter", func() { Convey("Add", func() { t := tq.NewTask("/hello/world") Convey("works", func() { t.Delay = 4 * time.Second t.Header = http.Header{} t.Header.Add("Cat", "tabby") t.Payload = []byte("watwatwat") t.RetryOptions = &tqS.RetryOptions{AgeLimit: 7 * time.Second} So(tq.Add(t, ""), ShouldBeNil) name := "Z_UjshxM9ecyMQfGbZmUGOEcgxWU0_5CGLl_-RntudwAw2DqQ5-58bzJiWQN4OKzeuUb9O4JrPkUw2rOvk2Ax46THojnQ6avBQgZdrKcJmrwQ6o4qKfJdiyUbGXvy691yRfzLeQhs6cBhWrgf3wH-VPMcA4SC-zlbJ2U8An7I0zJQA5nBFnMNoMgT-2peGoay3rCSbj4z9VFFm9kS_i6JCaQH518ujLDSNCYdjTq6B6lcWrZAh0U_q3a1S2nXEwrKiw_t9MTNQFgAQZWyGBbvZQPmeRYtu8SPaWzTfd25v_YWgBuVL2rRSPSMvlDwE04nNdtvVzE8vNNiA1zRimmdzKeqATQF9_ReUvj4D7U8dcS703DZWfKMBLgBffY9jqCassOOOw77V72Oq5EVauUw3Qw0L6bBsfM9FtahTKUdabzRZjXUoze3EK4KXPt3-wdidau-8JrVf2XFocjjZbwHoxcGvbtT3b4nGLDlgwdC00bwaFBZWff" So(tqt.GetScheduledTasks()["default"][name], ShouldResemble, &tqS.Task{ ETA: now.Add(4 * time.Second), Header: http.Header{"Cat": []string{"tabby"}}, Method: "POST", Name: name, Path: "/hello/world", Payload: []byte("watwatwat"), RetryOptions: &tqS.RetryOptions{AgeLimit: 7 * time.Second}, }) }) Convey("picks up namespace", func() { c, err := info.Get(c).Namespace("coolNamespace") So(err, ShouldBeNil) tq = tqS.Get(c) t := tq.NewTask("") So(tq.Add(t, ""), ShouldBeNil) So(t.Header, ShouldResemble, http.Header{ "X-Appengine-Current-Namespace": {"coolNamespace"}, }) }) Convey("cannot add to bad queues", func() { So(tq.Add(nil, "waaat").Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") Convey("but you can add Queues when testing", func() { tqt.CreateQueue("waaat") So(tq.Add(t, "waaat"), ShouldBeNil) Convey("you just can't add them twice", func() { So(func() { tqt.CreateQueue("waaat") }, ShouldPanic) }) }) }) Convey("supplies a URL if it's missing", func() { t.Path = "" So(tq.Add(t, ""), ShouldBeNil) So(t.Path, ShouldEqual, "/_ah/queue/default") }) Convey("cannot add twice", func() { t.Name = "bob" So(tq.Add(t, ""), ShouldBeNil) // can't add the same one twice! So(tq.Add(t, ""), ShouldEqual, tqS.ErrTaskAlreadyAdded) }) Convey("cannot add deleted task", func() { t.Name = "bob" So(tq.Add(t, ""), ShouldBeNil) So(tq.Delete(t, ""), ShouldBeNil) // can't add a deleted task! So(tq.Add(t, ""), ShouldEqual, tqS.ErrTaskAlreadyAdded) }) Convey("cannot set ETA+Delay", func() { t.ETA = clock.Now(c).Add(time.Hour) tc.Add(time.Second) t.Delay = time.Hour So(func() { tq.Add(t, "") }, ShouldPanic) }) Convey("must use a reasonable method", func() { t.Method = "Crystal" So(tq.Add(t, "").Error(), ShouldContainSubstring, "bad method") }) Convey("payload gets dumped for non POST/PUT methods", func() { t.Method = "HEAD" t.Payload = []byte("coool") So(tq.Add(t, ""), ShouldBeNil) So(t.Payload, ShouldBeNil) }) Convey("invalid names are rejected", func() { t.Name = "happy times" So(tq.Add(t, "").Error(), ShouldContainSubstring, "INVALID_TASK_NAME") }) Convey("AddMulti also works", func() { t2 := t.Duplicate() t2.Path = "/hi/city" expect := []*tqS.Task{t, t2} So(tq.AddMulti(expect, "default"), ShouldBeNil) So(len(expect), ShouldEqual, 2) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 2) for i := range expect { Convey(fmt.Sprintf("task %d: %s", i, expect[i].Path), func() { So(expect[i].Method, ShouldEqual, "POST") So(expect[i].ETA, ShouldHappenOnOrBefore, now) So(len(expect[i].Name), ShouldEqual, 500) }) } Convey("stats work too", func() { delay := -time.Second * 400 t := tq.NewTask("/somewhere") t.Delay = delay So(tq.Add(t, ""), ShouldBeNil) stats, err := tq.Stats("") So(err, ShouldBeNil) So(stats[0].Tasks, ShouldEqual, 3) So(stats[0].OldestETA, ShouldHappenOnOrBefore, clock.Now(c).Add(delay)) _, err = tq.Stats("noexist") So(err.Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") }) Convey("can purge all tasks", func() { So(tq.Add(&tqS.Task{Path: "/wut/nerbs"}, ""), ShouldBeNil) So(tq.Purge(""), ShouldBeNil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) Convey("purging a queue which DNE fails", func() { So(tq.Purge("noexist").Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") }) }) }) }) Convey("Delete", func() { t := &tqS.Task{Path: "/hello/world"} So(tq.Add(t, ""), ShouldBeNil) Convey("works", func() { err := tq.Delete(t, "") So(err, ShouldBeNil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 1) So(tqt.GetTombstonedTasks()["default"][t.Name], ShouldResemble, t) }) Convey("cannot delete a task twice", func() { So(tq.Delete(t, ""), ShouldBeNil) So(tq.Delete(t, "").Error(), ShouldContainSubstring, "TOMBSTONED_TASK") Convey("but you can if you do a reset", func() { tqt.ResetTasks() So(tq.Add(t, ""), ShouldBeNil) So(tq.Delete(t, ""), ShouldBeNil) }) }) Convey("cannot delete from bogus queues", func() { err := tq.Delete(t, "wat") So(err.Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") }) Convey("cannot delete a missing task", func() { t.Name = "tarntioarenstyw" err := tq.Delete(t, "") So(err.Error(), ShouldContainSubstring, "UNKNOWN_TASK") }) Convey("DeleteMulti also works", func() { t2 := t.Duplicate() t2.Name = "" t2.Path = "/hi/city" So(tq.Add(t2, ""), ShouldBeNil) Convey("usually works", func() { So(tq.DeleteMulti([]*tqS.Task{t, t2}, ""), ShouldBeNil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 2) }) }) }) }) Convey("works with transactions", func() { t := &tqS.Task{Path: "/hello/world"} So(tq.Add(t, ""), ShouldBeNil) t2 := &tqS.Task{Path: "/hi/city"} So(tq.Add(t2, ""), ShouldBeNil) So(tq.Delete(t2, ""), ShouldBeNil) Convey("can view regular tasks", func() { dsS.Get(c).RunInTransaction(func(c context.Context) error { tqt := tqS.GetRaw(c).Testable() So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"], ShouldBeNil) return nil }, nil) }) Convey("can add a new task", func() { t3 := &tqS.Task{Path: "/sandwitch/victory"} err := dsS.Get(c).RunInTransaction(func(c context.Context) error { tq := tqS.Get(c) tqt := tq.Raw().Testable() So(tq.Add(t3, ""), ShouldBeNil) So(t3.Name, ShouldEqual, "") So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"][0], ShouldResemble, t3) return nil }, nil) So(err, ShouldBeNil) for _, tsk := range tqt.GetScheduledTasks()["default"] { if tsk.Name == t.Name { So(tsk, ShouldResemble, t) } else { tsk.Name = "" So(tsk, ShouldResemble, t3) } } So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"], ShouldBeNil) }) Convey("can add a new task (but reset the state in a test)", func() { t3 := &tqS.Task{Path: "/sandwitch/victory"} ttq := tqS.Interface(nil) dsS.Get(c).RunInTransaction(func(c context.Context) error { ttq = tqS.Get(c) tqt := ttq.Raw().Testable() So(ttq.Add(t3, ""), ShouldBeNil) So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"][0], ShouldResemble, t3) tqt.ResetTasks() So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) return nil }, nil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) Convey("and reusing a closed context is bad times", func() { So(ttq.Add(nil, "").Error(), ShouldContainSubstring, "expired") }) }) Convey("you can AddMulti as well", func() { dsS.Get(c).RunInTransaction(func(c context.Context) error { tq := tqS.Get(c) tqt := tq.Raw().Testable() t.Name = "" tasks := []*tqS.Task{t.Duplicate(), t.Duplicate(), t.Duplicate()} So(tq.AddMulti(tasks, ""), ShouldBeNil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 1) So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 3) return nil }, nil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 4) So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) }) Convey("unless you add too many things", func() { dsS.Get(c).RunInTransaction(func(c context.Context) error { for i := 0; i < 5; i++ { So(tqS.Get(c).Add(t.Duplicate(), ""), ShouldBeNil) } So(tqS.Get(c).Add(t, "").Error(), ShouldContainSubstring, "BAD_REQUEST") return nil }, nil) }) Convey("unless you Add to a bad queue", func() { dsS.Get(c).RunInTransaction(func(c context.Context) error { So(tqS.Get(c).Add(t, "meat").Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") Convey("unless you add it!", func() { tqS.GetRaw(c).Testable().CreateQueue("meat") So(tqS.Get(c).Add(t, "meat"), ShouldBeNil) }) return nil }, nil) }) Convey("No other features are available, however", func() { dsS.Get(c).RunInTransaction(func(c context.Context) error { So(tqS.Get(c).Delete(t, "").Error(), ShouldContainSubstring, "cannot DeleteMulti from a transaction") So(tqS.Get(c).Purge("").Error(), ShouldContainSubstring, "cannot Purge from a transaction") _, err := tqS.Get(c).Stats("") So(err.Error(), ShouldContainSubstring, "cannot Stats from a transaction") return nil }, nil) }) Convey("adding a new task only happens if we don't errout", func() { dsS.Get(c).RunInTransaction(func(c context.Context) error { t3 := tq.NewTask("/sandwitch/victory") So(tqS.Get(c).Add(t3, ""), ShouldBeNil) return fmt.Errorf("nooooo") }, nil) So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"], ShouldBeNil) }) Convey("likewise, a panic doesn't schedule anything", func() { func() { defer func() { recover() }() dsS.Get(c).RunInTransaction(func(c context.Context) error { tq := tqS.Get(c) So(tq.Add(tq.NewTask("/sandwitch/victory"), ""), ShouldBeNil) panic(fmt.Errorf("nooooo")) }, nil) }() So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"], ShouldBeNil) }) }) }) }
func TestBasicDatastore(t *testing.T) { t.Parallel() Convey("basic", t, func() { inst, err := aetest.NewInstance(&aetest.Options{ StronglyConsistentDatastore: true, }) So(err, ShouldBeNil) defer inst.Close() req, err := inst.NewRequest("GET", "/", nil) So(err, ShouldBeNil) ctx := Use(context.Background(), req) ds := datastore.Get(ctx) mc := memcache.Get(ctx) inf := info.Get(ctx) Convey("logging allows you to tweak the level", func() { // You have to visually confirm that this actually happens in the stdout // of the test... yeah I know. logging.Debugf(ctx, "SHOULD NOT SEE") logging.Infof(ctx, "SHOULD SEE") ctx = logging.SetLevel(ctx, logging.Debug) logging.Debugf(ctx, "SHOULD SEE") logging.Infof(ctx, "SHOULD SEE (2)") }) Convey("Can probe/change Namespace", func() { So(inf.GetNamespace(), ShouldEqual, "") ctx, err = inf.Namespace("wat") So(err, ShouldBeNil) inf = info.Get(ctx) So(inf.GetNamespace(), ShouldEqual, "wat") ds = datastore.Get(ctx) So(ds.MakeKey("Hello", "world").Namespace(), ShouldEqual, "wat") }) Convey("Can get non-transactional context", func() { ctx, err := inf.Namespace("foo") So(err, ShouldBeNil) ds = datastore.Get(ctx) inf = info.Get(ctx) ds.RunInTransaction(func(ctx context.Context) error { So(ds.MakeKey("Foo", "bar").Namespace(), ShouldEqual, "foo") So(ds.Put(&TestStruct{ValueI: []int64{100}}), ShouldBeNil) err = datastore.GetNoTxn(ctx).RunInTransaction(func(ctx context.Context) error { ds = datastore.Get(ctx) So(ds.MakeKey("Foo", "bar").Namespace(), ShouldEqual, "foo") So(ds.Put(&TestStruct{ValueI: []int64{100}}), ShouldBeNil) return nil }, nil) So(err, ShouldBeNil) return nil }, nil) }) Convey("Can Put/Get", func() { orig := TestStruct{ ValueI: []int64{1, 7, 946688461000000, 996688461000000}, ValueB: []bool{true, false}, ValueS: []string{"hello", "world"}, ValueF: []float64{1.0, 7.0, 946688461000000.0, 996688461000000.0}, ValueBS: [][]byte{ []byte("allo"), []byte("hello"), []byte("world"), []byte("zurple"), }, ValueK: []*datastore.Key{ ds.NewKey("Something", "Cool", 0, nil), ds.NewKey("Something", "", 1, nil), ds.NewKey("Something", "Recursive", 0, ds.NewKey("Parent", "", 2, nil)), }, ValueBK: []blobstore.Key{"bellow", "hello"}, ValueGP: []datastore.GeoPoint{ {Lat: 120.7, Lng: 95.5}, }, } So(ds.Put(&orig), ShouldBeNil) ret := TestStruct{ID: orig.ID} So(ds.Get(&ret), ShouldBeNil) So(ret, ShouldResemble, orig) // can't be sure the indexes have caught up... so sleep time.Sleep(time.Second) Convey("Can query", func() { q := datastore.NewQuery("TestStruct") ds.Run(q, func(ts *TestStruct) { So(*ts, ShouldResemble, orig) }) count, err := ds.Count(q) So(err, ShouldBeNil) So(count, ShouldEqual, 1) }) Convey("Can project", func() { q := datastore.NewQuery("TestStruct").Project("ValueS") rslts := []datastore.PropertyMap{} So(ds.GetAll(q, &rslts), ShouldBeNil) So(rslts, ShouldResemble, []datastore.PropertyMap{ { "$key": {mpNI(ds.KeyForObj(&orig))}, "ValueS": {mp("hello")}, }, { "$key": {mpNI(ds.KeyForObj(&orig))}, "ValueS": {mp("world")}, }, }) q = datastore.NewQuery("TestStruct").Project("ValueBS") rslts = []datastore.PropertyMap{} So(ds.GetAll(q, &rslts), ShouldBeNil) So(rslts, ShouldResemble, []datastore.PropertyMap{ { "$key": {mpNI(ds.KeyForObj(&orig))}, "ValueBS": {mp("allo")}, }, { "$key": {mpNI(ds.KeyForObj(&orig))}, "ValueBS": {mp("hello")}, }, { "$key": {mpNI(ds.KeyForObj(&orig))}, "ValueBS": {mp("world")}, }, { "$key": {mpNI(ds.KeyForObj(&orig))}, "ValueBS": {mp("zurple")}, }, }) count, err := ds.Count(q) So(err, ShouldBeNil) So(count, ShouldEqual, 4) }) }) Convey("Can Put/Get (time)", func() { // time comparisons in Go are wonky, so this is pulled out pm := datastore.PropertyMap{ "$key": {mpNI(ds.NewKey("Something", "value", 0, nil))}, "Time": { mp(time.Date(1938, time.January, 1, 1, 1, 1, 1, time.UTC)), mp(time.Time{}), }, } So(ds.Put(&pm), ShouldBeNil) rslt := datastore.PropertyMap{} rslt.SetMeta("key", ds.KeyForObj(pm)) So(ds.Get(&rslt), ShouldBeNil) So(pm["Time"][0].Value(), ShouldResemble, rslt["Time"][0].Value()) q := datastore.NewQuery("Something").Project("Time") all := []datastore.PropertyMap{} So(ds.GetAll(q, &all), ShouldBeNil) So(len(all), ShouldEqual, 2) prop := all[0]["Time"][0] So(prop.Type(), ShouldEqual, datastore.PTInt) tval, err := prop.Project(datastore.PTTime) So(err, ShouldBeNil) So(tval, ShouldResemble, time.Time{}) tval, err = all[1]["Time"][0].Project(datastore.PTTime) So(err, ShouldBeNil) So(tval, ShouldResemble, pm["Time"][0].Value()) ent := datastore.PropertyMap{ "$key": {mpNI(ds.MakeKey("Something", "value"))}, } So(ds.Get(&ent), ShouldBeNil) So(ent["Time"], ShouldResemble, pm["Time"]) }) Convey("memcache: Set (nil) is the same as Set ([]byte{})", func() { So(mc.Set(mc.NewItem("bob")), ShouldBeNil) // normally would panic because Value is nil bob, err := mc.Get("bob") So(err, ShouldBeNil) So(bob.Value(), ShouldResemble, []byte{}) }) }) }
func TestQueryExecution(t *testing.T) { t.Parallel() Convey("Test query execution", t, func() { c, err := info.Get(Use(context.Background())).Namespace("ns") if err != nil { panic(err) } data := ds.Get(c) testing := data.Raw().Testable() for _, tc := range queryExecutionTests { Convey(tc.name, func() { for i, stage := range tc.test { // outside of Convey, since these must always happen testing.CatchupIndexes() testing.AddIndexes(stage.addIdxs...) if err := data.PutMulti(stage.putEnts); err != nil { // prevent Convey from thinking this assertion should show up in // every test loop. panic(err) } if err := data.DeleteMulti(stage.delEnts); err != nil { panic(err) } Convey(fmt.Sprintf("stage %d", i), func() { for j, expect := range stage.expect { runner := func(f func(ic context.Context) error, _ *ds.TransactionOptions) error { return f(c) } if expect.inTxn { runner = data.RunInTransaction } if expect.keys != nil { runner(func(c context.Context) error { data := ds.Get(c) Convey(fmt.Sprintf("expect %d (keys)", j), func() { rslt := []ds.Key(nil) So(data.GetAll(expect.q, &rslt), ShouldBeNil) So(len(rslt), ShouldEqual, len(expect.keys)) for i, r := range rslt { So(r, ShouldResemble, expect.keys[i]) } }) return nil }, &ds.TransactionOptions{XG: true}) } if expect.get != nil { Convey(fmt.Sprintf("expect %d (data)", j), func() { runner(func(c context.Context) error { rslt := []ds.PropertyMap(nil) So(data.GetAll(expect.q, &rslt), ShouldBeNil) So(len(rslt), ShouldEqual, len(expect.get)) for i, r := range rslt { So(r, ShouldResemble, expect.get[i]) } return nil }, &ds.TransactionOptions{XG: true}) }) } } for j, fn := range stage.extraFns { Convey(fmt.Sprintf("extraFn %d", j), func() { fn(c) }) } }) } }) } }) }
"When", 946688461000000), pmap("$key", key("Kind", 3), NEXT, "When", 996688461000000), }}, // Original (complex) types are retained when getting the full value. {q: nq("Kind").Order("When"), get: []ds.PropertyMap{ stage1Data[1], stage1Data[3], stage1Data[2], }}, }, extraFns: []func(context.Context){ func(c context.Context) { data := ds.Get(c) curs := ds.Cursor(nil) q := nq("").Filter("__key__ >", key("Kind", 2)) err := data.Run(q, func(pm ds.PropertyMap, gc ds.CursorCB) bool { So(pm, ShouldResemble, pmap( "$key", key("__entity_group__", 1, key("Kind", 2)), NEXT, "__version__", 1)) err := error(nil) curs, err = gc() So(err, ShouldBeNil) return false }) So(err, ShouldBeNil)
func TestQueryExecution(t *testing.T) { t.Parallel() Convey("Test query execution", t, func() { c, err := info.Get(Use(context.Background())).Namespace("ns") if err != nil { panic(err) } So(info.Get(c).FullyQualifiedAppID(), ShouldEqual, "dev~app") So(info.Get(c).GetNamespace(), ShouldEqual, "ns") data := ds.Get(c) testing := data.Testable() for _, tc := range queryExecutionTests { Convey(tc.name, func() { for i, stage := range tc.test { // outside of Convey, since these must always happen testing.CatchupIndexes() testing.AddIndexes(stage.addIdxs...) if err := data.PutMulti(stage.putEnts); err != nil { // prevent Convey from thinking this assertion should show up in // every test loop. panic(err) } if err := data.DeleteMulti(stage.delEnts); err != nil { panic(err) } Convey(fmt.Sprintf("stage %d", i), func() { for j, expect := range stage.expect { runner := func(f func(ic context.Context) error, _ *ds.TransactionOptions) error { return f(c) } if expect.inTxn { runner = data.RunInTransaction } if expect.count == 0 { if len(expect.keys) > 0 { expect.count = len(expect.keys) } else { expect.count = len(expect.get) } } if expect.keys != nil { Convey(fmt.Sprintf("expect %d (keys)", j), func() { err := runner(func(c context.Context) error { data := ds.Get(c) count, err := data.Count(expect.q) So(err, ShouldBeNil) So(count, ShouldEqual, expect.count) rslt := []*ds.Key(nil) So(data.GetAll(expect.q, &rslt), ShouldBeNil) So(len(rslt), ShouldEqual, len(expect.keys)) for i, r := range rslt { So(r, ShouldResemble, expect.keys[i]) } return nil }, &ds.TransactionOptions{XG: true}) So(err, ShouldBeNil) }) } if expect.get != nil { Convey(fmt.Sprintf("expect %d (data)", j), func() { err := runner(func(c context.Context) error { data := ds.Get(c) count, err := data.Count(expect.q) So(err, ShouldBeNil) So(count, ShouldEqual, expect.count) rslt := []ds.PropertyMap(nil) So(data.GetAll(expect.q, &rslt), ShouldBeNil) So(len(rslt), ShouldEqual, len(expect.get)) for i, r := range rslt { So(r, ShouldResembleV, expect.get[i]) } return nil }, &ds.TransactionOptions{XG: true}) So(err, ShouldBeNil) }) } } for j, fn := range stage.extraFns { Convey(fmt.Sprintf("extraFn %d", j), func() { fn(c) }) } }) } }) } }) Convey("Test AutoIndex", t, func() { c, err := info.Get(Use(context.Background())).Namespace("ns") if err != nil { panic(err) } data := ds.Get(c) testing := data.Testable() testing.Consistent(true) So(data.Put(pmap("$key", key("Kind", 1), Next, "Val", 1, 2, 3, Next, "Extra", "hello", )), ShouldBeNil) So(data.Put(pmap("$key", key("Kind", 2), Next, "Val", 2, 3, 9, Next, "Extra", "ace", "hello", "there", )), ShouldBeNil) q := nq("Kind").Gt("Val", 2).Order("Val", "Extra") count, err := data.Count(q) So(err, ShouldErrLike, "Insufficient indexes") testing.AutoIndex(true) count, err = data.Count(q) So(err, ShouldBeNil) So(count, ShouldEqual, 2) }) }
func TestContextAccess(t *testing.T) { t.Parallel() // p is a function which recovers an error and then immediately panics with // the contained string. It's defer'd in each test so that we can use the // ShouldPanicWith assertion (which does an == comparison and not // a reflect.DeepEquals comparison). p := func() { panic(recover().(error).Error()) } Convey("Context Access", t, func() { c := context.Background() Convey("blank", func() { So(dsS.GetRaw(c), ShouldBeNil) So(mcS.GetRaw(c), ShouldBeNil) So(tqS.GetRaw(c), ShouldBeNil) So(infoS.Get(c), ShouldBeNil) }) // needed for everything else c = infoS.Set(c, Info()) Convey("Info", func() { So(infoS.Get(c), ShouldNotBeNil) So(func() { defer p() infoS.Get(c).Datacenter() }, ShouldPanicWith, "dummy: method Info.Datacenter is not implemented") }) Convey("Datastore", func() { c = dsS.SetRaw(c, Datastore()) So(dsS.Get(c), ShouldNotBeNil) So(func() { defer p() _, _ = dsS.Get(c).DecodeCursor("wut") }, ShouldPanicWith, "dummy: method Datastore.DecodeCursor is not implemented") }) Convey("Memcache", func() { c = mcS.SetRaw(c, Memcache()) So(mcS.Get(c), ShouldNotBeNil) So(func() { defer p() _ = mcS.Get(c).Add(nil) }, ShouldPanicWith, "dummy: method Memcache.AddMulti is not implemented") }) Convey("TaskQueue", func() { c = tqS.SetRaw(c, TaskQueue()) So(tqS.Get(c), ShouldNotBeNil) So(func() { defer p() _ = tqS.Get(c).Purge("") }, ShouldPanicWith, "dummy: method TaskQueue.Purge is not implemented") }) Convey("User", func() { c = userS.Set(c, User()) So(userS.Get(c), ShouldNotBeNil) So(func() { defer p() _ = userS.Get(c).IsAdmin() }, ShouldPanicWith, "dummy: method User.IsAdmin is not implemented") }) Convey("Mail", func() { c = mailS.Set(c, Mail()) So(mailS.Get(c), ShouldNotBeNil) So(func() { defer p() _ = mailS.Get(c).Send(nil) }, ShouldPanicWith, "dummy: method Mail.Send is not implemented") }) }) }
func TestCount(t *testing.T) { t.Parallel() Convey("Test Count filter", t, func() { c, fb := featureBreaker.FilterRDS(memory.Use(context.Background()), nil) c, ctr := FilterRDS(c) So(c, ShouldNotBeNil) So(ctr, ShouldNotBeNil) ds := datastore.Get(c) vals := []datastore.PropertyMap{{ "Val": {datastore.MkProperty(100)}, "$key": {datastore.MkPropertyNI(ds.NewKey("Kind", "", 1, nil))}, }} Convey("Calling a ds function should reflect in counter", func() { So(ds.PutMulti(vals), ShouldBeNil) So(ctr.PutMulti.Successes(), ShouldEqual, 1) Convey("effects are cumulative", func() { So(ds.PutMulti(vals), ShouldBeNil) So(ctr.PutMulti.Successes(), ShouldEqual, 2) Convey("even within transactions", func() { die(ds.RunInTransaction(func(c context.Context) error { ds := datastore.Get(c) So(ds.PutMulti(append(vals, vals[0])), ShouldBeNil) return nil }, nil)) }) }) }) Convey("errors count against errors", func() { fb.BreakFeatures(nil, "GetMulti") So(ds.GetMulti(vals), ShouldErrLike, `"GetMulti" is broken`) So(ctr.GetMulti.Errors(), ShouldEqual, 1) fb.UnbreakFeatures("GetMulti") So(ds.PutMulti(vals), ShouldBeNil) die(ds.GetMulti(vals)) So(ctr.GetMulti.Errors(), ShouldEqual, 1) So(ctr.GetMulti.Successes(), ShouldEqual, 1) So(ctr.GetMulti.Total(), ShouldEqual, 2) }) }) Convey("works for memcache", t, func() { c, ctr := FilterMC(memory.Use(context.Background())) So(c, ShouldNotBeNil) So(ctr, ShouldNotBeNil) mc := memcache.Get(c) die(mc.Set(mc.NewItem("hello").SetValue([]byte("sup")))) _, err := mc.Get("Wat") So(err, ShouldNotBeNil) _, err = mc.Get("hello") die(err) So(ctr.SetMulti, shouldHaveSuccessesAndErrors, 1, 0) So(ctr.GetMulti, shouldHaveSuccessesAndErrors, 2, 0) So(ctr.NewItem, shouldHaveSuccessesAndErrors, 3, 0) }) Convey("works for taskqueue", t, func() { c, ctr := FilterTQ(memory.Use(context.Background())) So(c, ShouldNotBeNil) So(ctr, ShouldNotBeNil) tq := taskqueue.Get(c) die(tq.Add(&taskqueue.Task{Name: "wat"}, "")) So(tq.Add(&taskqueue.Task{Name: "wat"}, "DNE_QUEUE"), ShouldErrLike, "UNKNOWN_QUEUE") So(ctr.AddMulti, shouldHaveSuccessesAndErrors, 1, 1) }) Convey("works for global info", t, func() { c, fb := featureBreaker.FilterGI(memory.Use(context.Background()), nil) c, ctr := FilterGI(c) So(c, ShouldNotBeNil) So(ctr, ShouldNotBeNil) gi := info.Get(c) _, err := gi.Namespace("foo") die(err) fb.BreakFeatures(nil, "Namespace") _, err = gi.Namespace("boom") So(err, ShouldErrLike, `"Namespace" is broken`) So(ctr.Namespace, shouldHaveSuccessesAndErrors, 1, 1) }) Convey("works for user", t, func() { c, fb := featureBreaker.FilterUser(memory.Use(context.Background()), nil) c, ctr := FilterUser(c) So(c, ShouldNotBeNil) So(ctr, ShouldNotBeNil) u := user.Get(c) _, err := u.CurrentOAuth("foo") die(err) fb.BreakFeatures(nil, "CurrentOAuth") _, err = u.CurrentOAuth("foo") So(err, ShouldErrLike, `"CurrentOAuth" is broken`) So(ctr.CurrentOAuth, shouldHaveSuccessesAndErrors, 1, 1) }) Convey("works for mail", t, func() { c, fb := featureBreaker.FilterMail(memory.Use(context.Background()), nil) c, ctr := FilterMail(c) So(c, ShouldNotBeNil) So(ctr, ShouldNotBeNil) m := mail.Get(c) err := m.Send(&mail.Message{ Sender: "*****@*****.**", To: []string{"*****@*****.**"}, Body: "hi", }) die(err) fb.BreakFeatures(nil, "Send") err = m.Send(&mail.Message{ Sender: "*****@*****.**", To: []string{"*****@*****.**"}, Body: "hi", }) So(err, ShouldErrLike, `"Send" is broken`) So(ctr.Send, shouldHaveSuccessesAndErrors, 1, 1) }) }