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

Side by Side 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, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « impl/cloud/datastore.go ('k') | impl/cloud/info.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« 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