go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/impl/memory/datastore_test.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package memory
    16  
    17  import (
    18  	"context"
    19  	"encoding/hex"
    20  	"errors"
    21  	"fmt"
    22  	"sync"
    23  	"sync/atomic"
    24  	"testing"
    25  	"time"
    26  
    27  	ds "go.chromium.org/luci/gae/service/datastore"
    28  	infoS "go.chromium.org/luci/gae/service/info"
    29  
    30  	. "github.com/smartystreets/goconvey/convey"
    31  	. "go.chromium.org/luci/common/testing/assertions"
    32  )
    33  
    34  type MetaGroup struct {
    35  	_id    int64   `gae:"$id,1"`
    36  	_kind  string  `gae:"$kind,__entity_group__"`
    37  	Parent *ds.Key `gae:"$parent"`
    38  
    39  	Version int64 `gae:"__version__"`
    40  }
    41  
    42  func testGetMeta(c context.Context, k *ds.Key) int64 {
    43  	mg := &MetaGroup{Parent: k.Root()}
    44  	if err := ds.Get(c, mg); err != nil {
    45  		panic(err)
    46  	}
    47  	return mg.Version
    48  }
    49  
    50  type Nested struct {
    51  	Inner int
    52  }
    53  
    54  type Foo struct {
    55  	ID     int64   `gae:"$id"`
    56  	Parent *ds.Key `gae:"$parent"`
    57  
    58  	Val    int
    59  	Name   string
    60  	Multi  []string
    61  	Key    *ds.Key
    62  	Nested Nested `gae:",lsp"`
    63  
    64  	Scatter []byte `gae:"__scatter__"` // this is normally invisible
    65  }
    66  
    67  func TestDatastoreSingleReadWriter(t *testing.T) {
    68  	t.Parallel()
    69  
    70  	Convey("Datastore single reads and writes", t, func() {
    71  		c := Use(context.Background())
    72  		So(ds.Raw(c), ShouldNotBeNil)
    73  
    74  		Convey("getting objects that DNE is an error", func() {
    75  			So(ds.Get(c, &Foo{ID: 1}), ShouldEqual, ds.ErrNoSuchEntity)
    76  		})
    77  
    78  		Convey("bad namespaces fail", func() {
    79  			_, err := infoS.Namespace(c, "$$blzyall")
    80  			So(err.Error(), ShouldContainSubstring, "namespace \"$$blzyall\" does not match")
    81  		})
    82  
    83  		Convey("Can Put stuff", func() {
    84  			// with an incomplete key!
    85  			f := &Foo{
    86  				Val:   10,
    87  				Multi: []string{"foo", "bar"},
    88  				Key:   ds.MakeKey(c, "Bar", "Baz"),
    89  				Nested: Nested{
    90  					Inner: 456,
    91  				},
    92  			}
    93  			So(ds.Put(c, f), ShouldBeNil)
    94  			k := ds.KeyForObj(c, f)
    95  			So(k.String(), ShouldEqual, "dev~app::/Foo,1")
    96  
    97  			Convey("and Get it back", func() {
    98  				newFoo := &Foo{ID: 1}
    99  				So(ds.Get(c, newFoo), ShouldBeNil)
   100  				So(newFoo, ShouldResemble, f)
   101  
   102  				Convey("but it's hidden from a different namespace", func() {
   103  					c, err := infoS.Namespace(c, "whombat")
   104  					So(err, ShouldBeNil)
   105  					So(ds.Get(c, f), ShouldEqual, ds.ErrNoSuchEntity)
   106  				})
   107  
   108  				Convey("and we can Delete it", func() {
   109  					So(ds.Delete(c, k), ShouldBeNil)
   110  					So(ds.Get(c, newFoo), ShouldEqual, ds.ErrNoSuchEntity)
   111  				})
   112  
   113  			})
   114  			Convey("Can Get it back as a PropertyMap", func() {
   115  				pmap := ds.PropertyMap{
   116  					"$id":   propNI(1),
   117  					"$kind": propNI("Foo"),
   118  				}
   119  				So(ds.Get(c, pmap), ShouldBeNil)
   120  				So(pmap, ShouldResemble, ds.PropertyMap{
   121  					"$id":   propNI(1),
   122  					"$kind": propNI("Foo"),
   123  					"Name":  prop(""),
   124  					"Val":   prop(10),
   125  					"Multi": ds.PropertySlice{prop("foo"), prop("bar")},
   126  					"Key":   prop(ds.MkKeyContext("dev~app", "").MakeKey("Bar", "Baz")),
   127  					"Nested": prop(ds.PropertyMap{
   128  						"Inner": prop(456),
   129  					}),
   130  				})
   131  			})
   132  			Convey("Deleting with a bogus key is bad", func() {
   133  				So(ds.IsErrInvalidKey(ds.Delete(c, ds.NewKey(c, "Foo", "wat", 100, nil))), ShouldBeTrue)
   134  			})
   135  			Convey("Deleting a DNE entity is fine", func() {
   136  				So(ds.Delete(c, ds.NewKey(c, "Foo", "wat", 0, nil)), ShouldBeNil)
   137  			})
   138  
   139  			Convey("Deleting entities from a nonexistant namespace works", func(gctx C) {
   140  				c := infoS.MustNamespace(c, "noexist")
   141  				keys := make([]*ds.Key, 10)
   142  				for i := range keys {
   143  					keys[i] = ds.MakeKey(c, "Kind", i+1)
   144  				}
   145  				So(ds.Delete(c, keys), ShouldBeNil)
   146  				count := 0
   147  				So(ds.Raw(c).DeleteMulti(keys, func(idx int, err error) {
   148  					gctx.So(idx, ShouldEqual, count)
   149  					gctx.So(err, ShouldBeNil)
   150  
   151  					count++
   152  				}), ShouldBeNil)
   153  				So(count, ShouldEqual, len(keys))
   154  			})
   155  
   156  			Convey("with multiple puts", func() {
   157  				So(testGetMeta(c, k), ShouldEqual, 1)
   158  
   159  				foos := make([]Foo, 10)
   160  				for i := range foos {
   161  					foos[i].Val = 10
   162  					foos[i].Parent = k
   163  				}
   164  				So(ds.Put(c, foos), ShouldBeNil)
   165  				So(testGetMeta(c, k), ShouldEqual, 11)
   166  
   167  				keys := make([]*ds.Key, len(foos))
   168  				for i, f := range foos {
   169  					keys[i] = ds.KeyForObj(c, &f)
   170  				}
   171  
   172  				Convey("ensure that group versions persist across deletes", func() {
   173  					So(ds.Delete(c, append(keys, k)), ShouldBeNil)
   174  
   175  					ds.GetTestable(c).CatchupIndexes()
   176  
   177  					count := 0
   178  					So(ds.Run(c, ds.NewQuery(""), func(_ *ds.Key) {
   179  						count++
   180  					}), ShouldBeNil)
   181  					So(count, ShouldEqual, 2)
   182  
   183  					So(testGetMeta(c, k), ShouldEqual, 22)
   184  
   185  					So(ds.Put(c, &Foo{ID: 1}), ShouldBeNil)
   186  					So(testGetMeta(c, k), ShouldEqual, 23)
   187  				})
   188  
   189  				Convey("can Get", func() {
   190  					vals := make([]ds.PropertyMap, len(keys))
   191  					for i := range vals {
   192  						vals[i] = ds.PropertyMap{}
   193  						So(vals[i].SetMeta("key", keys[i]), ShouldBeTrue)
   194  					}
   195  					So(ds.Get(c, vals), ShouldBeNil)
   196  
   197  					for i, val := range vals {
   198  						So(val, ShouldResemble, ds.PropertyMap{
   199  							"Val":  ds.MkProperty(10),
   200  							"Name": ds.MkProperty(""),
   201  							"$key": ds.MkPropertyNI(keys[i]),
   202  							"Key":  ds.MkProperty(nil),
   203  							"Nested": ds.MkProperty(ds.PropertyMap{
   204  								"Inner": ds.MkProperty(0),
   205  							}),
   206  						})
   207  					}
   208  				})
   209  
   210  			})
   211  
   212  			Convey("allocating ids prevents their use", func() {
   213  				keys := ds.NewIncompleteKeys(c, 100, "Foo", nil)
   214  				So(ds.AllocateIDs(c, keys), ShouldBeNil)
   215  				So(len(keys), ShouldEqual, 100)
   216  
   217  				// Assert that none of our keys share the same ID.
   218  				ids := make(map[int64]struct{})
   219  				for _, k := range keys {
   220  					ids[k.IntID()] = struct{}{}
   221  				}
   222  				So(len(ids), ShouldEqual, len(keys))
   223  
   224  				// Put a new object and ensure that it is allocated an unused ID.
   225  				f := &Foo{Val: 10}
   226  				So(ds.Put(c, f), ShouldBeNil)
   227  				k := ds.KeyForObj(c, f)
   228  				So(k.String(), ShouldEqual, "dev~app::/Foo,102")
   229  
   230  				_, ok := ids[k.IntID()]
   231  				So(ok, ShouldBeFalse)
   232  			})
   233  		})
   234  
   235  		Convey("implements DSTransactioner", func() {
   236  			Convey("Put", func() {
   237  				f := &Foo{Val: 10}
   238  				So(ds.Put(c, f), ShouldBeNil)
   239  				k := ds.KeyForObj(c, f)
   240  				So(k.String(), ShouldEqual, "dev~app::/Foo,1")
   241  
   242  				Convey("can describe its transaction state", func() {
   243  					So(ds.CurrentTransaction(c), ShouldBeNil)
   244  
   245  					err := ds.RunInTransaction(c, func(c context.Context) error {
   246  						So(ds.CurrentTransaction(c), ShouldNotBeNil)
   247  
   248  						// Can reset to nil.
   249  						nc := ds.WithoutTransaction(c)
   250  						So(ds.CurrentTransaction(nc), ShouldBeNil)
   251  						return nil
   252  					}, nil)
   253  					So(err, ShouldBeNil)
   254  				})
   255  
   256  				Convey("can Put new entity groups", func() {
   257  					err := ds.RunInTransaction(c, func(c context.Context) error {
   258  						f := &Foo{Val: 100}
   259  						So(ds.Put(c, f), ShouldBeNil)
   260  						So(f.ID, ShouldEqual, 2)
   261  
   262  						f.ID = 0
   263  						f.Val = 200
   264  						So(ds.Put(c, f), ShouldBeNil)
   265  						So(f.ID, ShouldEqual, 3)
   266  
   267  						return nil
   268  					}, nil)
   269  					So(err, ShouldBeNil)
   270  
   271  					f := &Foo{ID: 2}
   272  					So(ds.Get(c, f), ShouldBeNil)
   273  					So(f.Val, ShouldEqual, 100)
   274  
   275  					f.ID = 3
   276  					So(ds.Get(c, f), ShouldBeNil)
   277  					So(f.Val, ShouldEqual, 200)
   278  				})
   279  
   280  				Convey("can Put new entities in a current group", func() {
   281  					err := ds.RunInTransaction(c, func(c context.Context) error {
   282  						f := &Foo{Val: 100, Parent: k}
   283  						So(ds.Put(c, f), ShouldBeNil)
   284  						So(ds.KeyForObj(c, f).String(), ShouldEqual, "dev~app::/Foo,1/Foo,1")
   285  
   286  						f.ID = 0
   287  						f.Val = 200
   288  						So(ds.Put(c, f), ShouldBeNil)
   289  						So(ds.KeyForObj(c, f).String(), ShouldEqual, "dev~app::/Foo,1/Foo,2")
   290  
   291  						return nil
   292  					}, nil)
   293  					So(err, ShouldBeNil)
   294  
   295  					f := &Foo{ID: 1, Parent: k}
   296  					So(ds.Get(c, f), ShouldBeNil)
   297  					So(f.Val, ShouldEqual, 100)
   298  
   299  					f.ID = 2
   300  					So(ds.Get(c, f), ShouldBeNil)
   301  					So(f.Val, ShouldEqual, 200)
   302  				})
   303  
   304  				Convey("Deletes work too", func() {
   305  					err := ds.RunInTransaction(c, func(c context.Context) error {
   306  						return ds.Delete(c, k)
   307  					}, nil)
   308  					So(err, ShouldBeNil)
   309  					So(ds.Get(c, &Foo{ID: 1}), ShouldEqual, ds.ErrNoSuchEntity)
   310  				})
   311  
   312  				Convey("Get takes a snapshot", func() {
   313  					err := ds.RunInTransaction(c, func(c context.Context) error {
   314  						So(ds.Get(c, f), ShouldBeNil)
   315  						So(f.Val, ShouldEqual, 10)
   316  
   317  						// Don't ever do this in a real program unless you want to guarantee
   318  						// a failed transaction :)
   319  						f.Val = 11
   320  						So(ds.Put(ds.WithoutTransaction(c), f), ShouldBeNil)
   321  
   322  						So(ds.Get(c, f), ShouldBeNil)
   323  						So(f.Val, ShouldEqual, 10)
   324  
   325  						return nil
   326  					}, nil)
   327  					So(err, ShouldBeNil)
   328  
   329  					f := &Foo{ID: 1}
   330  					So(ds.Get(c, f), ShouldBeNil)
   331  					So(f.Val, ShouldEqual, 11)
   332  				})
   333  
   334  				Convey("and snapshots are consistent even after Puts", func() {
   335  					err := ds.RunInTransaction(c, func(c context.Context) error {
   336  						f := &Foo{ID: 1}
   337  						So(ds.Get(c, f), ShouldBeNil)
   338  						So(f.Val, ShouldEqual, 10)
   339  
   340  						// Don't ever do this in a real program unless you want to guarantee
   341  						// a failed transaction :)
   342  						f.Val = 11
   343  						So(ds.Put(ds.WithoutTransaction(c), f), ShouldBeNil)
   344  
   345  						So(ds.Get(c, f), ShouldBeNil)
   346  						So(f.Val, ShouldEqual, 10)
   347  
   348  						f.Val = 20
   349  						So(ds.Put(c, f), ShouldBeNil)
   350  
   351  						So(ds.Get(c, f), ShouldBeNil)
   352  						So(f.Val, ShouldEqual, 10) // still gets 10
   353  
   354  						return nil
   355  					}, &ds.TransactionOptions{Attempts: 1})
   356  					So(err.Error(), ShouldContainSubstring, "concurrent")
   357  
   358  					f := &Foo{ID: 1}
   359  					So(ds.Get(c, f), ShouldBeNil)
   360  					So(f.Val, ShouldEqual, 11)
   361  				})
   362  
   363  				Convey("Reusing a transaction context is bad news", func() {
   364  					var txnCtx context.Context
   365  					err := ds.RunInTransaction(c, func(c context.Context) error {
   366  						txnCtx = c
   367  						So(ds.Get(c, f), ShouldBeNil)
   368  						return nil
   369  					}, nil)
   370  					So(err, ShouldBeNil)
   371  					So(ds.Get(txnCtx, f).Error(), ShouldContainSubstring, "expired")
   372  				})
   373  
   374  				Convey("Nested transactions are rejected", func() {
   375  					err := ds.RunInTransaction(c, func(c context.Context) error {
   376  						err := ds.RunInTransaction(c, func(c context.Context) error {
   377  							panic("noooo")
   378  						}, nil)
   379  						So(err.Error(), ShouldContainSubstring, "nested transactions")
   380  						return nil
   381  					}, nil)
   382  					So(err, ShouldBeNil)
   383  				})
   384  
   385  				Convey("Transactions can be escaped.", func() {
   386  					testError := errors.New("test error")
   387  					noTxnPM := ds.PropertyMap{
   388  						"$kind": ds.MkProperty("Test"),
   389  						"$id":   ds.MkProperty("no txn"),
   390  					}
   391  
   392  					err := ds.RunInTransaction(c, func(c context.Context) error {
   393  						So(ds.CurrentTransaction(c), ShouldNotBeNil)
   394  
   395  						pmap := ds.PropertyMap{
   396  							"$kind": ds.MkProperty("Test"),
   397  							"$id":   ds.MkProperty("quux"),
   398  						}
   399  						if err := ds.Put(c, pmap); err != nil {
   400  							return err
   401  						}
   402  
   403  						// Put an entity outside of the transaction so we can confirm that
   404  						// it was added even when the transaction fails.
   405  						if err := ds.Put(ds.WithoutTransaction(c), noTxnPM); err != nil {
   406  							return err
   407  						}
   408  						return testError
   409  					}, nil)
   410  					So(err, ShouldEqual, testError)
   411  
   412  					// Confirm that noTxnPM was added.
   413  					So(ds.CurrentTransaction(c), ShouldBeNil)
   414  					So(ds.Get(c, noTxnPM), ShouldBeNil)
   415  				})
   416  
   417  				Convey("Concurrent transactions only accept one set of changes", func() {
   418  					// Note: I think this implementation is actually /slightly/ wrong.
   419  					// According to my read of the docs for appengine, when you open a
   420  					// transaction it actually (essentially) holds a reference to the
   421  					// entire datastore. Our implementation takes a snapshot of the
   422  					// entity group as soon as something observes/affects it.
   423  					//
   424  					// That said... I'm not sure if there's really a semantic difference.
   425  					err := ds.RunInTransaction(c, func(c context.Context) error {
   426  						So(ds.Put(c, &Foo{ID: 1, Val: 21}), ShouldBeNil)
   427  
   428  						err := ds.RunInTransaction(ds.WithoutTransaction(c), func(c context.Context) error {
   429  							So(ds.Put(c, &Foo{ID: 1, Val: 27}), ShouldBeNil)
   430  							return nil
   431  						}, nil)
   432  						So(err, ShouldBeNil)
   433  
   434  						return nil
   435  					}, nil)
   436  					So(err.Error(), ShouldContainSubstring, "concurrent")
   437  
   438  					f := &Foo{ID: 1}
   439  					So(ds.Get(c, f), ShouldBeNil)
   440  					So(f.Val, ShouldEqual, 27)
   441  				})
   442  
   443  				Convey("Errors and panics", func() {
   444  					Convey("returning an error aborts", func() {
   445  						err := ds.RunInTransaction(c, func(c context.Context) error {
   446  							So(ds.Put(c, &Foo{ID: 1, Val: 200}), ShouldBeNil)
   447  							return fmt.Errorf("thingy")
   448  						}, nil)
   449  						So(err.Error(), ShouldEqual, "thingy")
   450  
   451  						f := &Foo{ID: 1}
   452  						So(ds.Get(c, f), ShouldBeNil)
   453  						So(f.Val, ShouldEqual, 10)
   454  					})
   455  
   456  					Convey("panicing aborts", func() {
   457  						So(func() {
   458  							So(ds.RunInTransaction(c, func(c context.Context) error {
   459  								So(ds.Put(c, &Foo{Val: 200}), ShouldBeNil)
   460  								panic("wheeeeee")
   461  							}, nil), ShouldBeNil)
   462  						}, ShouldPanic)
   463  
   464  						f := &Foo{ID: 1}
   465  						So(ds.Get(c, f), ShouldBeNil)
   466  						So(f.Val, ShouldEqual, 10)
   467  					})
   468  				})
   469  
   470  				Convey("Transaction retries", func() {
   471  					tst := ds.GetTestable(c)
   472  					Reset(func() { tst.SetTransactionRetryCount(0) })
   473  
   474  					Convey("SetTransactionRetryCount set to zero", func() {
   475  						tst.SetTransactionRetryCount(0)
   476  						calls := 0
   477  						So(ds.RunInTransaction(c, func(c context.Context) error {
   478  							calls++
   479  							return nil
   480  						}, nil), ShouldBeNil)
   481  						So(calls, ShouldEqual, 1)
   482  					})
   483  
   484  					Convey("default TransactionOptions is 3 attempts", func() {
   485  						tst.SetTransactionRetryCount(100) // more than 3
   486  						calls := 0
   487  						So(ds.RunInTransaction(c, func(c context.Context) error {
   488  							calls++
   489  							return nil
   490  						}, nil), ShouldEqual, ds.ErrConcurrentTransaction)
   491  						So(calls, ShouldEqual, 3)
   492  					})
   493  
   494  					Convey("non-default TransactionOptions ", func() {
   495  						tst.SetTransactionRetryCount(100) // more than 20
   496  						calls := 0
   497  						So(ds.RunInTransaction(c, func(c context.Context) error {
   498  							calls++
   499  							return nil
   500  						}, &ds.TransactionOptions{Attempts: 20}), ShouldEqual, ds.ErrConcurrentTransaction)
   501  						So(calls, ShouldEqual, 20)
   502  					})
   503  
   504  					Convey("SetTransactionRetryCount is respected", func() {
   505  						tst.SetTransactionRetryCount(1) // less than 3
   506  						calls := 0
   507  						So(ds.RunInTransaction(c, func(c context.Context) error {
   508  							calls++
   509  							return nil
   510  						}, nil), ShouldBeNil)
   511  						So(calls, ShouldEqual, 2)
   512  					})
   513  
   514  					Convey("fatal errors are not retried", func() {
   515  						tst.SetTransactionRetryCount(1)
   516  						calls := 0
   517  						So(ds.RunInTransaction(c, func(c context.Context) error {
   518  							calls++
   519  							return fmt.Errorf("omg")
   520  						}, nil).Error(), ShouldEqual, "omg")
   521  						So(calls, ShouldEqual, 1)
   522  					})
   523  				})
   524  
   525  				Convey("Read-only transactions reject writes", func() {
   526  					So(ds.Put(c, &Foo{ID: 1, Val: 100}), ShouldBeNil)
   527  					var val int
   528  
   529  					So(ds.RunInTransaction(c, func(c context.Context) error {
   530  						foo := &Foo{ID: 1}
   531  						So(ds.Get(c, foo), ShouldBeNil)
   532  						val = foo.Val
   533  
   534  						foo.Val = 1337
   535  						return ds.Put(c, foo)
   536  					}, &ds.TransactionOptions{ReadOnly: true}), ShouldErrLike, "Attempting to write")
   537  					So(val, ShouldResemble, 100)
   538  					foo := &Foo{ID: 1}
   539  					So(ds.Get(c, foo), ShouldBeNil)
   540  					So(foo.Val, ShouldResemble, 100)
   541  				})
   542  
   543  				Convey("Read-only transactions reject deletes", func() {
   544  					So(ds.Put(c, &Foo{ID: 1, Val: 100}), ShouldBeNil)
   545  					var val int
   546  
   547  					So(ds.RunInTransaction(c, func(c context.Context) error {
   548  						foo := &Foo{ID: 1}
   549  						So(ds.Get(c, foo), ShouldBeNil)
   550  						val = foo.Val
   551  
   552  						return ds.Delete(c, foo)
   553  					}, &ds.TransactionOptions{ReadOnly: true}), ShouldErrLike, "Attempting to delete")
   554  					So(val, ShouldResemble, 100)
   555  					So(ds.Get(c, &Foo{ID: 1}), ShouldBeNil)
   556  				})
   557  			})
   558  		})
   559  
   560  		Convey("Testable.Consistent", func() {
   561  			Convey("false", func() {
   562  				ds.GetTestable(c).Consistent(false) // the default
   563  				for i := 0; i < 10; i++ {
   564  					So(ds.Put(c, &Foo{ID: int64(i + 1), Val: i + 1}), ShouldBeNil)
   565  				}
   566  				q := ds.NewQuery("Foo").Gt("Val", 3)
   567  				count, err := ds.Count(c, q)
   568  				So(err, ShouldBeNil)
   569  				So(count, ShouldEqual, 0)
   570  
   571  				So(ds.Delete(c, ds.MakeKey(c, "Foo", 4)), ShouldBeNil)
   572  
   573  				count, err = ds.Count(c, q)
   574  				So(err, ShouldBeNil)
   575  				So(count, ShouldEqual, 0)
   576  
   577  				ds.GetTestable(c).Consistent(true)
   578  				count, err = ds.Count(c, q)
   579  				So(err, ShouldBeNil)
   580  				So(count, ShouldEqual, 6)
   581  			})
   582  
   583  			Convey("true", func() {
   584  				ds.GetTestable(c).Consistent(true)
   585  				for i := 0; i < 10; i++ {
   586  					So(ds.Put(c, &Foo{ID: int64(i + 1), Val: i + 1}), ShouldBeNil)
   587  				}
   588  				q := ds.NewQuery("Foo").Gt("Val", 3)
   589  				count, err := ds.Count(c, q)
   590  				So(err, ShouldBeNil)
   591  				So(count, ShouldEqual, 7)
   592  
   593  				So(ds.Delete(c, ds.MakeKey(c, "Foo", 4)), ShouldBeNil)
   594  
   595  				count, err = ds.Count(c, q)
   596  				So(err, ShouldBeNil)
   597  				So(count, ShouldEqual, 6)
   598  			})
   599  		})
   600  
   601  		Convey("Testable.DisableSpecialEntities", func() {
   602  			ds.GetTestable(c).DisableSpecialEntities(true)
   603  
   604  			So(ds.Put(c, &Foo{}), ShouldErrLike, "allocateIDs is disabled")
   605  
   606  			So(ds.Put(c, &Foo{ID: 1}), ShouldBeNil)
   607  
   608  			ds.GetTestable(c).CatchupIndexes()
   609  
   610  			count, err := ds.Count(c, ds.NewQuery(""))
   611  			So(err, ShouldBeNil)
   612  			So(count, ShouldEqual, 1) // normally this would include __entity_group__
   613  		})
   614  
   615  		Convey("Datastore namespace interaction", func() {
   616  			run := func(rc context.Context, txn bool) (putErr, getErr, queryErr, countErr error) {
   617  				var foo Foo
   618  
   619  				putFunc := func(doC context.Context) error {
   620  					return ds.Put(doC, &foo)
   621  				}
   622  
   623  				doFunc := func(doC context.Context) {
   624  					getErr = ds.Get(doC, &foo)
   625  
   626  					q := ds.NewQuery("Foo").Ancestor(ds.KeyForObj(doC, &foo))
   627  					queryErr = ds.Run(doC, q, func(f *Foo) error { return nil })
   628  					_, countErr = ds.Count(doC, q)
   629  				}
   630  
   631  				if txn {
   632  					putErr = ds.RunInTransaction(rc, func(ic context.Context) error {
   633  						return putFunc(ic)
   634  					}, nil)
   635  					if putErr != nil {
   636  						return
   637  					}
   638  
   639  					ds.GetTestable(rc).CatchupIndexes()
   640  					ds.RunInTransaction(rc, func(ic context.Context) error {
   641  						doFunc(ic)
   642  						return nil
   643  					}, nil)
   644  				} else {
   645  					putErr = putFunc(rc)
   646  					if putErr != nil {
   647  						return
   648  					}
   649  					ds.GetTestable(rc).CatchupIndexes()
   650  					doFunc(rc)
   651  				}
   652  				return
   653  			}
   654  
   655  			for _, txn := range []bool{false, true} {
   656  				Convey(fmt.Sprintf("In transaction? %v", txn), func() {
   657  					Convey("With no namespace installed, can Put, Get, Query, and Count.", func() {
   658  						So(infoS.GetNamespace(c), ShouldEqual, "")
   659  
   660  						putErr, getErr, queryErr, countErr := run(c, txn)
   661  						So(putErr, ShouldBeNil)
   662  						So(getErr, ShouldBeNil)
   663  						So(queryErr, ShouldBeNil)
   664  						So(countErr, ShouldBeNil)
   665  					})
   666  
   667  					Convey("With a namespace installed, can Put, Get, Query, and Count.", func() {
   668  						putErr, getErr, queryErr, countErr := run(infoS.MustNamespace(c, "foo"), txn)
   669  						So(putErr, ShouldBeNil)
   670  						So(getErr, ShouldBeNil)
   671  						So(queryErr, ShouldBeNil)
   672  						So(countErr, ShouldBeNil)
   673  					})
   674  				})
   675  			}
   676  		})
   677  
   678  		Convey("Testable.ShowSpecialProperties", func() {
   679  			ds.GetTestable(c).ShowSpecialProperties(true)
   680  
   681  			var ents []Foo
   682  			for i := 0; i < 10; i++ {
   683  				ent := &Foo{}
   684  				So(ds.Put(c, ent), ShouldBeNil)
   685  				ents = append(ents, *ent)
   686  			}
   687  			So(ds.Get(c, ents), ShouldBeNil)
   688  
   689  			// Some of these entities (~50%) should have __scatter__ property
   690  			// populated. The algorithm is deterministic.
   691  			scatter := make([]string, len(ents))
   692  			for i, e := range ents {
   693  				scatter[i] = hex.EncodeToString(e.Scatter)
   694  			}
   695  			So(scatter, ShouldResemble, []string{
   696  				"d77e219d0669b1808f236ca5b25127bf8e865e3f0e68b792374526251c873c61",
   697  				"",
   698  				"",
   699  				"",
   700  				"",
   701  				"",
   702  				"b592c9de652ffc3f458910247fc16690ba2ceeef20a8566fda5dd989a5fc160e",
   703  				"bcefad8a2212ee1cfa3636e94264b8c73c90eaded9f429e27c7384830c1e381c",
   704  				"d2358c1d9e5951be7117e06eaec96a6a63090f181615e2c51afaf7f214e4d873",
   705  				"b29a46a6c01adb88d7001fe399d6346d5d2725b190f4fb025c9cb7c73c4ffb15",
   706  			})
   707  		})
   708  
   709  		Convey("Query by __scatter__", func() {
   710  			for i := 0; i < 100; i++ {
   711  				So(ds.Put(c, &Foo{}), ShouldBeNil)
   712  			}
   713  			ds.GetTestable(c).CatchupIndexes()
   714  
   715  			var ids []int64
   716  			So(ds.Run(c, ds.NewQuery("Foo").Order("__scatter__").Limit(5), func(f *Foo) {
   717  				So(f.Scatter, ShouldBeNil) // it is "invisible"
   718  				ids = append(ids, f.ID)
   719  			}), ShouldBeNil)
   720  
   721  			// Approximately "even" distribution within [1, 100] range.
   722  			So(ids, ShouldResemble, []int64{43, 55, 99, 23, 17})
   723  		})
   724  	})
   725  }
   726  
   727  func TestCompoundIndexes(t *testing.T) {
   728  	t.Parallel()
   729  
   730  	idxKey := func(def ds.IndexDefinition) string {
   731  		So(def, ShouldNotBeNil)
   732  		return "idx::" + string(ds.Serialize.ToBytes(*def.PrepForIdxTable()))
   733  	}
   734  
   735  	Convey("Test Compound indexes", t, func() {
   736  		type Model struct {
   737  			ID int64 `gae:"$id"`
   738  
   739  			Field1 []string
   740  			Field2 []int64
   741  		}
   742  
   743  		c := Use(context.Background())
   744  		t := ds.GetTestable(c).(*dsImpl)
   745  		head := t.data.head
   746  
   747  		So(ds.Put(c, &Model{1, []string{"hello", "world"}, []int64{10, 11}}), ShouldBeNil)
   748  
   749  		idx := ds.IndexDefinition{
   750  			Kind: "Model",
   751  			SortBy: []ds.IndexColumn{
   752  				{Property: "Field2"},
   753  			},
   754  		}
   755  
   756  		coll := head.Snapshot().GetCollection(idxKey(idx))
   757  		So(coll, ShouldNotBeNil)
   758  		So(countItems(coll), ShouldEqual, 2)
   759  
   760  		idx.SortBy[0].Property = "Field1"
   761  		coll = head.Snapshot().GetCollection(idxKey(idx))
   762  		So(coll, ShouldNotBeNil)
   763  		So(countItems(coll), ShouldEqual, 2)
   764  
   765  		idx.SortBy = append(idx.SortBy, ds.IndexColumn{Property: "Field1"})
   766  		So(head.GetCollection(idxKey(idx)), ShouldBeNil)
   767  
   768  		t.AddIndexes(&idx)
   769  		coll = head.Snapshot().GetCollection(idxKey(idx))
   770  		So(coll, ShouldNotBeNil)
   771  		So(countItems(coll), ShouldEqual, 4)
   772  	})
   773  }
   774  
   775  // High level test for regression in how zero time is stored,
   776  // see https://codereview.chromium.org/1334043003/
   777  func TestDefaultTimeField(t *testing.T) {
   778  	t.Parallel()
   779  
   780  	Convey("Default time.Time{} can be stored", t, func() {
   781  		type Model struct {
   782  			ID   int64 `gae:"$id"`
   783  			Time time.Time
   784  		}
   785  		c := Use(context.Background())
   786  		m := Model{ID: 1}
   787  		So(ds.Put(c, &m), ShouldBeNil)
   788  
   789  		// Reset to something non zero to ensure zero is fetched.
   790  		m.Time = time.Now().UTC()
   791  		So(ds.Get(c, &m), ShouldBeNil)
   792  		So(m.Time.IsZero(), ShouldBeTrue)
   793  	})
   794  }
   795  
   796  func TestNewDatastore(t *testing.T) {
   797  	t.Parallel()
   798  
   799  	Convey("Can get and use a NewDatastore", t, func() {
   800  		c := UseWithAppID(context.Background(), "dev~aid")
   801  		c = infoS.MustNamespace(c, "ns")
   802  
   803  		dsInst := NewDatastore(c, infoS.Raw(c))
   804  		c = ds.SetRaw(c, dsInst)
   805  
   806  		k := ds.MakeKey(c, "Something", 1)
   807  		So(k.AppID(), ShouldEqual, "dev~aid")
   808  		So(k.Namespace(), ShouldEqual, "ns")
   809  
   810  		type Model struct {
   811  			ID    int64 `gae:"$id"`
   812  			Value []int64
   813  		}
   814  		So(ds.Put(c, &Model{ID: 1, Value: []int64{20, 30}}), ShouldBeNil)
   815  
   816  		vals := []ds.PropertyMap{}
   817  		So(ds.GetAll(c, ds.NewQuery("Model").Project("Value"), &vals), ShouldBeNil)
   818  		So(len(vals), ShouldEqual, 2)
   819  
   820  		So(vals[0].Slice("Value")[0].Value(), ShouldEqual, 20)
   821  		So(vals[1].Slice("Value")[0].Value(), ShouldEqual, 30)
   822  	})
   823  }
   824  
   825  func TestAddIndexes(t *testing.T) {
   826  	t.Parallel()
   827  
   828  	Convey("Test Testable.AddIndexes", t, func() {
   829  		ctx := UseWithAppID(context.Background(), "aid")
   830  		namespaces := []string{"", "good", "news", "everyone"}
   831  
   832  		Convey("After adding datastore entries, can query against indexes in various namespaces", func() {
   833  			foos := []*Foo{
   834  				{ID: 1, Val: 1, Name: "foo"},
   835  				{ID: 2, Val: 2, Name: "bar"},
   836  				{ID: 3, Val: 2, Name: "baz"},
   837  			}
   838  			for _, ns := range namespaces {
   839  				So(ds.Put(infoS.MustNamespace(ctx, ns), foos), ShouldBeNil)
   840  			}
   841  
   842  			// Initial query, no indexes, will fail.
   843  			ds.GetTestable(ctx).CatchupIndexes()
   844  
   845  			var results []*Foo
   846  			q := ds.NewQuery("Foo").Eq("Val", 2).Gte("Name", "bar")
   847  			So(ds.GetAll(ctx, q, &results), ShouldErrLike, "Insufficient indexes")
   848  
   849  			// Add index for default namespace.
   850  			ds.GetTestable(ctx).AddIndexes(&ds.IndexDefinition{
   851  				Kind: "Foo",
   852  				SortBy: []ds.IndexColumn{
   853  					{Property: "Val"},
   854  					{Property: "Name"},
   855  				},
   856  			})
   857  			ds.GetTestable(ctx).CatchupIndexes()
   858  
   859  			for _, ns := range namespaces {
   860  				if ns == "" {
   861  					// Skip query test for empty namespace, as this is invalid.
   862  					continue
   863  				}
   864  
   865  				results = nil
   866  				So(ds.GetAll(infoS.MustNamespace(ctx, ns), q, &results), ShouldBeNil)
   867  				So(len(results), ShouldEqual, 2)
   868  			}
   869  
   870  			// Add "foos" to a new namespace, then confirm that it gets indexed.
   871  			So(ds.Put(infoS.MustNamespace(ctx, "qux"), foos), ShouldBeNil)
   872  			ds.GetTestable(ctx).CatchupIndexes()
   873  
   874  			results = nil
   875  			So(ds.GetAll(infoS.MustNamespace(ctx, "qux"), q, &results), ShouldBeNil)
   876  			So(len(results), ShouldEqual, 2)
   877  		})
   878  	})
   879  }
   880  
   881  func TestConcurrentTxn(t *testing.T) {
   882  	t.Parallel()
   883  
   884  	// Stress test for concurrent transactions. It transactionally increments a
   885  	// counter in an entity and counts how many transactions succeeded. The final
   886  	// counter value and the number of committed transactions should match.
   887  
   888  	Convey("Concurrent transactions work", t, func() {
   889  		c := Use(context.Background())
   890  
   891  		var successes int64
   892  
   893  		for round := 0; round < 1000; round++ {
   894  			barrier := make(chan struct{})
   895  			wg := sync.WaitGroup{}
   896  
   897  			for track := 0; track < 5; track++ {
   898  				wg.Add(1)
   899  				go func(round, track int) {
   900  					defer wg.Done()
   901  					<-barrier
   902  
   903  					err := ds.RunInTransaction(c, func(c context.Context) error {
   904  						ent := Foo{ID: 1}
   905  						switch err := ds.Get(c, &ent); {
   906  						case err == ds.ErrNoSuchEntity:
   907  							// new entity
   908  						case err != nil:
   909  							return err
   910  						}
   911  						ent.Val++
   912  						return ds.Put(c, &ent)
   913  					}, nil)
   914  					if err == nil {
   915  						atomic.AddInt64(&successes, 1)
   916  					}
   917  
   918  				}(round, track)
   919  			}
   920  
   921  			// Run one round of the test.
   922  			close(barrier)
   923  			wg.Wait()
   924  
   925  			// Verify that everything is still ok.
   926  			ent := Foo{ID: 1}
   927  			ds.Get(c, &ent)
   928  			counter := atomic.LoadInt64(&successes)
   929  			if int64(ent.Val) != counter { // don't spam convey assertions
   930  				So(ent.Val, ShouldEqual, counter)
   931  			}
   932  		}
   933  	})
   934  }