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

Unified Diff: service/rawdatastore/datastore_test.go

Issue 1259593005: Add 'user friendly' datastore API. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: 100% coverage of new code Created 5 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 side-by-side diff with in-line comments
Download patch
Index: service/rawdatastore/datastore_test.go
diff --git a/service/rawdatastore/datastore_test.go b/service/rawdatastore/datastore_test.go
deleted file mode 100644
index a96048c68914494bb80bbb0a95809abe7a72984e..0000000000000000000000000000000000000000
--- a/service/rawdatastore/datastore_test.go
+++ /dev/null
@@ -1,1747 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// adapted from github.com/golang/appengine/datastore
-
-package rawdatastore
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "math"
- "reflect"
- "strconv"
- "strings"
- "testing"
- "time"
-
- "github.com/luci/gae/service/blobstore"
- . "github.com/smartystreets/goconvey/convey"
-)
-
-var (
- mp = MkProperty
- mpNI = MkPropertyNI
-)
-
-const testAppID = "testApp"
-
-type (
- myBlob []byte
- myByte byte
- myString string
-)
-
-func makeMyByteSlice(n int) []myByte {
- b := make([]myByte, n)
- for i := range b {
- b[i] = myByte(i)
- }
- return b
-}
-
-func makeInt8Slice(n int) []int8 {
- b := make([]int8, n)
- for i := range b {
- b[i] = int8(i)
- }
- return b
-}
-
-func makeUint8Slice(n int) []uint8 {
- b := make([]uint8, n)
- for i := range b {
- b[i] = uint8(i)
- }
- return b
-}
-
-var (
- testKey0 = mkKey("aid", "", "kind", "name0")
- testKey1a = mkKey("aid", "", "kind", "name1")
- testKey1b = mkKey("aid", "", "kind", "name1")
- testKey2a = mkKey("aid", "", "kind", "name0", "kind", "name2")
- testKey2b = mkKey("aid", "", "kind", "name0", "kind", "name2")
- testGeoPt0 = GeoPoint{Lat: 1.2, Lng: 3.4}
- testGeoPt1 = GeoPoint{Lat: 5, Lng: 10}
- testBadGeoPt = GeoPoint{Lat: 1000, Lng: 34}
-)
-
-type B0 struct {
- B []byte
-}
-
-type B1 struct {
- B []int8
-}
-
-type B2 struct {
- B myBlob
-}
-
-type B3 struct {
- B []myByte
-}
-
-type B4 struct {
- B [][]byte
-}
-
-type B5 struct {
- B ByteString
-}
-
-type C0 struct {
- I int
- C chan int
-}
-
-type C1 struct {
- I int
- C *chan int
-}
-
-type C2 struct {
- I int
- C []chan int
-}
-
-type C3 struct {
- C string
-}
-
-type E struct{}
-
-type G0 struct {
- G GeoPoint
-}
-
-type G1 struct {
- G []GeoPoint
-}
-
-type K0 struct {
- K Key
-}
-
-type K1 struct {
- K []Key
-}
-
-type N0 struct {
- X0
- ID int64 `gae:"$id"`
- _kind string `gae:"$kind,whatnow"`
- Nonymous X0
- Ignore string `gae:"-"`
- Other string
-}
-
-type N1 struct {
- X0
- Nonymous []X0
- Ignore string `gae:"-"`
- Other string
-}
-
-type N2 struct {
- N1 `gae:"red"`
- Green N1 `gae:"green"`
- Blue N1
- White N1 `gae:"-"`
-}
-
-type O0 struct {
- I int64
-}
-
-type O1 struct {
- I int32
-}
-
-type U0 struct {
- U uint
-}
-
-type U1 struct {
- U string
-}
-
-type T struct {
- T time.Time
-}
-
-type X0 struct {
- S string
- I int
- i int
-}
-
-type X1 struct {
- S myString
- I int32
- J int64
-}
-
-type X2 struct {
- Z string
- i int
-}
-
-type X3 struct {
- S bool
- I int
-}
-
-type Y0 struct {
- B bool
- F []float64
- G []float64
-}
-
-type Y1 struct {
- B bool
- F float64
-}
-
-type Y2 struct {
- B bool
- F []int64
-}
-
-type Y3 struct {
- B bool
- F int64
-}
-
-type Tagged struct {
- A int `gae:"a,noindex"`
- B []int `gae:"b1"`
- C int `gae:",noindex"`
- D int `gae:""`
- E int
- I int `gae:"-"`
- J int `gae:",noindex" json:"j"`
-
- Y0 `gae:"-"`
- Z chan int `gae:"-,"`
-}
-
-type InvalidTagged1 struct {
- I int `gae:"\t"`
-}
-
-type InvalidTagged2 struct {
- I int
- J int `gae:"I"`
-}
-
-type InvalidTagged3 struct {
- I int `gae:"a\t"`
-}
-
-type InvalidTagged4 struct {
- I int `gae:"a."`
-}
-
-type InvalidTaggedSub struct {
- I int
-}
-
-type InvalidTagged5 struct {
- I int `gae:"V.I"`
- V []InvalidTaggedSub
-}
-
-type Inner1 struct {
- W int32
- X string
-}
-
-type Inner2 struct {
- Y float64
-}
-
-type Inner3 struct {
- Z bool
-}
-
-type Outer struct {
- A int16
- I []Inner1
- J Inner2
- Inner3
-}
-
-type OuterEquivalent struct {
- A int16
- IDotW []int32 `gae:"I.W"`
- IDotX []string `gae:"I.X"`
- JDotY float64 `gae:"J.Y"`
- Z bool
-}
-
-type Dotted struct {
- A DottedA `gae:"A0.A1.A2"`
-}
-
-type DottedA struct {
- B DottedB `gae:"B3"`
-}
-
-type DottedB struct {
- C int `gae:"C4.C5"`
-}
-
-type SliceOfSlices struct {
- I int
- S []struct {
- J int
- F []float64
- }
-}
-
-type Recursive struct {
- I int
- R []Recursive
-}
-
-type MutuallyRecursive0 struct {
- I int
- R []MutuallyRecursive1
-}
-
-type MutuallyRecursive1 struct {
- I int
- R []MutuallyRecursive0
-}
-
-type ExoticTypes struct {
- BS blobstore.Key
- DSBS ByteString
-}
-
-type Underspecified struct {
- Iface PropertyConverter
-}
-
-type MismatchTypes struct {
- S string
- B bool
- F float32
- K Key
- T time.Time
- G GeoPoint
- IS []int
-}
-
-type BadSpecial struct {
- ID int64 `gae:"$id"`
- id string `gae:"$id"`
-}
-
-type Doubler struct {
- S string
- I int64
- B bool
-}
-
-func (d *Doubler) Load(props PropertyMap) error {
- return GetPLS(d).Load(props)
-}
-
-func (d *Doubler) Save(withMeta bool) (PropertyMap, error) {
- pls := GetPLS(d)
- propMap, err := pls.Save(withMeta)
- if err != nil {
- return nil, err
- }
-
- // Edit that map and send it on.
- for _, props := range propMap {
- for i := range props {
- switch v := props[i].Value().(type) {
- case string:
- // + means string concatenation.
- props[i].SetValue(v+v, props[i].IndexSetting())
- case int64:
- // + means integer addition.
- props[i].SetValue(v+v, props[i].IndexSetting())
- }
- }
- }
- return propMap, nil
-}
-
-func (d *Doubler) GetMeta(string) (interface{}, error) { return nil, ErrMetaFieldUnset }
-func (d *Doubler) SetMeta(string, interface{}) error { return ErrMetaFieldUnset }
-func (d *Doubler) Problem() error { return nil }
-
-var _ PropertyLoadSaver = (*Doubler)(nil)
-
-type Deriver struct {
- S, Derived, Ignored string
-}
-
-func (d *Deriver) Load(props PropertyMap) error {
- for name, p := range props {
- if name != "S" {
- continue
- }
- d.S = p[0].Value().(string)
- d.Derived = "derived+" + d.S
- }
- return nil
-}
-
-func (d *Deriver) Save(withMeta bool) (PropertyMap, error) {
- return map[string][]Property{
- "S": {mp(d.S)},
- }, nil
-}
-
-func (d *Deriver) GetMeta(string) (interface{}, error) { return nil, ErrMetaFieldUnset }
-func (d *Deriver) SetMeta(string, interface{}) error { return ErrMetaFieldUnset }
-func (d *Deriver) Problem() error { return nil }
-
-var _ PropertyLoadSaver = (*Deriver)(nil)
-
-type BK struct {
- Key blobstore.Key
-}
-
-type Convertable []int64
-
-var _ PropertyConverter = (*Convertable)(nil)
-
-func (c *Convertable) ToProperty() (ret Property, err error) {
- buf := make([]string, len(*c))
- for i, v := range *c {
- buf[i] = strconv.FormatInt(v, 10)
- }
- err = ret.SetValue(strings.Join(buf, ","), NoIndex)
- return
-}
-
-func (c *Convertable) FromProperty(pv Property) error {
- if sval, ok := pv.Value().(string); ok {
- for _, t := range strings.Split(sval, ",") {
- ival, err := strconv.ParseInt(t, 10, 64)
- if err != nil {
- return err
- }
- *c = append(*c, ival)
- }
- return nil
- }
- return fmt.Errorf("nope")
-}
-
-type Impossible struct {
- Nested []ImpossibleInner
-}
-
-type ImpossibleInner struct {
- Ints Convertable `gae:"wot"`
-}
-
-type Convertable2 struct {
- Data string
- Exploded []string
-}
-
-func (c *Convertable2) ToProperty() (ret Property, err error) {
- err = ret.SetValue(c.Data, ShouldIndex)
- return
-}
-
-func (c *Convertable2) FromProperty(pv Property) error {
- if sval, ok := pv.Value().(string); ok {
- c.Data = sval
- c.Exploded = []string{"turn", "down", "for", "what"}
- return nil
- }
- return fmt.Errorf("nope")
-}
-
-type Impossible2 struct {
- Nested []ImpossibleInner2
-}
-
-type ImpossibleInner2 struct {
- Thingy Convertable2 `gae:"nerb"`
-}
-
-type JSONKVProp map[string]interface{}
-
-var _ PropertyConverter = (*JSONKVProp)(nil)
-
-func (j *JSONKVProp) ToProperty() (ret Property, err error) {
- data, err := json.Marshal(map[string]interface{}(*j))
- if err != nil {
- return
- }
- err = ret.SetValue(data, NoIndex)
- return
-}
-
-func (j *JSONKVProp) FromProperty(pv Property) error {
- if bval, ok := pv.Value().([]byte); ok {
- dec := json.NewDecoder(bytes.NewBuffer(bval))
- dec.UseNumber()
- return dec.Decode((*map[string]interface{})(j))
- }
- return fmt.Errorf("nope")
-}
-
-type Impossible3 struct {
- KMap JSONKVProp `gae:"kewelmap"`
-}
-
-type Complex complex128
-
-var _ PropertyConverter = (*Complex)(nil)
-
-func (c *Complex) ToProperty() (ret Property, err error) {
- // cheat hardkore and usurp GeoPoint so datastore will index these suckers
- // (note that this won't REALLY work, since GeoPoints are limited to a very
- // limited range of values, but it's nice to pretend ;)). You'd probably
- // really end up with a packed binary representation.
- err = ret.SetValue(GeoPoint{Lat: real(*c), Lng: imag(*c)}, ShouldIndex)
- return
-}
-
-func (c *Complex) FromProperty(p Property) error {
- if gval, ok := p.Value().(GeoPoint); ok {
- *c = Complex(complex(gval.Lat, gval.Lng))
- return nil
- }
- return fmt.Errorf("nope")
-}
-
-type Impossible4 struct {
- Values []Complex
-}
-
-type DerivedKey struct {
- K *GenericKey
-}
-
-type IfaceKey struct {
- K Key
-}
-
-type testCase struct {
- desc string
- src interface{}
- want interface{}
- plsErr string
- saveErr string
- actualNoIndex bool
- plsLoadErr string
- loadErr string
-}
-
-var testCases = []testCase{
- {
- desc: "chan save fails",
- src: &C0{I: -1},
- plsErr: `field "C" has invalid type: chan int`,
- },
- {
- desc: "*chan save fails",
- src: &C1{I: -1},
- plsErr: `field "C" has invalid type: *chan int`,
- },
- {
- desc: "[]chan save fails",
- src: &C2{I: -1, C: make([]chan int, 8)},
- plsErr: `field "C" has invalid type: []chan int`,
- },
- {
- desc: "chan load fails",
- src: &C3{C: "not a chan"},
- want: &C0{},
- plsLoadErr: `field "C" has invalid type: chan int`,
- },
- {
- desc: "*chan load fails",
- src: &C3{C: "not a *chan"},
- want: &C1{},
- plsLoadErr: `field "C" has invalid type: *chan int`,
- },
- {
- desc: "[]chan load fails",
- src: &C3{C: "not a []chan"},
- want: &C2{},
- plsLoadErr: `field "C" has invalid type: []chan int`,
- },
- {
- desc: "empty struct",
- src: &E{},
- want: &E{},
- },
- {
- desc: "geopoint",
- src: &G0{G: testGeoPt0},
- want: &G0{G: testGeoPt0},
- },
- {
- desc: "geopoint invalid",
- src: &G0{G: testBadGeoPt},
- saveErr: "invalid GeoPoint value",
- },
- {
- desc: "geopoint as props",
- src: &G0{G: testGeoPt0},
- want: PropertyMap{
- "G": {mp(testGeoPt0)},
- },
- },
- {
- desc: "geopoint slice",
- src: &G1{G: []GeoPoint{testGeoPt0, testGeoPt1}},
- want: &G1{G: []GeoPoint{testGeoPt0, testGeoPt1}},
- },
- {
- desc: "key",
- src: &K0{K: testKey1a},
- want: &K0{K: testKey1b},
- },
- {
- desc: "key with parent",
- src: &K0{K: testKey2a},
- want: &K0{K: testKey2b},
- },
- {
- desc: "nil key",
- src: &K0{},
- want: &K0{},
- },
- {
- desc: "all nil keys in slice",
- src: &K1{[]Key{nil, nil}},
- want: &K1{[]Key{nil, nil}},
- },
- {
- desc: "some nil keys in slice",
- src: &K1{[]Key{testKey1a, nil, testKey2a}},
- want: &K1{[]Key{testKey1b, nil, testKey2b}},
- },
- {
- desc: "overflow",
- src: &O0{I: 1 << 48},
- want: &O1{},
- loadErr: "overflow",
- },
- {
- desc: "time",
- src: &T{T: time.Unix(1e9, 0).UTC()},
- want: &T{T: time.Unix(1e9, 0).UTC()},
- },
- {
- desc: "time as props",
- src: &T{T: time.Unix(1e9, 0).UTC()},
- want: PropertyMap{
- "T": {mp(time.Unix(1e9, 0).UTC())},
- },
- },
- {
- desc: "uint save",
- src: &U0{U: 1},
- plsErr: `field "U" has invalid type: uint`,
- },
- {
- desc: "uint load",
- src: &U1{U: "not a uint"},
- want: &U0{},
- plsLoadErr: `field "U" has invalid type: uint`,
- },
- {
- desc: "zero",
- src: &X0{},
- want: &X0{},
- },
- {
- desc: "basic",
- src: &X0{S: "one", I: 2, i: 3},
- want: &X0{S: "one", I: 2},
- },
- {
- desc: "save string/int load myString/int32",
- src: &X0{S: "one", I: 2, i: 3},
- want: &X1{S: "one", I: 2},
- },
- {
- desc: "missing fields",
- src: &X0{S: "one", I: 2, i: 3},
- want: &X2{},
- loadErr: "no such struct field",
- },
- {
- desc: "save string load bool",
- src: &X0{S: "one", I: 2, i: 3},
- want: &X3{I: 2},
- loadErr: "type mismatch",
- },
- {
- desc: "basic slice",
- src: &Y0{B: true, F: []float64{7, 8, 9}},
- want: &Y0{B: true, F: []float64{7, 8, 9}},
- },
- {
- desc: "save []float64 load float64",
- src: &Y0{B: true, F: []float64{7, 8, 9}},
- want: &Y1{B: true},
- loadErr: "requires a slice",
- },
- {
- desc: "save single []int64 load int64",
- src: &Y2{B: true, F: []int64{7}},
- want: &Y3{B: true, F: 7},
- },
- {
- desc: "save int64 load single []int64",
- src: &Y3{B: true, F: 7},
- want: &Y2{B: true, F: []int64{7}},
- },
- {
- desc: "use convertable slice",
- src: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Convertable{2, 4, 6}}}},
- want: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Convertable{2, 4, 6}}}},
- },
- {
- desc: "use convertable slice (to map)",
- src: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Convertable{2, 4, 6}}}},
- want: PropertyMap{
- "Nested.wot": {mpNI("1,5,9"), mpNI("2,4,6")},
- },
- },
- {
- desc: "convertable slice (bad load)",
- src: PropertyMap{"Nested.wot": {mpNI([]byte("ohai"))}},
- want: &Impossible{[]ImpossibleInner{{}}},
- loadErr: "nope",
- },
- {
- desc: "use convertable struct",
- src: &Impossible2{
- []ImpossibleInner2{
- {Convertable2{"nerb", nil}},
- },
- },
- want: &Impossible2{
- []ImpossibleInner2{
- {Convertable2{"nerb", []string{"turn", "down", "for", "what"}}},
- },
- },
- },
- {
- desc: "convertable json KVMap",
- src: &Impossible3{
- JSONKVProp{
- "epic": "success",
- "no_way!": []interface{}{true, "story"},
- "what": []interface{}{"is", "really", 100},
- },
- },
- want: &Impossible3{
- JSONKVProp{
- "epic": "success",
- "no_way!": []interface{}{true, "story"},
- "what": []interface{}{"is", "really", json.Number("100")},
- },
- },
- },
- {
- desc: "convertable json KVMap (to map)",
- src: &Impossible3{
- JSONKVProp{
- "epic": "success",
- "no_way!": []interface{}{true, "story"},
- "what": []interface{}{"is", "really", 100},
- },
- },
- want: PropertyMap{
- "kewelmap": {
- mpNI([]byte(
- `{"epic":"success","no_way!":[true,"story"],"what":["is","really",100]}`))},
- },
- },
- {
- desc: "convertable complex slice",
- src: &Impossible4{
- []Complex{complex(1, 2), complex(3, 4)},
- },
- want: &Impossible4{
- []Complex{complex(1, 2), complex(3, 4)},
- },
- },
- {
- desc: "convertable complex slice (to map)",
- src: &Impossible4{
- []Complex{complex(1, 2), complex(3, 4)},
- },
- want: PropertyMap{
- "Values": {
- mp(GeoPoint{Lat: 1, Lng: 2}), mp(GeoPoint{Lat: 3, Lng: 4})},
- },
- },
- {
- desc: "convertable complex slice (bad load)",
- src: PropertyMap{"Values": {mp("hello")}},
- want: &Impossible4{[]Complex(nil)},
- loadErr: "nope",
- },
- {
- desc: "allow concrete Key implementors (save)",
- src: &DerivedKey{testKey2a.(*GenericKey)},
- want: &IfaceKey{testKey2b},
- },
- {
- desc: "allow concrete Key implementors (load)",
- src: &IfaceKey{testKey2b},
- want: &DerivedKey{testKey2a.(*GenericKey)},
- },
- {
- desc: "save []float64 load []int64",
- src: &Y0{B: true, F: []float64{7, 8, 9}},
- want: &Y2{B: true},
- loadErr: "type mismatch",
- },
- {
- desc: "single slice is too long",
- src: &Y0{F: make([]float64, maxIndexedProperties+1)},
- want: &Y0{},
- saveErr: "gae: too many indexed properties",
- },
- {
- desc: "two slices are too long",
- src: &Y0{F: make([]float64, maxIndexedProperties), G: make([]float64, maxIndexedProperties)},
- want: &Y0{},
- saveErr: "gae: too many indexed properties",
- },
- {
- desc: "one slice and one scalar are too long",
- src: &Y0{F: make([]float64, maxIndexedProperties), B: true},
- want: &Y0{},
- saveErr: "gae: too many indexed properties",
- },
- {
- desc: "long blob",
- src: &B0{B: makeUint8Slice(maxIndexedProperties + 1)},
- want: &B0{B: makeUint8Slice(maxIndexedProperties + 1)},
- },
- {
- desc: "long []int8 is too long",
- src: &B1{B: makeInt8Slice(maxIndexedProperties + 1)},
- want: &B1{},
- saveErr: "gae: too many indexed properties",
- },
- {
- desc: "short []int8",
- src: &B1{B: makeInt8Slice(3)},
- want: &B1{B: makeInt8Slice(3)},
- },
- {
- desc: "long myBlob",
- src: &B2{B: makeUint8Slice(maxIndexedProperties + 1)},
- want: &B2{B: makeUint8Slice(maxIndexedProperties + 1)},
- },
- {
- desc: "short myBlob",
- src: &B2{B: makeUint8Slice(3)},
- want: &B2{B: makeUint8Slice(3)},
- },
- {
- desc: "long []myByte",
- src: &B3{B: makeMyByteSlice(maxIndexedProperties + 1)},
- want: &B3{B: makeMyByteSlice(maxIndexedProperties + 1)},
- },
- {
- desc: "short []myByte",
- src: &B3{B: makeMyByteSlice(3)},
- want: &B3{B: makeMyByteSlice(3)},
- },
- {
- desc: "slice of blobs",
- src: &B4{B: [][]byte{
- makeUint8Slice(3),
- makeUint8Slice(4),
- makeUint8Slice(5),
- }},
- want: &B4{B: [][]byte{
- makeUint8Slice(3),
- makeUint8Slice(4),
- makeUint8Slice(5),
- }},
- },
- {
- desc: "short ByteString",
- src: &B5{B: ByteString(makeUint8Slice(3))},
- want: &B5{B: ByteString(makeUint8Slice(3))},
- },
- {
- desc: "short ByteString as props",
- src: &B5{B: ByteString(makeUint8Slice(3))},
- want: PropertyMap{
- "B": {mp(ByteString(makeUint8Slice(3)))},
- },
- },
- {
- desc: "[]byte must be noindex",
- src: PropertyMap{
- "B": {mp(makeUint8Slice(3))},
- },
- actualNoIndex: true,
- },
- {
- desc: "save tagged load props",
- src: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6, J: 7},
- want: PropertyMap{
- // A and B are renamed to a and b; A and C are noindex, I is ignored.
- // Indexed properties are loaded before raw properties. Thus, the
- // result is: b, b, b, D, E, a, c.
- "b1": {
- mp(21),
- mp(22),
- mp(23),
- },
- "D": {mp(4)},
- "E": {mp(5)},
- "a": {mpNI(1)},
- "C": {mpNI(3)},
- "J": {mpNI(7)},
- },
- },
- {
- desc: "save tagged load tagged",
- src: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6, J: 7},
- want: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, J: 7},
- },
- {
- desc: "save props load tagged",
- src: PropertyMap{
- "A": {mpNI(11)},
- "a": {mpNI(12)},
- },
- want: &Tagged{A: 12},
- loadErr: `cannot load field "A"`,
- },
- {
- desc: "invalid tagged1",
- src: &InvalidTagged1{I: 1},
- plsErr: `struct tag has invalid property name: "\t"`,
- },
- {
- desc: "invalid tagged2",
- src: &InvalidTagged2{I: 1, J: 2},
- want: &InvalidTagged2{},
- plsErr: `struct tag has repeated property name: "I"`,
- },
- {
- desc: "invalid tagged3",
- src: &InvalidTagged3{I: 1},
- plsErr: `struct tag has invalid property name: "a\t"`,
- },
- {
- desc: "invalid tagged4",
- src: &InvalidTagged4{I: 1},
- plsErr: `struct tag has invalid property name: "a."`,
- },
- {
- desc: "invalid tagged5",
- src: &InvalidTagged5{I: 19, V: []InvalidTaggedSub{{1}}},
- plsErr: `struct tag has repeated property name: "V.I"`,
- },
- {
- desc: "doubler",
- src: &Doubler{S: "s", I: 1, B: true},
- want: &Doubler{S: "ss", I: 2, B: true},
- },
- {
- desc: "save struct load props",
- src: &X0{S: "s", I: 1},
- want: PropertyMap{
- "S": {mp("s")},
- "I": {mp(1)},
- },
- },
- {
- desc: "save props load struct",
- src: PropertyMap{
- "S": {mp("s")},
- "I": {mp(1)},
- },
- want: &X0{S: "s", I: 1},
- },
- {
- desc: "nil-value props",
- src: PropertyMap{
- "I": {mp(nil)},
- "B": {mp(nil)},
- "S": {mp(nil)},
- "F": {mp(nil)},
- "K": {mp(nil)},
- "T": {mp(nil)},
- "J": {
- mp(nil),
- mp(7),
- mp(nil),
- },
- },
- want: &struct {
- I int64
- B bool
- S string
- F float64
- K Key
- T time.Time
- J []int64
- }{
- J: []int64{0, 7, 0},
- },
- },
- {
- desc: "save outer load props",
- src: &Outer{
- A: 1,
- I: []Inner1{
- {10, "ten"},
- {20, "twenty"},
- {30, "thirty"},
- },
- J: Inner2{
- Y: 3.14,
- },
- Inner3: Inner3{
- Z: true,
- },
- },
- want: PropertyMap{
- "A": {mp(1)},
- "I.W": {
- mp(10),
- mp(20),
- mp(30),
- },
- "I.X": {
- mp("ten"),
- mp("twenty"),
- mp("thirty"),
- },
- "J.Y": {mp(3.14)},
- "Z": {mp(true)},
- },
- },
- {
- desc: "save props load outer-equivalent",
- src: PropertyMap{
- "A": {mp(1)},
- "I.W": {
- mp(10),
- mp(20),
- mp(30),
- },
- "I.X": {
- mp("ten"),
- mp("twenty"),
- mp("thirty"),
- },
- "J.Y": {mp(3.14)},
- "Z": {mp(true)},
- },
- want: &OuterEquivalent{
- A: 1,
- IDotW: []int32{10, 20, 30},
- IDotX: []string{"ten", "twenty", "thirty"},
- JDotY: 3.14,
- Z: true,
- },
- },
- {
- desc: "save outer-equivalent load outer",
- src: &OuterEquivalent{
- A: 1,
- IDotW: []int32{10, 20, 30},
- IDotX: []string{"ten", "twenty", "thirty"},
- JDotY: 3.14,
- Z: true,
- },
- want: &Outer{
- A: 1,
- I: []Inner1{
- {10, "ten"},
- {20, "twenty"},
- {30, "thirty"},
- },
- J: Inner2{
- Y: 3.14,
- },
- Inner3: Inner3{
- Z: true,
- },
- },
- },
- {
- desc: "dotted names save",
- src: &Dotted{A: DottedA{B: DottedB{C: 88}}},
- want: PropertyMap{
- "A0.A1.A2.B3.C4.C5": {mp(88)},
- },
- },
- {
- desc: "dotted names load",
- src: PropertyMap{
- "A0.A1.A2.B3.C4.C5": {mp(99)},
- },
- want: &Dotted{A: DottedA{B: DottedB{C: 99}}},
- },
- {
- desc: "save struct load deriver",
- src: &X0{S: "s", I: 1},
- want: &Deriver{S: "s", Derived: "derived+s"},
- },
- {
- desc: "save deriver load struct",
- src: &Deriver{S: "s", Derived: "derived+s", Ignored: "ignored"},
- want: &X0{S: "s"},
- },
- // Regression: CL 25062824 broke handling of appengine.BlobKey fields.
- {
- desc: "appengine.BlobKey",
- src: &BK{Key: "blah"},
- want: &BK{Key: "blah"},
- },
- {
- desc: "zero time.Time",
- src: &T{T: time.Time{}},
- want: &T{T: time.Time{}},
- },
- {
- desc: "time.Time near Unix zero time",
- src: &T{T: time.Unix(0, 4e3).UTC()},
- want: &T{T: time.Unix(0, 4e3).UTC()},
- },
- {
- desc: "time.Time, far in the future",
- src: &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)},
- want: &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)},
- },
- {
- desc: "time.Time, very far in the past",
- src: &T{T: time.Date(-300000, 1, 1, 0, 0, 0, 0, time.UTC)},
- want: &T{},
- saveErr: "time value out of range",
- },
- {
- desc: "time.Time, very far in the future",
- src: &T{T: time.Date(294248, 1, 1, 0, 0, 0, 0, time.UTC)},
- want: &T{},
- saveErr: "time value out of range",
- },
- {
- desc: "structs",
- src: &N0{
- X0: X0{S: "one", I: 2, i: 3},
- Nonymous: X0{S: "four", I: 5, i: 6},
- Ignore: "ignore",
- Other: "other",
- },
- want: &N0{
- X0: X0{S: "one", I: 2},
- Nonymous: X0{S: "four", I: 5},
- Other: "other",
- },
- },
- {
- desc: "exotic types",
- src: &ExoticTypes{
- BS: "sup",
- DSBS: ByteString("nerds"),
- },
- want: &ExoticTypes{
- BS: "sup",
- DSBS: ByteString("nerds"),
- },
- },
- {
- desc: "underspecified types",
- src: &Underspecified{},
- plsErr: "non-concrete interface",
- },
- {
- desc: "mismatch (string)",
- src: PropertyMap{
- "K": {mp(199)},
- "S": {mp([]byte("cats"))},
- "F": {mp(ByteString("nurbs"))},
- },
- want: &MismatchTypes{},
- loadErr: "type mismatch",
- },
- {
- desc: "mismatch (float)",
- src: PropertyMap{"F": {mp(blobstore.Key("wot"))}},
- want: &MismatchTypes{},
- loadErr: "type mismatch",
- },
- {
- desc: "mismatch (float/overflow)",
- src: PropertyMap{"F": {mp(math.MaxFloat64)}},
- want: &MismatchTypes{},
- loadErr: "overflows",
- },
- {
- desc: "mismatch (key)",
- src: PropertyMap{"K": {mp(false)}},
- want: &MismatchTypes{},
- loadErr: "type mismatch",
- },
- {
- desc: "mismatch (bool)",
- src: PropertyMap{"B": {mp(testKey0)}},
- want: &MismatchTypes{},
- loadErr: "type mismatch",
- },
- {
- desc: "mismatch (time)",
- src: PropertyMap{"T": {mp(GeoPoint{})}},
- want: &MismatchTypes{},
- loadErr: "type mismatch",
- },
- {
- desc: "mismatch (geopoint)",
- src: PropertyMap{"G": {mp(time.Now().UTC())}},
- want: &MismatchTypes{},
- loadErr: "type mismatch",
- },
- {
- desc: "slice of structs",
- src: &N1{
- X0: X0{S: "one", I: 2, i: 3},
- Nonymous: []X0{
- {S: "four", I: 5, i: 6},
- {S: "seven", I: 8, i: 9},
- {S: "ten", I: 11, i: 12},
- {S: "thirteen", I: 14, i: 15},
- },
- Ignore: "ignore",
- Other: "other",
- },
- want: &N1{
- X0: X0{S: "one", I: 2},
- Nonymous: []X0{
- {S: "four", I: 5},
- {S: "seven", I: 8},
- {S: "ten", I: 11},
- {S: "thirteen", I: 14},
- },
- Other: "other",
- },
- },
- {
- desc: "structs with slices of structs",
- src: &N2{
- N1: N1{
- X0: X0{S: "rouge"},
- Nonymous: []X0{
- {S: "rosso0"},
- {S: "rosso1"},
- },
- },
- Green: N1{
- X0: X0{S: "vert"},
- Nonymous: []X0{
- {S: "verde0"},
- {S: "verde1"},
- {S: "verde2"},
- },
- },
- Blue: N1{
- X0: X0{S: "bleu"},
- Nonymous: []X0{
- {S: "blu0"},
- {S: "blu1"},
- {S: "blu2"},
- {S: "blu3"},
- },
- },
- },
- want: &N2{
- N1: N1{
- X0: X0{S: "rouge"},
- Nonymous: []X0{
- {S: "rosso0"},
- {S: "rosso1"},
- },
- },
- Green: N1{
- X0: X0{S: "vert"},
- Nonymous: []X0{
- {S: "verde0"},
- {S: "verde1"},
- {S: "verde2"},
- },
- },
- Blue: N1{
- X0: X0{S: "bleu"},
- Nonymous: []X0{
- {S: "blu0"},
- {S: "blu1"},
- {S: "blu2"},
- {S: "blu3"},
- },
- },
- },
- },
- {
- desc: "save structs load props",
- src: &N2{
- N1: N1{
- X0: X0{S: "rouge"},
- Nonymous: []X0{
- {S: "rosso0"},
- {S: "rosso1"},
- },
- },
- Green: N1{
- X0: X0{S: "vert"},
- Nonymous: []X0{
- {S: "verde0"},
- {S: "verde1"},
- {S: "verde2"},
- },
- },
- Blue: N1{
- X0: X0{S: "bleu"},
- Nonymous: []X0{
- {S: "blu0"},
- {S: "blu1"},
- {S: "blu2"},
- {S: "blu3"},
- },
- },
- },
- want: PropertyMap{
- "red.S": {mp("rouge")},
- "red.I": {mp(0)},
- "red.Nonymous.S": {mp("rosso0"), mp("rosso1")},
- "red.Nonymous.I": {mp(0), mp(0)},
- "red.Other": {mp("")},
- "green.S": {mp("vert")},
- "green.I": {mp(0)},
- "green.Nonymous.S": {mp("verde0"), mp("verde1"), mp("verde2")},
- "green.Nonymous.I": {mp(0), mp(0), mp(0)},
- "green.Other": {mp("")},
- "Blue.S": {mp("bleu")},
- "Blue.I": {mp(0)},
- "Blue.Nonymous.S": {mp("blu0"), mp("blu1"), mp("blu2"), mp("blu3")},
- "Blue.Nonymous.I": {mp(0), mp(0), mp(0), mp(0)},
- "Blue.Other": {mp("")},
- },
- },
- {
- desc: "save props load structs with ragged fields",
- src: PropertyMap{
- "red.S": {mp("rot")},
- "green.Nonymous.I": {mp(10), mp(11), mp(12), mp(13)},
- "Blue.Nonymous.S": {mp("blau0"), mp("blau1"), mp("blau2")},
- "Blue.Nonymous.I": {mp(20), mp(21)},
- },
- want: &N2{
- N1: N1{
- X0: X0{S: "rot"},
- },
- Green: N1{
- Nonymous: []X0{
- {I: 10},
- {I: 11},
- {I: 12},
- {I: 13},
- },
- },
- Blue: N1{
- Nonymous: []X0{
- {S: "blau0", I: 20},
- {S: "blau1", I: 21},
- {S: "blau2"},
- },
- },
- },
- },
- {
- desc: "save structs with noindex tags",
- src: &struct {
- A struct {
- X string `gae:",noindex"`
- Y string
- } `gae:",noindex"`
- B struct {
- X string `gae:",noindex"`
- Y string
- }
- }{},
- want: PropertyMap{
- "B.Y": {mp("")},
- "A.X": {mpNI("")},
- "A.Y": {mpNI("")},
- "B.X": {mpNI("")},
- },
- },
- {
- desc: "embedded struct with name override",
- src: &struct {
- Inner1 `gae:"foo"`
- }{},
- want: PropertyMap{
- "foo.W": {mp(0)},
- "foo.X": {mp("")},
- },
- },
- {
- desc: "slice of slices",
- src: &SliceOfSlices{},
- plsErr: `flattening nested structs leads to a slice of slices: field "S"`,
- },
- {
- desc: "recursive struct",
- src: &Recursive{},
- plsErr: `field "R" is recursively defined`,
- },
- {
- desc: "mutually recursive struct",
- src: &MutuallyRecursive0{},
- plsErr: `field "R" has problem: field "R" is recursively defined`,
- },
- {
- desc: "non-exported struct fields",
- src: &struct {
- i, J int64
- }{i: 1, J: 2},
- want: PropertyMap{
- "J": {mp(2)},
- },
- },
- {
- desc: "json.RawMessage",
- src: &struct {
- J json.RawMessage
- }{
- J: json.RawMessage("rawr"),
- },
- want: PropertyMap{
- "J": {mp([]byte("rawr"))},
- },
- },
- {
- desc: "json.RawMessage to myBlob",
- src: &struct {
- B json.RawMessage
- }{
- B: json.RawMessage("rawr"),
- },
- want: &B2{B: myBlob("rawr")},
- },
-}
-
-// checkErr returns the empty string if either both want and err are zero,
-// or if want is a non-empty substring of err's string representation.
-func checkErr(want string, err error) string {
- if err != nil {
- got := err.Error()
- if want == "" || strings.Index(got, want) == -1 {
- return got
- }
- } else if want != "" {
- return fmt.Sprintf("want error %q", want)
- }
- return ""
-}
-
-func ShouldErrLike(actual interface{}, expected ...interface{}) string {
- e2s := func(o interface{}) (string, bool) {
- switch x := o.(type) {
- case nil:
- return "", true
- case string:
- return x, true
- case error:
- if x != nil {
- return x.Error(), true
- }
- return "", true
- }
- return fmt.Sprintf("unknown argument type %T, expected string, error or nil", actual), false
- }
-
- as, ok := e2s(actual)
- if !ok {
- return as
- }
-
- if len(expected) != 1 {
- return fmt.Sprintf("Assertion requires 1 expected value, got %d", len(expected))
- }
-
- err, ok := e2s(expected[0])
- if !ok {
- return err
- }
- return ShouldContainSubstring(as, err)
-}
-
-func TestRoundTrip(t *testing.T) {
- t.Parallel()
-
- checkErr := func(actual interface{}, expected string) bool {
- So(actual, ShouldErrLike, expected)
- return expected != ""
- }
-
- Convey("Test round-trip", t, func() {
- for _, tc := range testCases {
- tc := tc
- Convey(tc.desc, func() {
- pls, ok := tc.src.(PropertyLoadSaver)
- if !ok {
- pls = GetPLS(tc.src)
- }
- if checkErr(pls.Problem(), tc.plsErr) {
- return
- }
- So(pls, ShouldNotBeNil)
-
- savedProps, err := pls.Save(false)
- if checkErr(err, tc.saveErr) {
- return
- }
- So(savedProps, ShouldNotBeNil)
-
- if tc.actualNoIndex {
- for _, props := range savedProps {
- So(props[0].IndexSetting(), ShouldEqual, NoIndex)
- return
- }
- So(true, ShouldBeFalse) // shouldn't get here
- }
-
- var got interface{}
- if _, ok := tc.want.(PropertyMap); ok {
- pls = PropertyMap{}
- got = pls
- } else {
- got = reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
- if pls, ok = got.(PropertyLoadSaver); !ok {
- pls = GetPLS(got)
- }
- }
-
- if checkErr(pls.Problem(), tc.plsLoadErr) {
- return
- }
- So(pls, ShouldNotBeNil)
-
- err = pls.Load(savedProps)
- if checkErr(err, tc.loadErr) {
- return
- }
- if tc.want == nil {
- return
- }
-
- if gotT, ok := got.(*T); ok {
- // Round tripping a time.Time can result in a different time.Location: Local instead of UTC.
- // We therefore test equality explicitly, instead of relying on reflect.DeepEqual.
- So(gotT.T.Equal(tc.want.(*T).T), ShouldBeTrue)
- } else {
- So(got, ShouldResemble, tc.want)
- }
- })
- }
- })
-}
-
-func TestSpecial(t *testing.T) {
- t.Parallel()
-
- Convey("Test special fields", t, func() {
- Convey("Can retrieve from struct", func() {
- o := &N0{ID: 100}
- pls := GetPLS(o)
- val, err := pls.GetMeta("id")
- So(err, ShouldBeNil)
- So(val, ShouldEqual, 100)
-
- val, err = pls.GetMeta("kind")
- So(err, ShouldBeNil)
- So(val, ShouldEqual, "whatnow")
- })
-
- Convey("Getting something not there is an error", func() {
- o := &N0{ID: 100}
- pls := GetPLS(o)
- _, err := pls.GetMeta("wat")
- So(err, ShouldEqual, ErrMetaFieldUnset)
- })
-
- Convey("getting/setting from a bad struct is an error", func() {
- o := &Recursive{}
- pls := GetPLS(o)
- _, err := pls.GetMeta("wat")
- So(err, ShouldNotBeNil)
-
- err = pls.SetMeta("wat", 100)
- So(err, ShouldNotBeNil)
- })
-
- Convey("can assign values to exported special fields", func() {
- o := &N0{ID: 100}
- pls := GetPLS(o)
- err := pls.SetMeta("id", int64(200))
- So(err, ShouldBeNil)
- So(o.ID, ShouldEqual, 200)
-
- })
-
- Convey("assigning to unsassiagnable fields is a simple error", func() {
- o := &N0{ID: 100}
- pls := GetPLS(o)
- err := pls.SetMeta("kind", "hi")
- So(err.Error(), ShouldContainSubstring, "unexported field")
-
- err = pls.SetMeta("noob", "hi")
- So(err, ShouldEqual, ErrMetaFieldUnset)
- })
- })
-
- Convey("StructPLS Miscellaneous", t, func() {
- Convey("multiple overlapping fields is an error", func() {
- o := &BadSpecial{}
- pls := GetPLS(o)
- err := pls.Load(nil)
- So(err, ShouldErrLike, "multiple times")
- e := pls.Problem()
- _, err = pls.Save(true)
- So(err, ShouldEqual, e)
- err = pls.Load(nil)
- So(err, ShouldEqual, e)
- })
-
- Convey("empty property names are invalid", func() {
- So(validPropertyName(""), ShouldBeFalse)
- })
-
- Convey("attempting to get a PLS for a non *struct is an error", func() {
- pls := GetPLS((*[]string)(nil))
- So(pls.Problem(), ShouldEqual, ErrInvalidEntityType)
- })
-
- Convey("convertible meta default types", func() {
- type OKDefaults struct {
- When string `gae:"$when,tomorrow"`
- Amount int64 `gae:"$amt,100"`
- DoIt Toggle `gae:"$doit,on"`
- }
- okd := &OKDefaults{}
- pls := GetPLS(okd)
- So(pls.Problem(), ShouldBeNil)
-
- v, err := pls.GetMeta("when")
- So(err, ShouldBeNil)
- So(v, ShouldEqual, "tomorrow")
-
- v, err = pls.GetMeta("amt")
- So(err, ShouldBeNil)
- So(v, ShouldEqual, int64(100))
-
- So(okd.DoIt, ShouldEqual, Auto)
- v, err = pls.GetMeta("doit")
- So(err, ShouldBeNil)
- So(v, ShouldBeTrue)
-
- err = pls.SetMeta("doit", false)
- So(err, ShouldBeNil)
- v, err = pls.GetMeta("doit")
- So(err, ShouldBeNil)
- So(v, ShouldBeFalse)
- So(okd.DoIt, ShouldEqual, Off)
-
- err = pls.SetMeta("doit", true)
- So(err, ShouldBeNil)
- v, err = pls.GetMeta("doit")
- So(err, ShouldBeNil)
- So(v, ShouldBeTrue)
- So(okd.DoIt, ShouldEqual, On)
-
- Convey("Toggle fields REQUIRE a default", func() {
- type BadToggle struct {
- Bad Toggle `gae:"$wut"`
- }
- pls := GetPLS(&BadToggle{})
- So(pls.Problem().Error(), ShouldContainSubstring, "bad/missing default")
- })
- })
-
- Convey("meta fields can be saved", func() {
- type OKDefaults struct {
- When string `gae:"$when,tomorrow"`
- Amount int64 `gae:"$amt,100"`
- }
- pls := GetPLS(&OKDefaults{})
- pm, err := pls.Save(true)
- So(err, ShouldBeNil)
- So(pm, ShouldResemble, PropertyMap{
- "$when": {mpNI("tomorrow")},
- "$amt": {mpNI(100)},
- })
-
- v, err := pm.GetMeta("when")
- So(err, ShouldBeNil)
- So(v, ShouldEqual, "tomorrow")
-
- v, err = pm.GetMeta("amt")
- So(err, ShouldBeNil)
- So(v, ShouldEqual, int64(100))
- })
-
- Convey("default are optional", func() {
- type OverrideDefault struct {
- Val int64 `gae:"$val"`
- }
- o := &OverrideDefault{}
- pls := GetPLS(o)
-
- v, err := pls.GetMeta("val")
- So(err, ShouldBeNil)
- So(v, ShouldEqual, int64(0))
- })
-
- Convey("overridable defaults", func() {
- type OverrideDefault struct {
- Val int64 `gae:"$val,100"`
- }
- o := &OverrideDefault{}
- pls := GetPLS(o)
-
- v, err := pls.GetMeta("val")
- So(err, ShouldBeNil)
- So(v, ShouldEqual, int64(100))
-
- o.Val = 10
- v, err = pls.GetMeta("val")
- So(err, ShouldBeNil)
- So(v, ShouldEqual, int64(10))
- })
-
- Convey("Bad default meta type", func() {
- type BadDefault struct {
- Val time.Time `gae:"$meta,tomorrow"`
- }
- pls := GetPLS(&BadDefault{})
- So(pls.Problem().Error(), ShouldContainSubstring, "bad type")
- })
- })
-}

Powered by Google App Engine
This is Rietveld 408576698