OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. |
| 4 |
| 5 package cloud |
| 6 |
| 7 import ( |
| 8 "crypto/rand" |
| 9 "encoding/hex" |
| 10 "fmt" |
| 11 "os" |
| 12 "testing" |
| 13 "time" |
| 14 |
| 15 ds "github.com/luci/gae/service/datastore" |
| 16 "github.com/luci/gae/service/info" |
| 17 |
| 18 "github.com/luci/luci-go/common/errors" |
| 19 "golang.org/x/net/context" |
| 20 "google.golang.org/cloud/datastore" |
| 21 |
| 22 . "github.com/smartystreets/goconvey/convey" |
| 23 ) |
| 24 |
| 25 func mkProperties(index bool, vals ...interface{}) []ds.Property { |
| 26 indexSetting := ds.ShouldIndex |
| 27 if !index { |
| 28 indexSetting = ds.NoIndex |
| 29 } |
| 30 |
| 31 result := make([]ds.Property, len(vals)) |
| 32 for i, v := range vals { |
| 33 result[i].SetValue(v, indexSetting) |
| 34 } |
| 35 return result |
| 36 } |
| 37 |
| 38 func mkp(vals ...interface{}) []ds.Property { return mkProperties(true, vals..
.) } |
| 39 func mkpNI(vals ...interface{}) []ds.Property { return mkProperties(false, vals.
..) } |
| 40 |
| 41 // TestDatastore tests the cloud datastore implementation. |
| 42 // |
| 43 // This test uses the gcloud datastore emulator. Like the Go datastore package, |
| 44 // the emulator must use the gRPC interface. At the time of writing, the |
| 45 // emulator included with the "gcloud" tool is an older emulator that does NOT |
| 46 // support gRPC. |
| 47 // |
| 48 // Download the emulator linked here: |
| 49 // https://code.google.com/p/google-cloud-sdk/issues/detail?id=719#c3 |
| 50 // |
| 51 // Run it in "--testing" mode, which removes random consistency failures and |
| 52 // runs in-memory: |
| 53 // $ ./gcd.sh start --testing |
| 54 // |
| 55 // Export the DATASTORE_EMULATOR_HOST environment variable. By default: |
| 56 // $ export DATASTORE_EMULATOR_HOST=localhost:8080 |
| 57 // |
| 58 // If the emulator environment is not detected, this test will be skipped. |
| 59 func TestDatastore(t *testing.T) { |
| 60 t.Parallel() |
| 61 |
| 62 // See if an emulator is running. If no emulator is running, we will ski
p this |
| 63 // test suite. |
| 64 emulatorHost := os.Getenv("DATASTORE_EMULATOR_HOST") |
| 65 if emulatorHost == "" { |
| 66 t.Logf("No emulator detected. Skipping test suite.") |
| 67 return |
| 68 } |
| 69 |
| 70 Convey(fmt.Sprintf(`A cloud installation using datastore emulator %q`, e
mulatorHost), t, func() { |
| 71 c := context.Background() |
| 72 client, err := datastore.NewClient(c, "luci-gae-test") |
| 73 So(err, ShouldBeNil) |
| 74 defer client.Close() |
| 75 |
| 76 testTime := ds.RoundTime(time.Date(2016, 1, 1, 0, 0, 0, 0, time.
UTC)) |
| 77 |
| 78 c = Use(c, client) |
| 79 |
| 80 Convey(`Supports namespaces`, func() { |
| 81 namespaces := []string{"foo", "bar", "baz"} |
| 82 |
| 83 // Clear all used entities from all namespaces. |
| 84 for _, ns := range namespaces { |
| 85 nsCtx := info.Get(c).MustNamespace(ns) |
| 86 di := ds.Get(nsCtx) |
| 87 |
| 88 keys := make([]*ds.Key, len(namespaces)) |
| 89 for i := range keys { |
| 90 keys[i] = di.MakeKey("Test", i+1) |
| 91 } |
| 92 So(errors.Filter(di.DeleteMulti(keys), ds.ErrNoS
uchEntity), ShouldBeNil) |
| 93 } |
| 94 |
| 95 // Put one entity per namespace. |
| 96 for i, ns := range namespaces { |
| 97 nsCtx := info.Get(c).MustNamespace(ns) |
| 98 |
| 99 pmap := ds.PropertyMap{"$kind": mkp("Test"), "$i
d": mkp(i + 1), "Value": mkp(i)} |
| 100 So(ds.Get(nsCtx).Put(pmap), ShouldBeNil) |
| 101 } |
| 102 |
| 103 // Make sure that entity only exists in that namespace. |
| 104 for _, ns := range namespaces { |
| 105 nsCtx := info.Get(c).MustNamespace(ns) |
| 106 |
| 107 for i := range namespaces { |
| 108 pmap := ds.PropertyMap{"$kind": mkp("Tes
t"), "$id": mkp(i + 1)} |
| 109 err := ds.Get(nsCtx).Get(pmap) |
| 110 |
| 111 if namespaces[i] == ns { |
| 112 So(err, ShouldBeNil) |
| 113 } else { |
| 114 So(err, ShouldEqual, ds.ErrNoSuc
hEntity) |
| 115 } |
| 116 } |
| 117 } |
| 118 }) |
| 119 |
| 120 Convey(`In a clean random testing namespace`, func() { |
| 121 // Enter a namespace for this round of tests. |
| 122 randNamespace := make([]byte, 32) |
| 123 if _, err := rand.Read(randNamespace); err != nil { |
| 124 panic(err) |
| 125 } |
| 126 c = info.Get(c).MustNamespace(fmt.Sprintf("testing-%s",
hex.EncodeToString(randNamespace))) |
| 127 di := ds.Get(c) |
| 128 |
| 129 // Execute a kindless query to clear the namespace. |
| 130 q := ds.NewQuery("").KeysOnly(true) |
| 131 var allKeys []*ds.Key |
| 132 So(di.GetAll(q, &allKeys), ShouldBeNil) |
| 133 So(di.DeleteMulti(allKeys), ShouldBeNil) |
| 134 |
| 135 Convey(`Can allocate an ID range`, func() { |
| 136 var keys []*ds.Key |
| 137 keys = append(keys, di.NewIncompleteKeys(10, "Ba
r", di.MakeKey("Foo", 12))...) |
| 138 keys = append(keys, di.NewIncompleteKeys(10, "Ba
z", di.MakeKey("Foo", 12))...) |
| 139 |
| 140 seen := map[string]struct{}{} |
| 141 So(di.AllocateIDs(keys), ShouldBeNil) |
| 142 for _, k := range keys { |
| 143 So(k.IsIncomplete(), ShouldBeFalse) |
| 144 seen[k.String()] = struct{}{} |
| 145 } |
| 146 |
| 147 So(di.AllocateIDs(keys), ShouldBeNil) |
| 148 for _, k := range keys { |
| 149 So(k.IsIncomplete(), ShouldBeFalse) |
| 150 |
| 151 _, ok := seen[k.String()] |
| 152 So(ok, ShouldBeFalse) |
| 153 } |
| 154 }) |
| 155 |
| 156 Convey(`Can get, put, and delete entities`, func() { |
| 157 // Put: "foo", "bar", "baz". |
| 158 put := []ds.PropertyMap{ |
| 159 {"$kind": mkp("test"), "$id": mkp("foo")
, "Value": mkp(1337)}, |
| 160 {"$kind": mkp("test"), "$id": mkp("bar")
, "Value": mkp(42)}, |
| 161 {"$kind": mkp("test"), "$id": mkp("baz")
, "Value": mkp(0xd065)}, |
| 162 } |
| 163 So(di.PutMulti(put), ShouldBeNil) |
| 164 delete(put[0], "$key") |
| 165 delete(put[1], "$key") |
| 166 delete(put[2], "$key") |
| 167 |
| 168 // Delete: "bar". |
| 169 So(di.Delete(di.MakeKey("test", "bar")), ShouldB
eNil) |
| 170 |
| 171 // Get: "foo", "bar", "baz" |
| 172 get := []ds.PropertyMap{ |
| 173 {"$kind": mkp("test"), "$id": mkp("foo")
}, |
| 174 {"$kind": mkp("test"), "$id": mkp("bar")
}, |
| 175 {"$kind": mkp("test"), "$id": mkp("baz")
}, |
| 176 } |
| 177 |
| 178 err := di.GetMulti(get) |
| 179 So(err, ShouldHaveSameTypeAs, errors.MultiError(
nil)) |
| 180 |
| 181 merr := err.(errors.MultiError) |
| 182 So(len(merr), ShouldEqual, 3) |
| 183 So(merr[0], ShouldBeNil) |
| 184 So(merr[1], ShouldEqual, ds.ErrNoSuchEntity) |
| 185 So(merr[2], ShouldBeNil) |
| 186 |
| 187 // put[1] will not be retrieved (delete) |
| 188 put[1] = get[1] |
| 189 So(get, ShouldResemble, put) |
| 190 }) |
| 191 |
| 192 Convey(`Can put and get all supported entity fields.`, f
unc() { |
| 193 put := ds.PropertyMap{ |
| 194 "$id": mkpNI("foo"), |
| 195 "$kind": mkpNI("FooType"), |
| 196 "Number": mkp(1337), |
| 197 "String": mkpNI("hello"), |
| 198 "Bytes": mkp([]byte("world")), |
| 199 "Time": mkp(testTime), |
| 200 "Float": mkpNI(3.14), |
| 201 "Key": mkp(di.MakeKey("Parent", "Pare
ntID", "Child", 1337)), |
| 202 |
| 203 "ComplexSlice": mkp(1337, "string", []by
te("bytes"), testTime, float32(3.14), |
| 204 float64(2.71), true, di.MakeKey(
"SomeKey", "SomeID")), |
| 205 } |
| 206 So(di.Put(put), ShouldBeNil) |
| 207 delete(put, "$key") |
| 208 |
| 209 get := ds.PropertyMap{ |
| 210 "$id": mkpNI("foo"), |
| 211 "$kind": mkpNI("FooType"), |
| 212 } |
| 213 So(di.Get(get), ShouldBeNil) |
| 214 So(get, ShouldResemble, put) |
| 215 }) |
| 216 |
| 217 Convey(`With several entities installed`, func() { |
| 218 So(di.PutMulti([]ds.PropertyMap{ |
| 219 {"$kind": mkp("Test"), "$id": mkp("foo")
, "FooBar": mkp(true)}, |
| 220 {"$kind": mkp("Test"), "$id": mkp("bar")
, "FooBar": mkp(true)}, |
| 221 {"$kind": mkp("Test"), "$id": mkp("baz")
}, |
| 222 {"$kind": mkp("Test"), "$id": mkp("qux")
}, |
| 223 }), ShouldBeNil) |
| 224 |
| 225 q := ds.NewQuery("Test") |
| 226 |
| 227 Convey(`Can query for entities with FooBar == tr
ue.`, func() { |
| 228 var results []ds.PropertyMap |
| 229 q = q.Eq("FooBar", true) |
| 230 So(di.GetAll(q, &results), ShouldBeNil) |
| 231 |
| 232 So(results, ShouldResemble, []ds.Propert
yMap{ |
| 233 {"$key": mkpNI(di.MakeKey("Test"
, "bar")), "FooBar": mkp(true)}, |
| 234 {"$key": mkpNI(di.MakeKey("Test"
, "foo")), "FooBar": mkp(true)}, |
| 235 }) |
| 236 }) |
| 237 |
| 238 Convey(`Can query for entities whose __key__ > "
baz".`, func() { |
| 239 var results []ds.PropertyMap |
| 240 q = q.Gt("__key__", di.MakeKey("Test", "
baz")) |
| 241 So(di.GetAll(q, &results), ShouldBeNil) |
| 242 |
| 243 So(results, ShouldResemble, []ds.Propert
yMap{ |
| 244 {"$key": mkpNI(di.MakeKey("Test"
, "foo")), "FooBar": mkp(true)}, |
| 245 {"$key": mkpNI(di.MakeKey("Test"
, "qux"))}, |
| 246 }) |
| 247 }) |
| 248 |
| 249 Convey(`Can transactionally get and put.`, func(
) { |
| 250 err := di.RunInTransaction(func(c contex
t.Context) error { |
| 251 di := ds.Get(c) |
| 252 |
| 253 pmap := ds.PropertyMap{"$kind":
mkp("Test"), "$id": mkp("qux")} |
| 254 if err := di.Get(pmap); err != n
il { |
| 255 return err |
| 256 } |
| 257 |
| 258 pmap["ExtraField"] = mkp("Presen
t!") |
| 259 return di.Put(pmap) |
| 260 }, nil) |
| 261 So(err, ShouldBeNil) |
| 262 |
| 263 pmap := ds.PropertyMap{"$kind": mkp("Tes
t"), "$id": mkp("qux")} |
| 264 err = di.RunInTransaction(func(c context
.Context) error { |
| 265 return ds.Get(c).Get(pmap) |
| 266 }, nil) |
| 267 So(err, ShouldBeNil) |
| 268 So(pmap, ShouldResemble, ds.PropertyMap{
"$kind": mkp("Test"), "$id": mkp("qux"), "ExtraField": mkp("Present!")}) |
| 269 }) |
| 270 |
| 271 Convey(`Can fail in a transaction with no effect
.`, func() { |
| 272 testError := errors.New("test error") |
| 273 |
| 274 err := di.RunInTransaction(func(c contex
t.Context) error { |
| 275 di := ds.Get(c) |
| 276 |
| 277 pmap := ds.PropertyMap{"$kind":
mkp("Test"), "$id": mkp("quux")} |
| 278 if err := di.Put(pmap); err != n
il { |
| 279 return err |
| 280 } |
| 281 return testError |
| 282 }, nil) |
| 283 So(err, ShouldEqual, testError) |
| 284 |
| 285 pmap := ds.PropertyMap{"$kind": mkp("Tes
t"), "$id": mkp("quux")} |
| 286 err = di.RunInTransaction(func(c context
.Context) error { |
| 287 return ds.Get(c).Get(pmap) |
| 288 }, nil) |
| 289 So(err, ShouldEqual, ds.ErrNoSuchEntity) |
| 290 }) |
| 291 }) |
| 292 }) |
| 293 }) |
| 294 } |
OLD | NEW |