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