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

Unified Diff: impl/cloud/datastore_test.go

Issue 1957953002: Add cloud datastore implementation. (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/gae@master
Patch Set: Adjust minimum coverage. Created 4 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « impl/cloud/datastore.go ('k') | impl/cloud/info.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: impl/cloud/datastore_test.go
diff --git a/impl/cloud/datastore_test.go b/impl/cloud/datastore_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..8415ca9a2257e6c2fc8e3f824e67bfbd12770698
--- /dev/null
+++ b/impl/cloud/datastore_test.go
@@ -0,0 +1,294 @@
+// Copyright 2016 The LUCI Authors. All rights reserved.
+// Use of this source code is governed under the Apache License, Version 2.0
+// that can be found in the LICENSE file.
+
+package cloud
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "os"
+ "testing"
+ "time"
+
+ ds "github.com/luci/gae/service/datastore"
+ "github.com/luci/gae/service/info"
+
+ "github.com/luci/luci-go/common/errors"
+ "golang.org/x/net/context"
+ "google.golang.org/cloud/datastore"
+
+ . "github.com/smartystreets/goconvey/convey"
+)
+
+func mkProperties(index bool, vals ...interface{}) []ds.Property {
+ indexSetting := ds.ShouldIndex
+ if !index {
+ indexSetting = ds.NoIndex
+ }
+
+ result := make([]ds.Property, len(vals))
+ for i, v := range vals {
+ result[i].SetValue(v, indexSetting)
+ }
+ return result
+}
+
+func mkp(vals ...interface{}) []ds.Property { return mkProperties(true, vals...) }
+func mkpNI(vals ...interface{}) []ds.Property { return mkProperties(false, vals...) }
+
+// TestDatastore tests the cloud datastore implementation.
+//
+// This test uses the gcloud datastore emulator. Like the Go datastore package,
+// the emulator must use the gRPC interface. At the time of writing, the
+// emulator included with the "gcloud" tool is an older emulator that does NOT
+// support gRPC.
+//
+// Download the emulator linked here:
+// https://code.google.com/p/google-cloud-sdk/issues/detail?id=719#c3
+//
+// Run it in "--testing" mode, which removes random consistency failures and
+// runs in-memory:
+// $ ./gcd.sh start --testing
+//
+// Export the DATASTORE_EMULATOR_HOST environment variable. By default:
+// $ export DATASTORE_EMULATOR_HOST=localhost:8080
+//
+// If the emulator environment is not detected, this test will be skipped.
+func TestDatastore(t *testing.T) {
+ t.Parallel()
+
+ // See if an emulator is running. If no emulator is running, we will skip this
+ // test suite.
+ emulatorHost := os.Getenv("DATASTORE_EMULATOR_HOST")
+ if emulatorHost == "" {
+ t.Logf("No emulator detected. Skipping test suite.")
+ return
+ }
+
+ Convey(fmt.Sprintf(`A cloud installation using datastore emulator %q`, emulatorHost), t, func() {
+ c := context.Background()
+ client, err := datastore.NewClient(c, "luci-gae-test")
+ So(err, ShouldBeNil)
+ defer client.Close()
+
+ testTime := ds.RoundTime(time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC))
+
+ c = Use(c, client)
+
+ Convey(`Supports namespaces`, func() {
+ namespaces := []string{"foo", "bar", "baz"}
+
+ // Clear all used entities from all namespaces.
+ for _, ns := range namespaces {
+ nsCtx := info.Get(c).MustNamespace(ns)
+ di := ds.Get(nsCtx)
+
+ keys := make([]*ds.Key, len(namespaces))
+ for i := range keys {
+ keys[i] = di.MakeKey("Test", i+1)
+ }
+ So(errors.Filter(di.DeleteMulti(keys), ds.ErrNoSuchEntity), ShouldBeNil)
+ }
+
+ // Put one entity per namespace.
+ for i, ns := range namespaces {
+ nsCtx := info.Get(c).MustNamespace(ns)
+
+ pmap := ds.PropertyMap{"$kind": mkp("Test"), "$id": mkp(i + 1), "Value": mkp(i)}
+ So(ds.Get(nsCtx).Put(pmap), ShouldBeNil)
+ }
+
+ // Make sure that entity only exists in that namespace.
+ for _, ns := range namespaces {
+ nsCtx := info.Get(c).MustNamespace(ns)
+
+ for i := range namespaces {
+ pmap := ds.PropertyMap{"$kind": mkp("Test"), "$id": mkp(i + 1)}
+ err := ds.Get(nsCtx).Get(pmap)
+
+ if namespaces[i] == ns {
+ So(err, ShouldBeNil)
+ } else {
+ So(err, ShouldEqual, ds.ErrNoSuchEntity)
+ }
+ }
+ }
+ })
+
+ Convey(`In a clean random testing namespace`, func() {
+ // Enter a namespace for this round of tests.
+ randNamespace := make([]byte, 32)
+ if _, err := rand.Read(randNamespace); err != nil {
+ panic(err)
+ }
+ c = info.Get(c).MustNamespace(fmt.Sprintf("testing-%s", hex.EncodeToString(randNamespace)))
+ di := ds.Get(c)
+
+ // Execute a kindless query to clear the namespace.
+ q := ds.NewQuery("").KeysOnly(true)
+ var allKeys []*ds.Key
+ So(di.GetAll(q, &allKeys), ShouldBeNil)
+ So(di.DeleteMulti(allKeys), ShouldBeNil)
+
+ Convey(`Can allocate an ID range`, func() {
+ var keys []*ds.Key
+ keys = append(keys, di.NewIncompleteKeys(10, "Bar", di.MakeKey("Foo", 12))...)
+ keys = append(keys, di.NewIncompleteKeys(10, "Baz", di.MakeKey("Foo", 12))...)
+
+ seen := map[string]struct{}{}
+ So(di.AllocateIDs(keys), ShouldBeNil)
+ for _, k := range keys {
+ So(k.IsIncomplete(), ShouldBeFalse)
+ seen[k.String()] = struct{}{}
+ }
+
+ So(di.AllocateIDs(keys), ShouldBeNil)
+ for _, k := range keys {
+ So(k.IsIncomplete(), ShouldBeFalse)
+
+ _, ok := seen[k.String()]
+ So(ok, ShouldBeFalse)
+ }
+ })
+
+ Convey(`Can get, put, and delete entities`, func() {
+ // Put: "foo", "bar", "baz".
+ put := []ds.PropertyMap{
+ {"$kind": mkp("test"), "$id": mkp("foo"), "Value": mkp(1337)},
+ {"$kind": mkp("test"), "$id": mkp("bar"), "Value": mkp(42)},
+ {"$kind": mkp("test"), "$id": mkp("baz"), "Value": mkp(0xd065)},
+ }
+ So(di.PutMulti(put), ShouldBeNil)
+ delete(put[0], "$key")
+ delete(put[1], "$key")
+ delete(put[2], "$key")
+
+ // Delete: "bar".
+ So(di.Delete(di.MakeKey("test", "bar")), ShouldBeNil)
+
+ // Get: "foo", "bar", "baz"
+ get := []ds.PropertyMap{
+ {"$kind": mkp("test"), "$id": mkp("foo")},
+ {"$kind": mkp("test"), "$id": mkp("bar")},
+ {"$kind": mkp("test"), "$id": mkp("baz")},
+ }
+
+ err := di.GetMulti(get)
+ So(err, ShouldHaveSameTypeAs, errors.MultiError(nil))
+
+ merr := err.(errors.MultiError)
+ So(len(merr), ShouldEqual, 3)
+ So(merr[0], ShouldBeNil)
+ So(merr[1], ShouldEqual, ds.ErrNoSuchEntity)
+ So(merr[2], ShouldBeNil)
+
+ // put[1] will not be retrieved (delete)
+ put[1] = get[1]
+ So(get, ShouldResemble, put)
+ })
+
+ Convey(`Can put and get all supported entity fields.`, func() {
+ put := ds.PropertyMap{
+ "$id": mkpNI("foo"),
+ "$kind": mkpNI("FooType"),
+ "Number": mkp(1337),
+ "String": mkpNI("hello"),
+ "Bytes": mkp([]byte("world")),
+ "Time": mkp(testTime),
+ "Float": mkpNI(3.14),
+ "Key": mkp(di.MakeKey("Parent", "ParentID", "Child", 1337)),
+
+ "ComplexSlice": mkp(1337, "string", []byte("bytes"), testTime, float32(3.14),
+ float64(2.71), true, di.MakeKey("SomeKey", "SomeID")),
+ }
+ So(di.Put(put), ShouldBeNil)
+ delete(put, "$key")
+
+ get := ds.PropertyMap{
+ "$id": mkpNI("foo"),
+ "$kind": mkpNI("FooType"),
+ }
+ So(di.Get(get), ShouldBeNil)
+ So(get, ShouldResemble, put)
+ })
+
+ Convey(`With several entities installed`, func() {
+ So(di.PutMulti([]ds.PropertyMap{
+ {"$kind": mkp("Test"), "$id": mkp("foo"), "FooBar": mkp(true)},
+ {"$kind": mkp("Test"), "$id": mkp("bar"), "FooBar": mkp(true)},
+ {"$kind": mkp("Test"), "$id": mkp("baz")},
+ {"$kind": mkp("Test"), "$id": mkp("qux")},
+ }), ShouldBeNil)
+
+ q := ds.NewQuery("Test")
+
+ Convey(`Can query for entities with FooBar == true.`, func() {
+ var results []ds.PropertyMap
+ q = q.Eq("FooBar", true)
+ So(di.GetAll(q, &results), ShouldBeNil)
+
+ So(results, ShouldResemble, []ds.PropertyMap{
+ {"$key": mkpNI(di.MakeKey("Test", "bar")), "FooBar": mkp(true)},
+ {"$key": mkpNI(di.MakeKey("Test", "foo")), "FooBar": mkp(true)},
+ })
+ })
+
+ Convey(`Can query for entities whose __key__ > "baz".`, func() {
+ var results []ds.PropertyMap
+ q = q.Gt("__key__", di.MakeKey("Test", "baz"))
+ So(di.GetAll(q, &results), ShouldBeNil)
+
+ So(results, ShouldResemble, []ds.PropertyMap{
+ {"$key": mkpNI(di.MakeKey("Test", "foo")), "FooBar": mkp(true)},
+ {"$key": mkpNI(di.MakeKey("Test", "qux"))},
+ })
+ })
+
+ Convey(`Can transactionally get and put.`, func() {
+ err := di.RunInTransaction(func(c context.Context) error {
+ di := ds.Get(c)
+
+ pmap := ds.PropertyMap{"$kind": mkp("Test"), "$id": mkp("qux")}
+ if err := di.Get(pmap); err != nil {
+ return err
+ }
+
+ pmap["ExtraField"] = mkp("Present!")
+ return di.Put(pmap)
+ }, nil)
+ So(err, ShouldBeNil)
+
+ pmap := ds.PropertyMap{"$kind": mkp("Test"), "$id": mkp("qux")}
+ err = di.RunInTransaction(func(c context.Context) error {
+ return ds.Get(c).Get(pmap)
+ }, nil)
+ So(err, ShouldBeNil)
+ So(pmap, ShouldResemble, ds.PropertyMap{"$kind": mkp("Test"), "$id": mkp("qux"), "ExtraField": mkp("Present!")})
+ })
+
+ Convey(`Can fail in a transaction with no effect.`, func() {
+ testError := errors.New("test error")
+
+ err := di.RunInTransaction(func(c context.Context) error {
+ di := ds.Get(c)
+
+ pmap := ds.PropertyMap{"$kind": mkp("Test"), "$id": mkp("quux")}
+ if err := di.Put(pmap); err != nil {
+ return err
+ }
+ return testError
+ }, nil)
+ So(err, ShouldEqual, testError)
+
+ pmap := ds.PropertyMap{"$kind": mkp("Test"), "$id": mkp("quux")}
+ err = di.RunInTransaction(func(c context.Context) error {
+ return ds.Get(c).Get(pmap)
+ }, nil)
+ So(err, ShouldEqual, ds.ErrNoSuchEntity)
+ })
+ })
+ })
+ })
+}
« no previous file with comments | « impl/cloud/datastore.go ('k') | impl/cloud/info.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698