OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 // adapted from github.com/golang/appengine/datastore | |
6 | |
7 package helper | |
8 | |
9 import ( | |
10 "bytes" | |
11 "encoding/json" | |
12 "fmt" | |
13 "math" | |
14 "reflect" | |
15 "strconv" | |
16 "strings" | |
17 "testing" | |
18 "time" | |
19 | |
20 "github.com/luci/gae" | |
21 . "github.com/smartystreets/goconvey/convey" | |
22 ) | |
23 | |
24 var ( | |
25 mp = gae.MkDSProperty | |
26 mpNI = gae.MkDSPropertyNI | |
27 ) | |
28 | |
29 const testAppID = "testApp" | |
30 | |
31 type ( | |
32 myBlob []byte | |
33 myByte byte | |
34 myString string | |
35 ) | |
36 | |
37 func makeMyByteSlice(n int) []myByte { | |
38 b := make([]myByte, n) | |
39 for i := range b { | |
40 b[i] = myByte(i) | |
41 } | |
42 return b | |
43 } | |
44 | |
45 func makeInt8Slice(n int) []int8 { | |
46 b := make([]int8, n) | |
47 for i := range b { | |
48 b[i] = int8(i) | |
49 } | |
50 return b | |
51 } | |
52 | |
53 func makeUint8Slice(n int) []uint8 { | |
54 b := make([]uint8, n) | |
55 for i := range b { | |
56 b[i] = uint8(i) | |
57 } | |
58 return b | |
59 } | |
60 | |
61 var ( | |
62 testKey0 = mkKey("aid", "", "kind", "name0") | |
63 testKey1a = mkKey("aid", "", "kind", "name1") | |
64 testKey1b = mkKey("aid", "", "kind", "name1") | |
65 testKey2a = mkKey("aid", "", "kind", "name0", "kind", "name2") | |
66 testKey2b = mkKey("aid", "", "kind", "name0", "kind", "name2") | |
67 testGeoPt0 = gae.DSGeoPoint{Lat: 1.2, Lng: 3.4} | |
68 testGeoPt1 = gae.DSGeoPoint{Lat: 5, Lng: 10} | |
69 testBadGeoPt = gae.DSGeoPoint{Lat: 1000, Lng: 34} | |
70 ) | |
71 | |
72 type B0 struct { | |
73 B []byte | |
74 } | |
75 | |
76 type B1 struct { | |
77 B []int8 | |
78 } | |
79 | |
80 type B2 struct { | |
81 B myBlob | |
82 } | |
83 | |
84 type B3 struct { | |
85 B []myByte | |
86 } | |
87 | |
88 type B4 struct { | |
89 B [][]byte | |
90 } | |
91 | |
92 type B5 struct { | |
93 B gae.DSByteString | |
94 } | |
95 | |
96 type C0 struct { | |
97 I int | |
98 C chan int | |
99 } | |
100 | |
101 type C1 struct { | |
102 I int | |
103 C *chan int | |
104 } | |
105 | |
106 type C2 struct { | |
107 I int | |
108 C []chan int | |
109 } | |
110 | |
111 type C3 struct { | |
112 C string | |
113 } | |
114 | |
115 type E struct{} | |
116 | |
117 type G0 struct { | |
118 G gae.DSGeoPoint | |
119 } | |
120 | |
121 type G1 struct { | |
122 G []gae.DSGeoPoint | |
123 } | |
124 | |
125 type K0 struct { | |
126 K gae.DSKey | |
127 } | |
128 | |
129 type K1 struct { | |
130 K []gae.DSKey | |
131 } | |
132 | |
133 type N0 struct { | |
134 X0 | |
135 ID int64 `gae:"$id"` | |
136 _kind string `gae:"$kind,whatnow"` | |
137 Nonymous X0 | |
138 Ignore string `gae:"-"` | |
139 Other string | |
140 } | |
141 | |
142 type N1 struct { | |
143 X0 | |
144 Nonymous []X0 | |
145 Ignore string `gae:"-"` | |
146 Other string | |
147 } | |
148 | |
149 type N2 struct { | |
150 N1 `gae:"red"` | |
151 Green N1 `gae:"green"` | |
152 Blue N1 | |
153 White N1 `gae:"-"` | |
154 } | |
155 | |
156 type O0 struct { | |
157 I int64 | |
158 } | |
159 | |
160 type O1 struct { | |
161 I int32 | |
162 } | |
163 | |
164 type U0 struct { | |
165 U uint | |
166 } | |
167 | |
168 type U1 struct { | |
169 U string | |
170 } | |
171 | |
172 type T struct { | |
173 T time.Time | |
174 } | |
175 | |
176 type X0 struct { | |
177 S string | |
178 I int | |
179 i int | |
180 } | |
181 | |
182 type X1 struct { | |
183 S myString | |
184 I int32 | |
185 J int64 | |
186 } | |
187 | |
188 type X2 struct { | |
189 Z string | |
190 i int | |
191 } | |
192 | |
193 type X3 struct { | |
194 S bool | |
195 I int | |
196 } | |
197 | |
198 type Y0 struct { | |
199 B bool | |
200 F []float64 | |
201 G []float64 | |
202 } | |
203 | |
204 type Y1 struct { | |
205 B bool | |
206 F float64 | |
207 } | |
208 | |
209 type Y2 struct { | |
210 B bool | |
211 F []int64 | |
212 } | |
213 | |
214 type Y3 struct { | |
215 B bool | |
216 F int64 | |
217 } | |
218 | |
219 type Tagged struct { | |
220 A int `gae:"a,noindex"` | |
221 B []int `gae:"b1"` | |
222 C int `gae:",noindex"` | |
223 D int `gae:""` | |
224 E int | |
225 I int `gae:"-"` | |
226 J int `gae:",noindex" json:"j"` | |
227 | |
228 Y0 `gae:"-"` | |
229 Z chan int `gae:"-,"` | |
230 } | |
231 | |
232 type InvalidTagged1 struct { | |
233 I int `gae:"\t"` | |
234 } | |
235 | |
236 type InvalidTagged2 struct { | |
237 I int | |
238 J int `gae:"I"` | |
239 } | |
240 | |
241 type InvalidTagged3 struct { | |
242 I int `gae:"a\t"` | |
243 } | |
244 | |
245 type InvalidTagged4 struct { | |
246 I int `gae:"a."` | |
247 } | |
248 | |
249 type InvalidTaggedSub struct { | |
250 I int | |
251 } | |
252 | |
253 type InvalidTagged5 struct { | |
254 I int `gae:"V.I"` | |
255 V []InvalidTaggedSub | |
256 } | |
257 | |
258 type Inner1 struct { | |
259 W int32 | |
260 X string | |
261 } | |
262 | |
263 type Inner2 struct { | |
264 Y float64 | |
265 } | |
266 | |
267 type Inner3 struct { | |
268 Z bool | |
269 } | |
270 | |
271 type Outer struct { | |
272 A int16 | |
273 I []Inner1 | |
274 J Inner2 | |
275 Inner3 | |
276 } | |
277 | |
278 type OuterEquivalent struct { | |
279 A int16 | |
280 IDotW []int32 `gae:"I.W"` | |
281 IDotX []string `gae:"I.X"` | |
282 JDotY float64 `gae:"J.Y"` | |
283 Z bool | |
284 } | |
285 | |
286 type Dotted struct { | |
287 A DottedA `gae:"A0.A1.A2"` | |
288 } | |
289 | |
290 type DottedA struct { | |
291 B DottedB `gae:"B3"` | |
292 } | |
293 | |
294 type DottedB struct { | |
295 C int `gae:"C4.C5"` | |
296 } | |
297 | |
298 type SliceOfSlices struct { | |
299 I int | |
300 S []struct { | |
301 J int | |
302 F []float64 | |
303 } | |
304 } | |
305 | |
306 type Recursive struct { | |
307 I int | |
308 R []Recursive | |
309 } | |
310 | |
311 type MutuallyRecursive0 struct { | |
312 I int | |
313 R []MutuallyRecursive1 | |
314 } | |
315 | |
316 type MutuallyRecursive1 struct { | |
317 I int | |
318 R []MutuallyRecursive0 | |
319 } | |
320 | |
321 type ExoticTypes struct { | |
322 BS gae.BSKey | |
323 DSBS gae.DSByteString | |
324 } | |
325 | |
326 type Underspecified struct { | |
327 Iface gae.DSPropertyConverter | |
328 } | |
329 | |
330 type MismatchTypes struct { | |
331 S string | |
332 B bool | |
333 F float32 | |
334 K gae.DSKey | |
335 T time.Time | |
336 G gae.DSGeoPoint | |
337 IS []int | |
338 } | |
339 | |
340 type BadSpecial struct { | |
341 ID int64 `gae:"$id"` | |
342 id string `gae:"$id"` | |
343 } | |
344 | |
345 type Doubler struct { | |
346 S string | |
347 I int64 | |
348 B bool | |
349 } | |
350 | |
351 func (d *Doubler) Load(props gae.DSPropertyMap) error { | |
352 return GetPLS(d).Load(props) | |
353 } | |
354 | |
355 func (d *Doubler) Save(withMeta bool) (gae.DSPropertyMap, error) { | |
356 pls := GetPLS(d) | |
357 propMap, err := pls.Save(withMeta) | |
358 if err != nil { | |
359 return nil, err | |
360 } | |
361 | |
362 // Edit that map and send it on. | |
363 for _, props := range propMap { | |
364 for i := range props { | |
365 switch v := props[i].Value().(type) { | |
366 case string: | |
367 // + means string concatenation. | |
368 props[i].SetValue(v+v, props[i].IndexSetting()) | |
369 case int64: | |
370 // + means integer addition. | |
371 props[i].SetValue(v+v, props[i].IndexSetting()) | |
372 } | |
373 } | |
374 } | |
375 return propMap, nil | |
376 } | |
377 | |
378 func (d *Doubler) GetMeta(string) (interface{}, error) { return nil, gae.ErrDSMe
taFieldUnset } | |
379 func (d *Doubler) SetMeta(string, interface{}) error { return gae.ErrDSMetaFie
ldUnset } | |
380 func (d *Doubler) Problem() error { return nil } | |
381 | |
382 var _ gae.DSPropertyLoadSaver = (*Doubler)(nil) | |
383 | |
384 type Deriver struct { | |
385 S, Derived, Ignored string | |
386 } | |
387 | |
388 func (d *Deriver) Load(props gae.DSPropertyMap) error { | |
389 for name, p := range props { | |
390 if name != "S" { | |
391 continue | |
392 } | |
393 d.S = p[0].Value().(string) | |
394 d.Derived = "derived+" + d.S | |
395 } | |
396 return nil | |
397 } | |
398 | |
399 func (d *Deriver) Save(withMeta bool) (gae.DSPropertyMap, error) { | |
400 return map[string][]gae.DSProperty{ | |
401 "S": {mp(d.S)}, | |
402 }, nil | |
403 } | |
404 | |
405 func (d *Deriver) GetMeta(string) (interface{}, error) { return nil, gae.ErrDSMe
taFieldUnset } | |
406 func (d *Deriver) SetMeta(string, interface{}) error { return gae.ErrDSMetaFie
ldUnset } | |
407 func (d *Deriver) Problem() error { return nil } | |
408 | |
409 var _ gae.DSPropertyLoadSaver = (*Deriver)(nil) | |
410 | |
411 type BK struct { | |
412 Key gae.BSKey | |
413 } | |
414 | |
415 type Convertable []int64 | |
416 | |
417 var _ gae.DSPropertyConverter = (*Convertable)(nil) | |
418 | |
419 func (c *Convertable) ToDSProperty() (ret gae.DSProperty, err error) { | |
420 buf := make([]string, len(*c)) | |
421 for i, v := range *c { | |
422 buf[i] = strconv.FormatInt(v, 10) | |
423 } | |
424 err = ret.SetValue(strings.Join(buf, ","), gae.NoIndex) | |
425 return | |
426 } | |
427 | |
428 func (c *Convertable) FromDSProperty(pv gae.DSProperty) error { | |
429 if sval, ok := pv.Value().(string); ok { | |
430 for _, t := range strings.Split(sval, ",") { | |
431 ival, err := strconv.ParseInt(t, 10, 64) | |
432 if err != nil { | |
433 return err | |
434 } | |
435 *c = append(*c, ival) | |
436 } | |
437 return nil | |
438 } | |
439 return fmt.Errorf("nope") | |
440 } | |
441 | |
442 type Impossible struct { | |
443 Nested []ImpossibleInner | |
444 } | |
445 | |
446 type ImpossibleInner struct { | |
447 Ints Convertable `gae:"wot"` | |
448 } | |
449 | |
450 type Convertable2 struct { | |
451 Data string | |
452 Exploded []string | |
453 } | |
454 | |
455 func (c *Convertable2) ToDSProperty() (ret gae.DSProperty, err error) { | |
456 err = ret.SetValue(c.Data, gae.ShouldIndex) | |
457 return | |
458 } | |
459 | |
460 func (c *Convertable2) FromDSProperty(pv gae.DSProperty) error { | |
461 if sval, ok := pv.Value().(string); ok { | |
462 c.Data = sval | |
463 c.Exploded = []string{"turn", "down", "for", "what"} | |
464 return nil | |
465 } | |
466 return fmt.Errorf("nope") | |
467 } | |
468 | |
469 type Impossible2 struct { | |
470 Nested []ImpossibleInner2 | |
471 } | |
472 | |
473 type ImpossibleInner2 struct { | |
474 Thingy Convertable2 `gae:"nerb"` | |
475 } | |
476 | |
477 type JSONKVProp map[string]interface{} | |
478 | |
479 var _ gae.DSPropertyConverter = (*JSONKVProp)(nil) | |
480 | |
481 func (j *JSONKVProp) ToDSProperty() (ret gae.DSProperty, err error) { | |
482 data, err := json.Marshal(map[string]interface{}(*j)) | |
483 if err != nil { | |
484 return | |
485 } | |
486 err = ret.SetValue(data, gae.NoIndex) | |
487 return | |
488 } | |
489 | |
490 func (j *JSONKVProp) FromDSProperty(pv gae.DSProperty) error { | |
491 if bval, ok := pv.Value().([]byte); ok { | |
492 dec := json.NewDecoder(bytes.NewBuffer(bval)) | |
493 dec.UseNumber() | |
494 return dec.Decode((*map[string]interface{})(j)) | |
495 } | |
496 return fmt.Errorf("nope") | |
497 } | |
498 | |
499 type Impossible3 struct { | |
500 KMap JSONKVProp `gae:"kewelmap"` | |
501 } | |
502 | |
503 type Complex complex128 | |
504 | |
505 var _ gae.DSPropertyConverter = (*Complex)(nil) | |
506 | |
507 func (c *Complex) ToDSProperty() (ret gae.DSProperty, err error) { | |
508 // cheat hardkore and usurp GeoPoint so datastore will index these sucke
rs | |
509 // (note that this won't REALLY work, since GeoPoints are limited to a v
ery | |
510 // limited range of values, but it's nice to pretend ;)). You'd probably | |
511 // really end up with a packed binary representation. | |
512 err = ret.SetValue(gae.DSGeoPoint{Lat: real(*c), Lng: imag(*c)}, gae.Sho
uldIndex) | |
513 return | |
514 } | |
515 | |
516 func (c *Complex) FromDSProperty(p gae.DSProperty) error { | |
517 if gval, ok := p.Value().(gae.DSGeoPoint); ok { | |
518 *c = Complex(complex(gval.Lat, gval.Lng)) | |
519 return nil | |
520 } | |
521 return fmt.Errorf("nope") | |
522 } | |
523 | |
524 type Impossible4 struct { | |
525 Values []Complex | |
526 } | |
527 | |
528 type DerivedKey struct { | |
529 K *GenericDSKey | |
530 } | |
531 | |
532 type IfaceKey struct { | |
533 K gae.DSKey | |
534 } | |
535 | |
536 type testCase struct { | |
537 desc string | |
538 src interface{} | |
539 want interface{} | |
540 plsErr string | |
541 saveErr string | |
542 actualNoIndex bool | |
543 plsLoadErr string | |
544 loadErr string | |
545 } | |
546 | |
547 var testCases = []testCase{ | |
548 { | |
549 desc: "chan save fails", | |
550 src: &C0{I: -1}, | |
551 plsErr: `field "C" has invalid type: chan int`, | |
552 }, | |
553 { | |
554 desc: "*chan save fails", | |
555 src: &C1{I: -1}, | |
556 plsErr: `field "C" has invalid type: *chan int`, | |
557 }, | |
558 { | |
559 desc: "[]chan save fails", | |
560 src: &C2{I: -1, C: make([]chan int, 8)}, | |
561 plsErr: `field "C" has invalid type: []chan int`, | |
562 }, | |
563 { | |
564 desc: "chan load fails", | |
565 src: &C3{C: "not a chan"}, | |
566 want: &C0{}, | |
567 plsLoadErr: `field "C" has invalid type: chan int`, | |
568 }, | |
569 { | |
570 desc: "*chan load fails", | |
571 src: &C3{C: "not a *chan"}, | |
572 want: &C1{}, | |
573 plsLoadErr: `field "C" has invalid type: *chan int`, | |
574 }, | |
575 { | |
576 desc: "[]chan load fails", | |
577 src: &C3{C: "not a []chan"}, | |
578 want: &C2{}, | |
579 plsLoadErr: `field "C" has invalid type: []chan int`, | |
580 }, | |
581 { | |
582 desc: "empty struct", | |
583 src: &E{}, | |
584 want: &E{}, | |
585 }, | |
586 { | |
587 desc: "geopoint", | |
588 src: &G0{G: testGeoPt0}, | |
589 want: &G0{G: testGeoPt0}, | |
590 }, | |
591 { | |
592 desc: "geopoint invalid", | |
593 src: &G0{G: testBadGeoPt}, | |
594 saveErr: "invalid GeoPoint value", | |
595 }, | |
596 { | |
597 desc: "geopoint as props", | |
598 src: &G0{G: testGeoPt0}, | |
599 want: gae.DSPropertyMap{ | |
600 "G": {mp(testGeoPt0)}, | |
601 }, | |
602 }, | |
603 { | |
604 desc: "geopoint slice", | |
605 src: &G1{G: []gae.DSGeoPoint{testGeoPt0, testGeoPt1}}, | |
606 want: &G1{G: []gae.DSGeoPoint{testGeoPt0, testGeoPt1}}, | |
607 }, | |
608 { | |
609 desc: "key", | |
610 src: &K0{K: testKey1a}, | |
611 want: &K0{K: testKey1b}, | |
612 }, | |
613 { | |
614 desc: "key with parent", | |
615 src: &K0{K: testKey2a}, | |
616 want: &K0{K: testKey2b}, | |
617 }, | |
618 { | |
619 desc: "nil key", | |
620 src: &K0{}, | |
621 want: &K0{}, | |
622 }, | |
623 { | |
624 desc: "all nil keys in slice", | |
625 src: &K1{[]gae.DSKey{nil, nil}}, | |
626 want: &K1{[]gae.DSKey{nil, nil}}, | |
627 }, | |
628 { | |
629 desc: "some nil keys in slice", | |
630 src: &K1{[]gae.DSKey{testKey1a, nil, testKey2a}}, | |
631 want: &K1{[]gae.DSKey{testKey1b, nil, testKey2b}}, | |
632 }, | |
633 { | |
634 desc: "overflow", | |
635 src: &O0{I: 1 << 48}, | |
636 want: &O1{}, | |
637 loadErr: "overflow", | |
638 }, | |
639 { | |
640 desc: "time", | |
641 src: &T{T: time.Unix(1e9, 0).UTC()}, | |
642 want: &T{T: time.Unix(1e9, 0).UTC()}, | |
643 }, | |
644 { | |
645 desc: "time as props", | |
646 src: &T{T: time.Unix(1e9, 0).UTC()}, | |
647 want: gae.DSPropertyMap{ | |
648 "T": {mp(time.Unix(1e9, 0).UTC())}, | |
649 }, | |
650 }, | |
651 { | |
652 desc: "uint save", | |
653 src: &U0{U: 1}, | |
654 plsErr: `field "U" has invalid type: uint`, | |
655 }, | |
656 { | |
657 desc: "uint load", | |
658 src: &U1{U: "not a uint"}, | |
659 want: &U0{}, | |
660 plsLoadErr: `field "U" has invalid type: uint`, | |
661 }, | |
662 { | |
663 desc: "zero", | |
664 src: &X0{}, | |
665 want: &X0{}, | |
666 }, | |
667 { | |
668 desc: "basic", | |
669 src: &X0{S: "one", I: 2, i: 3}, | |
670 want: &X0{S: "one", I: 2}, | |
671 }, | |
672 { | |
673 desc: "save string/int load myString/int32", | |
674 src: &X0{S: "one", I: 2, i: 3}, | |
675 want: &X1{S: "one", I: 2}, | |
676 }, | |
677 { | |
678 desc: "missing fields", | |
679 src: &X0{S: "one", I: 2, i: 3}, | |
680 want: &X2{}, | |
681 loadErr: "no such struct field", | |
682 }, | |
683 { | |
684 desc: "save string load bool", | |
685 src: &X0{S: "one", I: 2, i: 3}, | |
686 want: &X3{I: 2}, | |
687 loadErr: "type mismatch", | |
688 }, | |
689 { | |
690 desc: "basic slice", | |
691 src: &Y0{B: true, F: []float64{7, 8, 9}}, | |
692 want: &Y0{B: true, F: []float64{7, 8, 9}}, | |
693 }, | |
694 { | |
695 desc: "save []float64 load float64", | |
696 src: &Y0{B: true, F: []float64{7, 8, 9}}, | |
697 want: &Y1{B: true}, | |
698 loadErr: "requires a slice", | |
699 }, | |
700 { | |
701 desc: "save single []int64 load int64", | |
702 src: &Y2{B: true, F: []int64{7}}, | |
703 want: &Y3{B: true, F: 7}, | |
704 }, | |
705 { | |
706 desc: "save int64 load single []int64", | |
707 src: &Y3{B: true, F: 7}, | |
708 want: &Y2{B: true, F: []int64{7}}, | |
709 }, | |
710 { | |
711 desc: "use convertable slice", | |
712 src: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con
vertable{2, 4, 6}}}}, | |
713 want: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con
vertable{2, 4, 6}}}}, | |
714 }, | |
715 { | |
716 desc: "use convertable slice (to map)", | |
717 src: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con
vertable{2, 4, 6}}}}, | |
718 want: gae.DSPropertyMap{ | |
719 "Nested.wot": {mpNI("1,5,9"), mpNI("2,4,6")}, | |
720 }, | |
721 }, | |
722 { | |
723 desc: "convertable slice (bad load)", | |
724 src: gae.DSPropertyMap{"Nested.wot": {mpNI([]byte("ohai"))}}
, | |
725 want: &Impossible{[]ImpossibleInner{{}}}, | |
726 loadErr: "nope", | |
727 }, | |
728 { | |
729 desc: "use convertable struct", | |
730 src: &Impossible2{ | |
731 []ImpossibleInner2{ | |
732 {Convertable2{"nerb", nil}}, | |
733 }, | |
734 }, | |
735 want: &Impossible2{ | |
736 []ImpossibleInner2{ | |
737 {Convertable2{"nerb", []string{"turn", "down", "
for", "what"}}}, | |
738 }, | |
739 }, | |
740 }, | |
741 { | |
742 desc: "convertable json KVMap", | |
743 src: &Impossible3{ | |
744 JSONKVProp{ | |
745 "epic": "success", | |
746 "no_way!": []interface{}{true, "story"}, | |
747 "what": []interface{}{"is", "really", 100}, | |
748 }, | |
749 }, | |
750 want: &Impossible3{ | |
751 JSONKVProp{ | |
752 "epic": "success", | |
753 "no_way!": []interface{}{true, "story"}, | |
754 "what": []interface{}{"is", "really", json.Nu
mber("100")}, | |
755 }, | |
756 }, | |
757 }, | |
758 { | |
759 desc: "convertable json KVMap (to map)", | |
760 src: &Impossible3{ | |
761 JSONKVProp{ | |
762 "epic": "success", | |
763 "no_way!": []interface{}{true, "story"}, | |
764 "what": []interface{}{"is", "really", 100}, | |
765 }, | |
766 }, | |
767 want: gae.DSPropertyMap{ | |
768 "kewelmap": { | |
769 mpNI([]byte( | |
770 `{"epic":"success","no_way!":[true,"stor
y"],"what":["is","really",100]}`))}, | |
771 }, | |
772 }, | |
773 { | |
774 desc: "convertable complex slice", | |
775 src: &Impossible4{ | |
776 []Complex{complex(1, 2), complex(3, 4)}, | |
777 }, | |
778 want: &Impossible4{ | |
779 []Complex{complex(1, 2), complex(3, 4)}, | |
780 }, | |
781 }, | |
782 { | |
783 desc: "convertable complex slice (to map)", | |
784 src: &Impossible4{ | |
785 []Complex{complex(1, 2), complex(3, 4)}, | |
786 }, | |
787 want: gae.DSPropertyMap{ | |
788 "Values": { | |
789 mp(gae.DSGeoPoint{Lat: 1, Lng: 2}), mp(gae.DSGeo
Point{Lat: 3, Lng: 4})}, | |
790 }, | |
791 }, | |
792 { | |
793 desc: "convertable complex slice (bad load)", | |
794 src: gae.DSPropertyMap{"Values": {mp("hello")}}, | |
795 want: &Impossible4{[]Complex(nil)}, | |
796 loadErr: "nope", | |
797 }, | |
798 { | |
799 desc: "allow concrete gae.DSKey implementors (save)", | |
800 src: &DerivedKey{testKey2a.(*GenericDSKey)}, | |
801 want: &IfaceKey{testKey2b}, | |
802 }, | |
803 { | |
804 desc: "allow concrete gae.DSKey implementors (load)", | |
805 src: &IfaceKey{testKey2b}, | |
806 want: &DerivedKey{testKey2a.(*GenericDSKey)}, | |
807 }, | |
808 { | |
809 desc: "save []float64 load []int64", | |
810 src: &Y0{B: true, F: []float64{7, 8, 9}}, | |
811 want: &Y2{B: true}, | |
812 loadErr: "type mismatch", | |
813 }, | |
814 { | |
815 desc: "single slice is too long", | |
816 src: &Y0{F: make([]float64, maxIndexedProperties+1)}, | |
817 want: &Y0{}, | |
818 saveErr: "gae: too many indexed properties", | |
819 }, | |
820 { | |
821 desc: "two slices are too long", | |
822 src: &Y0{F: make([]float64, maxIndexedProperties), G: make([
]float64, maxIndexedProperties)}, | |
823 want: &Y0{}, | |
824 saveErr: "gae: too many indexed properties", | |
825 }, | |
826 { | |
827 desc: "one slice and one scalar are too long", | |
828 src: &Y0{F: make([]float64, maxIndexedProperties), B: true}, | |
829 want: &Y0{}, | |
830 saveErr: "gae: too many indexed properties", | |
831 }, | |
832 { | |
833 desc: "long blob", | |
834 src: &B0{B: makeUint8Slice(maxIndexedProperties + 1)}, | |
835 want: &B0{B: makeUint8Slice(maxIndexedProperties + 1)}, | |
836 }, | |
837 { | |
838 desc: "long []int8 is too long", | |
839 src: &B1{B: makeInt8Slice(maxIndexedProperties + 1)}, | |
840 want: &B1{}, | |
841 saveErr: "gae: too many indexed properties", | |
842 }, | |
843 { | |
844 desc: "short []int8", | |
845 src: &B1{B: makeInt8Slice(3)}, | |
846 want: &B1{B: makeInt8Slice(3)}, | |
847 }, | |
848 { | |
849 desc: "long myBlob", | |
850 src: &B2{B: makeUint8Slice(maxIndexedProperties + 1)}, | |
851 want: &B2{B: makeUint8Slice(maxIndexedProperties + 1)}, | |
852 }, | |
853 { | |
854 desc: "short myBlob", | |
855 src: &B2{B: makeUint8Slice(3)}, | |
856 want: &B2{B: makeUint8Slice(3)}, | |
857 }, | |
858 { | |
859 desc: "long []myByte", | |
860 src: &B3{B: makeMyByteSlice(maxIndexedProperties + 1)}, | |
861 want: &B3{B: makeMyByteSlice(maxIndexedProperties + 1)}, | |
862 }, | |
863 { | |
864 desc: "short []myByte", | |
865 src: &B3{B: makeMyByteSlice(3)}, | |
866 want: &B3{B: makeMyByteSlice(3)}, | |
867 }, | |
868 { | |
869 desc: "slice of blobs", | |
870 src: &B4{B: [][]byte{ | |
871 makeUint8Slice(3), | |
872 makeUint8Slice(4), | |
873 makeUint8Slice(5), | |
874 }}, | |
875 want: &B4{B: [][]byte{ | |
876 makeUint8Slice(3), | |
877 makeUint8Slice(4), | |
878 makeUint8Slice(5), | |
879 }}, | |
880 }, | |
881 { | |
882 desc: "short gae.DSByteString", | |
883 src: &B5{B: gae.DSByteString(makeUint8Slice(3))}, | |
884 want: &B5{B: gae.DSByteString(makeUint8Slice(3))}, | |
885 }, | |
886 { | |
887 desc: "short gae.DSByteString as props", | |
888 src: &B5{B: gae.DSByteString(makeUint8Slice(3))}, | |
889 want: gae.DSPropertyMap{ | |
890 "B": {mp(gae.DSByteString(makeUint8Slice(3)))}, | |
891 }, | |
892 }, | |
893 { | |
894 desc: "[]byte must be noindex", | |
895 src: gae.DSPropertyMap{ | |
896 "B": {mp(makeUint8Slice(3))}, | |
897 }, | |
898 actualNoIndex: true, | |
899 }, | |
900 { | |
901 desc: "save tagged load props", | |
902 src: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6
, J: 7}, | |
903 want: gae.DSPropertyMap{ | |
904 // A and B are renamed to a and b; A and C are noindex,
I is ignored. | |
905 // Indexed properties are loaded before raw properties.
Thus, the | |
906 // result is: b, b, b, D, E, a, c. | |
907 "b1": { | |
908 mp(21), | |
909 mp(22), | |
910 mp(23), | |
911 }, | |
912 "D": {mp(4)}, | |
913 "E": {mp(5)}, | |
914 "a": {mpNI(1)}, | |
915 "C": {mpNI(3)}, | |
916 "J": {mpNI(7)}, | |
917 }, | |
918 }, | |
919 { | |
920 desc: "save tagged load tagged", | |
921 src: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6
, J: 7}, | |
922 want: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, J: 7
}, | |
923 }, | |
924 { | |
925 desc: "save props load tagged", | |
926 src: gae.DSPropertyMap{ | |
927 "A": {mpNI(11)}, | |
928 "a": {mpNI(12)}, | |
929 }, | |
930 want: &Tagged{A: 12}, | |
931 loadErr: `cannot load field "A"`, | |
932 }, | |
933 { | |
934 desc: "invalid tagged1", | |
935 src: &InvalidTagged1{I: 1}, | |
936 plsErr: `struct tag has invalid property name: "\t"`, | |
937 }, | |
938 { | |
939 desc: "invalid tagged2", | |
940 src: &InvalidTagged2{I: 1, J: 2}, | |
941 want: &InvalidTagged2{}, | |
942 plsErr: `struct tag has repeated property name: "I"`, | |
943 }, | |
944 { | |
945 desc: "invalid tagged3", | |
946 src: &InvalidTagged3{I: 1}, | |
947 plsErr: `struct tag has invalid property name: "a\t"`, | |
948 }, | |
949 { | |
950 desc: "invalid tagged4", | |
951 src: &InvalidTagged4{I: 1}, | |
952 plsErr: `struct tag has invalid property name: "a."`, | |
953 }, | |
954 { | |
955 desc: "invalid tagged5", | |
956 src: &InvalidTagged5{I: 19, V: []InvalidTaggedSub{{1}}}, | |
957 plsErr: `struct tag has repeated property name: "V.I"`, | |
958 }, | |
959 { | |
960 desc: "doubler", | |
961 src: &Doubler{S: "s", I: 1, B: true}, | |
962 want: &Doubler{S: "ss", I: 2, B: true}, | |
963 }, | |
964 { | |
965 desc: "save struct load props", | |
966 src: &X0{S: "s", I: 1}, | |
967 want: gae.DSPropertyMap{ | |
968 "S": {mp("s")}, | |
969 "I": {mp(1)}, | |
970 }, | |
971 }, | |
972 { | |
973 desc: "save props load struct", | |
974 src: gae.DSPropertyMap{ | |
975 "S": {mp("s")}, | |
976 "I": {mp(1)}, | |
977 }, | |
978 want: &X0{S: "s", I: 1}, | |
979 }, | |
980 { | |
981 desc: "nil-value props", | |
982 src: gae.DSPropertyMap{ | |
983 "I": {mp(nil)}, | |
984 "B": {mp(nil)}, | |
985 "S": {mp(nil)}, | |
986 "F": {mp(nil)}, | |
987 "K": {mp(nil)}, | |
988 "T": {mp(nil)}, | |
989 "J": { | |
990 mp(nil), | |
991 mp(7), | |
992 mp(nil), | |
993 }, | |
994 }, | |
995 want: &struct { | |
996 I int64 | |
997 B bool | |
998 S string | |
999 F float64 | |
1000 K gae.DSKey | |
1001 T time.Time | |
1002 J []int64 | |
1003 }{ | |
1004 J: []int64{0, 7, 0}, | |
1005 }, | |
1006 }, | |
1007 { | |
1008 desc: "save outer load props", | |
1009 src: &Outer{ | |
1010 A: 1, | |
1011 I: []Inner1{ | |
1012 {10, "ten"}, | |
1013 {20, "twenty"}, | |
1014 {30, "thirty"}, | |
1015 }, | |
1016 J: Inner2{ | |
1017 Y: 3.14, | |
1018 }, | |
1019 Inner3: Inner3{ | |
1020 Z: true, | |
1021 }, | |
1022 }, | |
1023 want: gae.DSPropertyMap{ | |
1024 "A": {mp(1)}, | |
1025 "I.W": { | |
1026 mp(10), | |
1027 mp(20), | |
1028 mp(30), | |
1029 }, | |
1030 "I.X": { | |
1031 mp("ten"), | |
1032 mp("twenty"), | |
1033 mp("thirty"), | |
1034 }, | |
1035 "J.Y": {mp(3.14)}, | |
1036 "Z": {mp(true)}, | |
1037 }, | |
1038 }, | |
1039 { | |
1040 desc: "save props load outer-equivalent", | |
1041 src: gae.DSPropertyMap{ | |
1042 "A": {mp(1)}, | |
1043 "I.W": { | |
1044 mp(10), | |
1045 mp(20), | |
1046 mp(30), | |
1047 }, | |
1048 "I.X": { | |
1049 mp("ten"), | |
1050 mp("twenty"), | |
1051 mp("thirty"), | |
1052 }, | |
1053 "J.Y": {mp(3.14)}, | |
1054 "Z": {mp(true)}, | |
1055 }, | |
1056 want: &OuterEquivalent{ | |
1057 A: 1, | |
1058 IDotW: []int32{10, 20, 30}, | |
1059 IDotX: []string{"ten", "twenty", "thirty"}, | |
1060 JDotY: 3.14, | |
1061 Z: true, | |
1062 }, | |
1063 }, | |
1064 { | |
1065 desc: "save outer-equivalent load outer", | |
1066 src: &OuterEquivalent{ | |
1067 A: 1, | |
1068 IDotW: []int32{10, 20, 30}, | |
1069 IDotX: []string{"ten", "twenty", "thirty"}, | |
1070 JDotY: 3.14, | |
1071 Z: true, | |
1072 }, | |
1073 want: &Outer{ | |
1074 A: 1, | |
1075 I: []Inner1{ | |
1076 {10, "ten"}, | |
1077 {20, "twenty"}, | |
1078 {30, "thirty"}, | |
1079 }, | |
1080 J: Inner2{ | |
1081 Y: 3.14, | |
1082 }, | |
1083 Inner3: Inner3{ | |
1084 Z: true, | |
1085 }, | |
1086 }, | |
1087 }, | |
1088 { | |
1089 desc: "dotted names save", | |
1090 src: &Dotted{A: DottedA{B: DottedB{C: 88}}}, | |
1091 want: gae.DSPropertyMap{ | |
1092 "A0.A1.A2.B3.C4.C5": {mp(88)}, | |
1093 }, | |
1094 }, | |
1095 { | |
1096 desc: "dotted names load", | |
1097 src: gae.DSPropertyMap{ | |
1098 "A0.A1.A2.B3.C4.C5": {mp(99)}, | |
1099 }, | |
1100 want: &Dotted{A: DottedA{B: DottedB{C: 99}}}, | |
1101 }, | |
1102 { | |
1103 desc: "save struct load deriver", | |
1104 src: &X0{S: "s", I: 1}, | |
1105 want: &Deriver{S: "s", Derived: "derived+s"}, | |
1106 }, | |
1107 { | |
1108 desc: "save deriver load struct", | |
1109 src: &Deriver{S: "s", Derived: "derived+s", Ignored: "ignored"}
, | |
1110 want: &X0{S: "s"}, | |
1111 }, | |
1112 // Regression: CL 25062824 broke handling of appengine.BlobKey fields. | |
1113 { | |
1114 desc: "appengine.BlobKey", | |
1115 src: &BK{Key: "blah"}, | |
1116 want: &BK{Key: "blah"}, | |
1117 }, | |
1118 { | |
1119 desc: "zero time.Time", | |
1120 src: &T{T: time.Time{}}, | |
1121 want: &T{T: time.Time{}}, | |
1122 }, | |
1123 { | |
1124 desc: "time.Time near Unix zero time", | |
1125 src: &T{T: time.Unix(0, 4e3).UTC()}, | |
1126 want: &T{T: time.Unix(0, 4e3).UTC()}, | |
1127 }, | |
1128 { | |
1129 desc: "time.Time, far in the future", | |
1130 src: &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)}, | |
1131 want: &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)}, | |
1132 }, | |
1133 { | |
1134 desc: "time.Time, very far in the past", | |
1135 src: &T{T: time.Date(-300000, 1, 1, 0, 0, 0, 0, time.UTC)}, | |
1136 want: &T{}, | |
1137 saveErr: "time value out of range", | |
1138 }, | |
1139 { | |
1140 desc: "time.Time, very far in the future", | |
1141 src: &T{T: time.Date(294248, 1, 1, 0, 0, 0, 0, time.UTC)}, | |
1142 want: &T{}, | |
1143 saveErr: "time value out of range", | |
1144 }, | |
1145 { | |
1146 desc: "structs", | |
1147 src: &N0{ | |
1148 X0: X0{S: "one", I: 2, i: 3}, | |
1149 Nonymous: X0{S: "four", I: 5, i: 6}, | |
1150 Ignore: "ignore", | |
1151 Other: "other", | |
1152 }, | |
1153 want: &N0{ | |
1154 X0: X0{S: "one", I: 2}, | |
1155 Nonymous: X0{S: "four", I: 5}, | |
1156 Other: "other", | |
1157 }, | |
1158 }, | |
1159 { | |
1160 desc: "exotic types", | |
1161 src: &ExoticTypes{ | |
1162 BS: "sup", | |
1163 DSBS: gae.DSByteString("nerds"), | |
1164 }, | |
1165 want: &ExoticTypes{ | |
1166 BS: "sup", | |
1167 DSBS: gae.DSByteString("nerds"), | |
1168 }, | |
1169 }, | |
1170 { | |
1171 desc: "underspecified types", | |
1172 src: &Underspecified{}, | |
1173 plsErr: "non-concrete interface", | |
1174 }, | |
1175 { | |
1176 desc: "mismatch (string)", | |
1177 src: gae.DSPropertyMap{ | |
1178 "K": {mp(199)}, | |
1179 "S": {mp([]byte("cats"))}, | |
1180 "F": {mp(gae.DSByteString("nurbs"))}, | |
1181 }, | |
1182 want: &MismatchTypes{}, | |
1183 loadErr: "type mismatch", | |
1184 }, | |
1185 { | |
1186 desc: "mismatch (float)", | |
1187 src: gae.DSPropertyMap{"F": {mp(gae.BSKey("wot"))}}, | |
1188 want: &MismatchTypes{}, | |
1189 loadErr: "type mismatch", | |
1190 }, | |
1191 { | |
1192 desc: "mismatch (float/overflow)", | |
1193 src: gae.DSPropertyMap{"F": {mp(math.MaxFloat64)}}, | |
1194 want: &MismatchTypes{}, | |
1195 loadErr: "overflows", | |
1196 }, | |
1197 { | |
1198 desc: "mismatch (key)", | |
1199 src: gae.DSPropertyMap{"K": {mp(false)}}, | |
1200 want: &MismatchTypes{}, | |
1201 loadErr: "type mismatch", | |
1202 }, | |
1203 { | |
1204 desc: "mismatch (bool)", | |
1205 src: gae.DSPropertyMap{"B": {mp(testKey0)}}, | |
1206 want: &MismatchTypes{}, | |
1207 loadErr: "type mismatch", | |
1208 }, | |
1209 { | |
1210 desc: "mismatch (time)", | |
1211 src: gae.DSPropertyMap{"T": {mp(gae.DSGeoPoint{})}}, | |
1212 want: &MismatchTypes{}, | |
1213 loadErr: "type mismatch", | |
1214 }, | |
1215 { | |
1216 desc: "mismatch (geopoint)", | |
1217 src: gae.DSPropertyMap{"G": {mp(time.Now().UTC())}}, | |
1218 want: &MismatchTypes{}, | |
1219 loadErr: "type mismatch", | |
1220 }, | |
1221 { | |
1222 desc: "slice of structs", | |
1223 src: &N1{ | |
1224 X0: X0{S: "one", I: 2, i: 3}, | |
1225 Nonymous: []X0{ | |
1226 {S: "four", I: 5, i: 6}, | |
1227 {S: "seven", I: 8, i: 9}, | |
1228 {S: "ten", I: 11, i: 12}, | |
1229 {S: "thirteen", I: 14, i: 15}, | |
1230 }, | |
1231 Ignore: "ignore", | |
1232 Other: "other", | |
1233 }, | |
1234 want: &N1{ | |
1235 X0: X0{S: "one", I: 2}, | |
1236 Nonymous: []X0{ | |
1237 {S: "four", I: 5}, | |
1238 {S: "seven", I: 8}, | |
1239 {S: "ten", I: 11}, | |
1240 {S: "thirteen", I: 14}, | |
1241 }, | |
1242 Other: "other", | |
1243 }, | |
1244 }, | |
1245 { | |
1246 desc: "structs with slices of structs", | |
1247 src: &N2{ | |
1248 N1: N1{ | |
1249 X0: X0{S: "rouge"}, | |
1250 Nonymous: []X0{ | |
1251 {S: "rosso0"}, | |
1252 {S: "rosso1"}, | |
1253 }, | |
1254 }, | |
1255 Green: N1{ | |
1256 X0: X0{S: "vert"}, | |
1257 Nonymous: []X0{ | |
1258 {S: "verde0"}, | |
1259 {S: "verde1"}, | |
1260 {S: "verde2"}, | |
1261 }, | |
1262 }, | |
1263 Blue: N1{ | |
1264 X0: X0{S: "bleu"}, | |
1265 Nonymous: []X0{ | |
1266 {S: "blu0"}, | |
1267 {S: "blu1"}, | |
1268 {S: "blu2"}, | |
1269 {S: "blu3"}, | |
1270 }, | |
1271 }, | |
1272 }, | |
1273 want: &N2{ | |
1274 N1: N1{ | |
1275 X0: X0{S: "rouge"}, | |
1276 Nonymous: []X0{ | |
1277 {S: "rosso0"}, | |
1278 {S: "rosso1"}, | |
1279 }, | |
1280 }, | |
1281 Green: N1{ | |
1282 X0: X0{S: "vert"}, | |
1283 Nonymous: []X0{ | |
1284 {S: "verde0"}, | |
1285 {S: "verde1"}, | |
1286 {S: "verde2"}, | |
1287 }, | |
1288 }, | |
1289 Blue: N1{ | |
1290 X0: X0{S: "bleu"}, | |
1291 Nonymous: []X0{ | |
1292 {S: "blu0"}, | |
1293 {S: "blu1"}, | |
1294 {S: "blu2"}, | |
1295 {S: "blu3"}, | |
1296 }, | |
1297 }, | |
1298 }, | |
1299 }, | |
1300 { | |
1301 desc: "save structs load props", | |
1302 src: &N2{ | |
1303 N1: N1{ | |
1304 X0: X0{S: "rouge"}, | |
1305 Nonymous: []X0{ | |
1306 {S: "rosso0"}, | |
1307 {S: "rosso1"}, | |
1308 }, | |
1309 }, | |
1310 Green: N1{ | |
1311 X0: X0{S: "vert"}, | |
1312 Nonymous: []X0{ | |
1313 {S: "verde0"}, | |
1314 {S: "verde1"}, | |
1315 {S: "verde2"}, | |
1316 }, | |
1317 }, | |
1318 Blue: N1{ | |
1319 X0: X0{S: "bleu"}, | |
1320 Nonymous: []X0{ | |
1321 {S: "blu0"}, | |
1322 {S: "blu1"}, | |
1323 {S: "blu2"}, | |
1324 {S: "blu3"}, | |
1325 }, | |
1326 }, | |
1327 }, | |
1328 want: gae.DSPropertyMap{ | |
1329 "red.S": {mp("rouge")}, | |
1330 "red.I": {mp(0)}, | |
1331 "red.Nonymous.S": {mp("rosso0"), mp("rosso1")}, | |
1332 "red.Nonymous.I": {mp(0), mp(0)}, | |
1333 "red.Other": {mp("")}, | |
1334 "green.S": {mp("vert")}, | |
1335 "green.I": {mp(0)}, | |
1336 "green.Nonymous.S": {mp("verde0"), mp("verde1"), mp("ver
de2")}, | |
1337 "green.Nonymous.I": {mp(0), mp(0), mp(0)}, | |
1338 "green.Other": {mp("")}, | |
1339 "Blue.S": {mp("bleu")}, | |
1340 "Blue.I": {mp(0)}, | |
1341 "Blue.Nonymous.S": {mp("blu0"), mp("blu1"), mp("blu2"),
mp("blu3")}, | |
1342 "Blue.Nonymous.I": {mp(0), mp(0), mp(0), mp(0)}, | |
1343 "Blue.Other": {mp("")}, | |
1344 }, | |
1345 }, | |
1346 { | |
1347 desc: "save props load structs with ragged fields", | |
1348 src: gae.DSPropertyMap{ | |
1349 "red.S": {mp("rot")}, | |
1350 "green.Nonymous.I": {mp(10), mp(11), mp(12), mp(13)}, | |
1351 "Blue.Nonymous.S": {mp("blau0"), mp("blau1"), mp("blau2
")}, | |
1352 "Blue.Nonymous.I": {mp(20), mp(21)}, | |
1353 }, | |
1354 want: &N2{ | |
1355 N1: N1{ | |
1356 X0: X0{S: "rot"}, | |
1357 }, | |
1358 Green: N1{ | |
1359 Nonymous: []X0{ | |
1360 {I: 10}, | |
1361 {I: 11}, | |
1362 {I: 12}, | |
1363 {I: 13}, | |
1364 }, | |
1365 }, | |
1366 Blue: N1{ | |
1367 Nonymous: []X0{ | |
1368 {S: "blau0", I: 20}, | |
1369 {S: "blau1", I: 21}, | |
1370 {S: "blau2"}, | |
1371 }, | |
1372 }, | |
1373 }, | |
1374 }, | |
1375 { | |
1376 desc: "save structs with noindex tags", | |
1377 src: &struct { | |
1378 A struct { | |
1379 X string `gae:",noindex"` | |
1380 Y string | |
1381 } `gae:",noindex"` | |
1382 B struct { | |
1383 X string `gae:",noindex"` | |
1384 Y string | |
1385 } | |
1386 }{}, | |
1387 want: gae.DSPropertyMap{ | |
1388 "B.Y": {mp("")}, | |
1389 "A.X": {mpNI("")}, | |
1390 "A.Y": {mpNI("")}, | |
1391 "B.X": {mpNI("")}, | |
1392 }, | |
1393 }, | |
1394 { | |
1395 desc: "embedded struct with name override", | |
1396 src: &struct { | |
1397 Inner1 `gae:"foo"` | |
1398 }{}, | |
1399 want: gae.DSPropertyMap{ | |
1400 "foo.W": {mp(0)}, | |
1401 "foo.X": {mp("")}, | |
1402 }, | |
1403 }, | |
1404 { | |
1405 desc: "slice of slices", | |
1406 src: &SliceOfSlices{}, | |
1407 plsErr: `flattening nested structs leads to a slice of slices: f
ield "S"`, | |
1408 }, | |
1409 { | |
1410 desc: "recursive struct", | |
1411 src: &Recursive{}, | |
1412 plsErr: `field "R" is recursively defined`, | |
1413 }, | |
1414 { | |
1415 desc: "mutually recursive struct", | |
1416 src: &MutuallyRecursive0{}, | |
1417 plsErr: `field "R" has problem: field "R" is recursively defined
`, | |
1418 }, | |
1419 { | |
1420 desc: "non-exported struct fields", | |
1421 src: &struct { | |
1422 i, J int64 | |
1423 }{i: 1, J: 2}, | |
1424 want: gae.DSPropertyMap{ | |
1425 "J": {mp(2)}, | |
1426 }, | |
1427 }, | |
1428 { | |
1429 desc: "json.RawMessage", | |
1430 src: &struct { | |
1431 J json.RawMessage | |
1432 }{ | |
1433 J: json.RawMessage("rawr"), | |
1434 }, | |
1435 want: gae.DSPropertyMap{ | |
1436 "J": {mp([]byte("rawr"))}, | |
1437 }, | |
1438 }, | |
1439 { | |
1440 desc: "json.RawMessage to myBlob", | |
1441 src: &struct { | |
1442 B json.RawMessage | |
1443 }{ | |
1444 B: json.RawMessage("rawr"), | |
1445 }, | |
1446 want: &B2{B: myBlob("rawr")}, | |
1447 }, | |
1448 } | |
1449 | |
1450 // checkErr returns the empty string if either both want and err are zero, | |
1451 // or if want is a non-empty substring of err's string representation. | |
1452 func checkErr(want string, err error) string { | |
1453 if err != nil { | |
1454 got := err.Error() | |
1455 if want == "" || strings.Index(got, want) == -1 { | |
1456 return got | |
1457 } | |
1458 } else if want != "" { | |
1459 return fmt.Sprintf("want error %q", want) | |
1460 } | |
1461 return "" | |
1462 } | |
1463 | |
1464 func ShouldErrLike(actual interface{}, expected ...interface{}) string { | |
1465 e2s := func(o interface{}) (string, bool) { | |
1466 switch x := o.(type) { | |
1467 case nil: | |
1468 return "", true | |
1469 case string: | |
1470 return x, true | |
1471 case error: | |
1472 if x != nil { | |
1473 return x.Error(), true | |
1474 } | |
1475 return "", true | |
1476 } | |
1477 return fmt.Sprintf("unknown argument type %T, expected string, e
rror or nil", actual), false | |
1478 } | |
1479 | |
1480 as, ok := e2s(actual) | |
1481 if !ok { | |
1482 return as | |
1483 } | |
1484 | |
1485 if len(expected) != 1 { | |
1486 return fmt.Sprintf("Assertion requires 1 expected value, got %d"
, len(expected)) | |
1487 } | |
1488 | |
1489 err, ok := e2s(expected[0]) | |
1490 if !ok { | |
1491 return err | |
1492 } | |
1493 return ShouldContainSubstring(as, err) | |
1494 } | |
1495 | |
1496 func TestRoundTrip(t *testing.T) { | |
1497 t.Parallel() | |
1498 | |
1499 checkErr := func(actual interface{}, expected string) bool { | |
1500 So(actual, ShouldErrLike, expected) | |
1501 return expected != "" | |
1502 } | |
1503 | |
1504 Convey("Test round-trip", t, func() { | |
1505 for _, tc := range testCases { | |
1506 tc := tc | |
1507 Convey(tc.desc, func() { | |
1508 pls, ok := tc.src.(gae.DSPropertyLoadSaver) | |
1509 if !ok { | |
1510 pls = GetPLS(tc.src) | |
1511 } | |
1512 if checkErr(pls.Problem(), tc.plsErr) { | |
1513 return | |
1514 } | |
1515 So(pls, ShouldNotBeNil) | |
1516 | |
1517 savedProps, err := pls.Save(false) | |
1518 if checkErr(err, tc.saveErr) { | |
1519 return | |
1520 } | |
1521 So(savedProps, ShouldNotBeNil) | |
1522 | |
1523 if tc.actualNoIndex { | |
1524 for _, props := range savedProps { | |
1525 So(props[0].IndexSetting(), Shou
ldEqual, gae.NoIndex) | |
1526 return | |
1527 } | |
1528 So(true, ShouldBeFalse) // shouldn't get
here | |
1529 } | |
1530 | |
1531 var got interface{} | |
1532 if _, ok := tc.want.(gae.DSPropertyMap); ok { | |
1533 pls = gae.DSPropertyMap{} | |
1534 got = pls | |
1535 } else { | |
1536 got = reflect.New(reflect.TypeOf(tc.want
).Elem()).Interface() | |
1537 if pls, ok = got.(gae.DSPropertyLoadSave
r); !ok { | |
1538 pls = GetPLS(got) | |
1539 } | |
1540 } | |
1541 | |
1542 if checkErr(pls.Problem(), tc.plsLoadErr) { | |
1543 return | |
1544 } | |
1545 So(pls, ShouldNotBeNil) | |
1546 | |
1547 err = pls.Load(savedProps) | |
1548 if checkErr(err, tc.loadErr) { | |
1549 return | |
1550 } | |
1551 if tc.want == nil { | |
1552 return | |
1553 } | |
1554 | |
1555 if gotT, ok := got.(*T); ok { | |
1556 // Round tripping a time.Time can result
in a different time.Location: Local instead of UTC. | |
1557 // We therefore test equality explicitly
, instead of relying on reflect.DeepEqual. | |
1558 So(gotT.T.Equal(tc.want.(*T).T), ShouldB
eTrue) | |
1559 } else { | |
1560 So(got, ShouldResemble, tc.want) | |
1561 } | |
1562 }) | |
1563 } | |
1564 }) | |
1565 } | |
1566 | |
1567 func TestSpecial(t *testing.T) { | |
1568 t.Parallel() | |
1569 | |
1570 Convey("Test special fields", t, func() { | |
1571 Convey("Can retrieve from struct", func() { | |
1572 o := &N0{ID: 100} | |
1573 pls := GetPLS(o) | |
1574 val, err := pls.GetMeta("id") | |
1575 So(err, ShouldBeNil) | |
1576 So(val, ShouldEqual, 100) | |
1577 | |
1578 val, err = pls.GetMeta("kind") | |
1579 So(err, ShouldBeNil) | |
1580 So(val, ShouldEqual, "whatnow") | |
1581 }) | |
1582 | |
1583 Convey("Getting something not there is an error", func() { | |
1584 o := &N0{ID: 100} | |
1585 pls := GetPLS(o) | |
1586 _, err := pls.GetMeta("wat") | |
1587 So(err, ShouldEqual, gae.ErrDSMetaFieldUnset) | |
1588 }) | |
1589 | |
1590 Convey("getting/setting from a bad struct is an error", func() { | |
1591 o := &Recursive{} | |
1592 pls := GetPLS(o) | |
1593 _, err := pls.GetMeta("wat") | |
1594 So(err, ShouldNotBeNil) | |
1595 | |
1596 err = pls.SetMeta("wat", 100) | |
1597 So(err, ShouldNotBeNil) | |
1598 }) | |
1599 | |
1600 Convey("can assign values to exported special fields", func() { | |
1601 o := &N0{ID: 100} | |
1602 pls := GetPLS(o) | |
1603 err := pls.SetMeta("id", int64(200)) | |
1604 So(err, ShouldBeNil) | |
1605 So(o.ID, ShouldEqual, 200) | |
1606 | |
1607 }) | |
1608 | |
1609 Convey("assigning to unsassiagnable fields is a simple error", f
unc() { | |
1610 o := &N0{ID: 100} | |
1611 pls := GetPLS(o) | |
1612 err := pls.SetMeta("kind", "hi") | |
1613 So(err.Error(), ShouldContainSubstring, "unexported fiel
d") | |
1614 | |
1615 err = pls.SetMeta("noob", "hi") | |
1616 So(err, ShouldEqual, gae.ErrDSMetaFieldUnset) | |
1617 }) | |
1618 }) | |
1619 | |
1620 Convey("StructPLS Miscellaneous", t, func() { | |
1621 Convey("multiple overlapping fields is an error", func() { | |
1622 o := &BadSpecial{} | |
1623 pls := GetPLS(o) | |
1624 err := pls.Load(nil) | |
1625 So(err, ShouldErrLike, "multiple times") | |
1626 e := pls.Problem() | |
1627 _, err = pls.Save(true) | |
1628 So(err, ShouldEqual, e) | |
1629 err = pls.Load(nil) | |
1630 So(err, ShouldEqual, e) | |
1631 }) | |
1632 | |
1633 Convey("empty property names are invalid", func() { | |
1634 So(validPropertyName(""), ShouldBeFalse) | |
1635 }) | |
1636 | |
1637 Convey("attempting to get a PLS for a non *struct is an error",
func() { | |
1638 pls := GetPLS((*[]string)(nil)) | |
1639 So(pls.Problem(), ShouldEqual, gae.ErrDSInvalidEntityTyp
e) | |
1640 }) | |
1641 | |
1642 Convey("convertible meta default types", func() { | |
1643 type OKDefaults struct { | |
1644 When string `gae:"$when,tomorrow"` | |
1645 Amount int64 `gae:"$amt,100"` | |
1646 } | |
1647 pls := GetPLS(&OKDefaults{}) | |
1648 So(pls.Problem(), ShouldBeNil) | |
1649 | |
1650 v, err := pls.GetMeta("when") | |
1651 So(err, ShouldBeNil) | |
1652 So(v, ShouldEqual, "tomorrow") | |
1653 | |
1654 v, err = pls.GetMeta("amt") | |
1655 So(err, ShouldBeNil) | |
1656 So(v, ShouldEqual, int64(100)) | |
1657 }) | |
1658 | |
1659 Convey("meta fields can be saved", func() { | |
1660 type OKDefaults struct { | |
1661 When string `gae:"$when,tomorrow"` | |
1662 Amount int64 `gae:"$amt,100"` | |
1663 } | |
1664 pls := GetPLS(&OKDefaults{}) | |
1665 pm, err := pls.Save(true) | |
1666 So(err, ShouldBeNil) | |
1667 So(pm, ShouldResemble, gae.DSPropertyMap{ | |
1668 "$when": {gae.MkDSPropertyNI("tomorrow")}, | |
1669 "$amt": {gae.MkDSPropertyNI(100)}, | |
1670 }) | |
1671 | |
1672 v, err := pm.GetMeta("when") | |
1673 So(err, ShouldBeNil) | |
1674 So(v, ShouldEqual, "tomorrow") | |
1675 | |
1676 v, err = pm.GetMeta("amt") | |
1677 So(err, ShouldBeNil) | |
1678 So(v, ShouldEqual, int64(100)) | |
1679 }) | |
1680 | |
1681 Convey("default are optional", func() { | |
1682 type OverrideDefault struct { | |
1683 Val int64 `gae:"$val"` | |
1684 } | |
1685 o := &OverrideDefault{} | |
1686 pls := GetPLS(o) | |
1687 | |
1688 v, err := pls.GetMeta("val") | |
1689 So(err, ShouldBeNil) | |
1690 So(v, ShouldEqual, int64(0)) | |
1691 }) | |
1692 | |
1693 Convey("overridable defaults", func() { | |
1694 type OverrideDefault struct { | |
1695 Val int64 `gae:"$val,100"` | |
1696 } | |
1697 o := &OverrideDefault{} | |
1698 pls := GetPLS(o) | |
1699 | |
1700 v, err := pls.GetMeta("val") | |
1701 So(err, ShouldBeNil) | |
1702 So(v, ShouldEqual, int64(100)) | |
1703 | |
1704 o.Val = 10 | |
1705 v, err = pls.GetMeta("val") | |
1706 So(err, ShouldBeNil) | |
1707 So(v, ShouldEqual, int64(10)) | |
1708 }) | |
1709 | |
1710 Convey("Bad default meta type", func() { | |
1711 type BadDefault struct { | |
1712 Val time.Time `gae:"$meta,tomorrow"` | |
1713 } | |
1714 pls := GetPLS(&BadDefault{}) | |
1715 So(pls.Problem().Error(), ShouldContainSubstring, "bad t
ype") | |
1716 }) | |
1717 }) | |
1718 } | |
OLD | NEW |