123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604 |
- // Copyright 2011 Google Inc. All Rights Reserved.
- // Use of this source code is governed by the Apache 2.0
- // license that can be found in the LICENSE file.
- package datastore
- import (
- "reflect"
- "testing"
- "time"
- "google.golang.org/appengine"
- )
- func TestValidPropertyName(t *testing.T) {
- testCases := []struct {
- name string
- want bool
- }{
- // Invalid names.
- {"", false},
- {"'", false},
- {".", false},
- {"..", false},
- {".foo", false},
- {"0", false},
- {"00", false},
- {"X.X.4.X.X", false},
- {"\n", false},
- {"\x00", false},
- {"abc\xffz", false},
- {"foo.", false},
- {"foo..", false},
- {"foo..bar", false},
- {"☃", false},
- {`"`, false},
- // Valid names.
- {"AB", true},
- {"Abc", true},
- {"X.X.X.X.X", true},
- {"_", true},
- {"_0", true},
- {"a", true},
- {"a_B", true},
- {"f00", true},
- {"f0o", true},
- {"fo0", true},
- {"foo", true},
- {"foo.bar", true},
- {"foo.bar.baz", true},
- {"世界", true},
- }
- for _, tc := range testCases {
- got := validPropertyName(tc.name)
- if got != tc.want {
- t.Errorf("%q: got %v, want %v", tc.name, got, tc.want)
- }
- }
- }
- func TestStructCodec(t *testing.T) {
- type oStruct struct {
- O int
- }
- type pStruct struct {
- P int
- Q int
- }
- type rStruct struct {
- R int
- S pStruct
- T oStruct
- oStruct
- }
- type uStruct struct {
- U int
- v int
- }
- type vStruct struct {
- V string `datastore:",noindex"`
- }
- oStructCodec := &structCodec{
- byIndex: []structTag{
- {name: "O"},
- },
- byName: map[string]fieldCodec{
- "O": {index: 0},
- },
- complete: true,
- }
- pStructCodec := &structCodec{
- byIndex: []structTag{
- {name: "P"},
- {name: "Q"},
- },
- byName: map[string]fieldCodec{
- "P": {index: 0},
- "Q": {index: 1},
- },
- complete: true,
- }
- rStructCodec := &structCodec{
- byIndex: []structTag{
- {name: "R"},
- {name: "S."},
- {name: "T."},
- {name: ""},
- },
- byName: map[string]fieldCodec{
- "R": {index: 0},
- "S.P": {index: 1, substructCodec: pStructCodec},
- "S.Q": {index: 1, substructCodec: pStructCodec},
- "T.O": {index: 2, substructCodec: oStructCodec},
- "O": {index: 3, substructCodec: oStructCodec},
- },
- complete: true,
- }
- uStructCodec := &structCodec{
- byIndex: []structTag{
- {name: "U"},
- {name: "v"},
- },
- byName: map[string]fieldCodec{
- "U": {index: 0},
- "v": {index: 1},
- },
- complete: true,
- }
- vStructCodec := &structCodec{
- byIndex: []structTag{
- {name: "V", noIndex: true},
- },
- byName: map[string]fieldCodec{
- "V": {index: 0},
- },
- complete: true,
- }
- testCases := []struct {
- desc string
- structValue interface{}
- want *structCodec
- }{
- {
- "oStruct",
- oStruct{},
- oStructCodec,
- },
- {
- "pStruct",
- pStruct{},
- pStructCodec,
- },
- {
- "rStruct",
- rStruct{},
- rStructCodec,
- },
- {
- "uStruct",
- uStruct{},
- uStructCodec,
- },
- {
- "non-basic fields",
- struct {
- B appengine.BlobKey
- K *Key
- T time.Time
- }{},
- &structCodec{
- byIndex: []structTag{
- {name: "B"},
- {name: "K"},
- {name: "T"},
- },
- byName: map[string]fieldCodec{
- "B": {index: 0},
- "K": {index: 1},
- "T": {index: 2},
- },
- complete: true,
- },
- },
- {
- "struct tags with ignored embed",
- struct {
- A int `datastore:"a,noindex"`
- B int `datastore:"b"`
- C int `datastore:",noindex"`
- D int `datastore:""`
- E int
- I int `datastore:"-"`
- J int `datastore:",noindex" json:"j"`
- oStruct `datastore:"-"`
- }{},
- &structCodec{
- byIndex: []structTag{
- {name: "a", noIndex: true},
- {name: "b", noIndex: false},
- {name: "C", noIndex: true},
- {name: "D", noIndex: false},
- {name: "E", noIndex: false},
- {name: "-", noIndex: false},
- {name: "J", noIndex: true},
- {name: "-", noIndex: false},
- },
- byName: map[string]fieldCodec{
- "a": {index: 0},
- "b": {index: 1},
- "C": {index: 2},
- "D": {index: 3},
- "E": {index: 4},
- "J": {index: 6},
- },
- complete: true,
- },
- },
- {
- "unexported fields",
- struct {
- A int
- b int
- C int `datastore:"x"`
- d int `datastore:"Y"`
- }{},
- &structCodec{
- byIndex: []structTag{
- {name: "A"},
- {name: "b"},
- {name: "x"},
- {name: "Y"},
- },
- byName: map[string]fieldCodec{
- "A": {index: 0},
- "b": {index: 1},
- "x": {index: 2},
- "Y": {index: 3},
- },
- complete: true,
- },
- },
- {
- "nested and embedded structs",
- struct {
- A int
- B int
- CC oStruct
- DDD rStruct
- oStruct
- }{},
- &structCodec{
- byIndex: []structTag{
- {name: "A"},
- {name: "B"},
- {name: "CC."},
- {name: "DDD."},
- {name: ""},
- },
- byName: map[string]fieldCodec{
- "A": {index: 0},
- "B": {index: 1},
- "CC.O": {index: 2, substructCodec: oStructCodec},
- "DDD.R": {index: 3, substructCodec: rStructCodec},
- "DDD.S.P": {index: 3, substructCodec: rStructCodec},
- "DDD.S.Q": {index: 3, substructCodec: rStructCodec},
- "DDD.T.O": {index: 3, substructCodec: rStructCodec},
- "DDD.O": {index: 3, substructCodec: rStructCodec},
- "O": {index: 4, substructCodec: oStructCodec},
- },
- complete: true,
- },
- },
- {
- "struct tags with nested and embedded structs",
- struct {
- A int `datastore:"-"`
- B int `datastore:"w"`
- C oStruct `datastore:"xx"`
- D rStruct `datastore:"y"`
- oStruct `datastore:"z"`
- }{},
- &structCodec{
- byIndex: []structTag{
- {name: "-"},
- {name: "w"},
- {name: "xx."},
- {name: "y."},
- {name: "z."},
- },
- byName: map[string]fieldCodec{
- "w": {index: 1},
- "xx.O": {index: 2, substructCodec: oStructCodec},
- "y.R": {index: 3, substructCodec: rStructCodec},
- "y.S.P": {index: 3, substructCodec: rStructCodec},
- "y.S.Q": {index: 3, substructCodec: rStructCodec},
- "y.T.O": {index: 3, substructCodec: rStructCodec},
- "y.O": {index: 3, substructCodec: rStructCodec},
- "z.O": {index: 4, substructCodec: oStructCodec},
- },
- complete: true,
- },
- },
- {
- "unexported nested and embedded structs",
- struct {
- a int
- B int
- c uStruct
- D uStruct
- uStruct
- }{},
- &structCodec{
- byIndex: []structTag{
- {name: "a"},
- {name: "B"},
- {name: "c."},
- {name: "D."},
- {name: ""},
- },
- byName: map[string]fieldCodec{
- "a": {index: 0},
- "B": {index: 1},
- "c.U": {index: 2, substructCodec: uStructCodec},
- "c.v": {index: 2, substructCodec: uStructCodec},
- "D.U": {index: 3, substructCodec: uStructCodec},
- "D.v": {index: 3, substructCodec: uStructCodec},
- "U": {index: 4, substructCodec: uStructCodec},
- "v": {index: 4, substructCodec: uStructCodec},
- },
- complete: true,
- },
- },
- {
- "noindex nested struct",
- struct {
- A oStruct `datastore:",noindex"`
- }{},
- &structCodec{
- byIndex: []structTag{
- {name: "A.", noIndex: true},
- },
- byName: map[string]fieldCodec{
- "A.O": {index: 0, substructCodec: oStructCodec},
- },
- complete: true,
- },
- },
- {
- "noindex slice",
- struct {
- A []string `datastore:",noindex"`
- }{},
- &structCodec{
- byIndex: []structTag{
- {name: "A", noIndex: true},
- },
- byName: map[string]fieldCodec{
- "A": {index: 0},
- },
- hasSlice: true,
- complete: true,
- },
- },
- {
- "noindex embedded struct slice",
- struct {
- // vStruct has a single field, V, also with noindex.
- A []vStruct `datastore:",noindex"`
- }{},
- &structCodec{
- byIndex: []structTag{
- {name: "A.", noIndex: true},
- },
- byName: map[string]fieldCodec{
- "A.V": {index: 0, substructCodec: vStructCodec},
- },
- hasSlice: true,
- complete: true,
- },
- },
- }
- for _, tc := range testCases {
- got, err := getStructCodec(reflect.TypeOf(tc.structValue))
- if err != nil {
- t.Errorf("%s: getStructCodec: %v", tc.desc, err)
- continue
- }
- if !reflect.DeepEqual(got, tc.want) {
- t.Errorf("%s\ngot %+v\nwant %+v\n", tc.desc, got, tc.want)
- continue
- }
- }
- }
- func TestRepeatedPropertyName(t *testing.T) {
- good := []interface{}{
- struct {
- A int `datastore:"-"`
- }{},
- struct {
- A int `datastore:"b"`
- B int
- }{},
- struct {
- A int
- B int `datastore:"B"`
- }{},
- struct {
- A int `datastore:"B"`
- B int `datastore:"-"`
- }{},
- struct {
- A int `datastore:"-"`
- B int `datastore:"A"`
- }{},
- struct {
- A int `datastore:"B"`
- B int `datastore:"A"`
- }{},
- struct {
- A int `datastore:"B"`
- B int `datastore:"C"`
- C int `datastore:"A"`
- }{},
- struct {
- A int `datastore:"B"`
- B int `datastore:"C"`
- C int `datastore:"D"`
- }{},
- }
- bad := []interface{}{
- struct {
- A int `datastore:"B"`
- B int
- }{},
- struct {
- A int
- B int `datastore:"A"`
- }{},
- struct {
- A int `datastore:"C"`
- B int `datastore:"C"`
- }{},
- struct {
- A int `datastore:"B"`
- B int `datastore:"C"`
- C int `datastore:"B"`
- }{},
- }
- testGetStructCodec(t, good, bad)
- }
- func TestFlatteningNestedStructs(t *testing.T) {
- type deepGood struct {
- A struct {
- B []struct {
- C struct {
- D int
- }
- }
- }
- }
- type deepBad struct {
- A struct {
- B []struct {
- C struct {
- D []int
- }
- }
- }
- }
- type iSay struct {
- Tomato int
- }
- type youSay struct {
- Tomato int
- }
- type tweedledee struct {
- Dee int `datastore:"D"`
- }
- type tweedledum struct {
- Dum int `datastore:"D"`
- }
- good := []interface{}{
- struct {
- X []struct {
- Y string
- }
- }{},
- struct {
- X []struct {
- Y []byte
- }
- }{},
- struct {
- P []int
- X struct {
- Y []int
- }
- }{},
- struct {
- X struct {
- Y []int
- }
- Q []int
- }{},
- struct {
- P []int
- X struct {
- Y []int
- }
- Q []int
- }{},
- struct {
- deepGood
- }{},
- struct {
- DG deepGood
- }{},
- struct {
- Foo struct {
- Z int `datastore:"X"`
- } `datastore:"A"`
- Bar struct {
- Z int `datastore:"Y"`
- } `datastore:"A"`
- }{},
- }
- bad := []interface{}{
- struct {
- X []struct {
- Y []string
- }
- }{},
- struct {
- X []struct {
- Y []int
- }
- }{},
- struct {
- deepBad
- }{},
- struct {
- DB deepBad
- }{},
- struct {
- iSay
- youSay
- }{},
- struct {
- tweedledee
- tweedledum
- }{},
- struct {
- Foo struct {
- Z int
- } `datastore:"A"`
- Bar struct {
- Z int
- } `datastore:"A"`
- }{},
- }
- testGetStructCodec(t, good, bad)
- }
- func testGetStructCodec(t *testing.T, good []interface{}, bad []interface{}) {
- for _, x := range good {
- if _, err := getStructCodec(reflect.TypeOf(x)); err != nil {
- t.Errorf("type %T: got non-nil error (%s), want nil", x, err)
- }
- }
- for _, x := range bad {
- if _, err := getStructCodec(reflect.TypeOf(x)); err == nil {
- t.Errorf("type %T: got nil error, want non-nil", x)
- }
- }
- }
- func TestNilKeyIsStored(t *testing.T) {
- x := struct {
- K *Key
- I int
- }{}
- p := PropertyList{}
- // Save x as properties.
- p1, _ := SaveStruct(&x)
- p.Load(p1)
- // Set x's fields to non-zero.
- x.K = &Key{}
- x.I = 2
- // Load x from properties.
- p2, _ := p.Save()
- LoadStruct(&x, p2)
- // Check that x's fields were set to zero.
- if x.K != nil {
- t.Errorf("K field was not zero")
- }
- if x.I != 0 {
- t.Errorf("I field was not zero")
- }
- }
|