| 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")
|
| - })
|
| - })
|
| -}
|
|
|