1// Copyright 2014 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package datastore
16
17import (
18	"context"
19	"encoding/json"
20	"errors"
21	"fmt"
22	"reflect"
23	"sort"
24	"strings"
25	"testing"
26	"time"
27
28	"cloud.google.com/go/internal/testutil"
29	"github.com/golang/protobuf/proto"
30	"github.com/google/go-cmp/cmp"
31	pb "google.golang.org/genproto/googleapis/datastore/v1"
32	"google.golang.org/grpc"
33)
34
35type (
36	myBlob   []byte
37	myByte   byte
38	myString string
39)
40
41func makeMyByteSlice(n int) []myByte {
42	b := make([]myByte, n)
43	for i := range b {
44		b[i] = myByte(i)
45	}
46	return b
47}
48
49func makeInt8Slice(n int) []int8 {
50	b := make([]int8, n)
51	for i := range b {
52		b[i] = int8(i)
53	}
54	return b
55}
56
57func makeUint8Slice(n int) []uint8 {
58	b := make([]uint8, n)
59	for i := range b {
60		b[i] = uint8(i)
61	}
62	return b
63}
64
65func newKey(stringID string, parent *Key) *Key {
66	return NameKey("kind", stringID, parent)
67}
68
69var (
70	testKey0     = newKey("name0", nil)
71	testKey1a    = newKey("name1", nil)
72	testKey1b    = newKey("name1", nil)
73	testKey2a    = newKey("name2", testKey0)
74	testKey2b    = newKey("name2", testKey0)
75	testGeoPt0   = GeoPoint{Lat: 1.2, Lng: 3.4}
76	testGeoPt1   = GeoPoint{Lat: 5, Lng: 10}
77	testBadGeoPt = GeoPoint{Lat: 1000, Lng: 34}
78
79	ts = time.Unix(1e9, 0).UTC()
80)
81
82type B0 struct {
83	B []byte `datastore:",noindex"`
84}
85
86type B1 struct {
87	B []int8
88}
89
90type B2 struct {
91	B myBlob `datastore:",noindex"`
92}
93
94type B3 struct {
95	B []myByte `datastore:",noindex"`
96}
97
98type B4 struct {
99	B [][]byte
100}
101
102type C0 struct {
103	I int
104	C chan int
105}
106
107type C1 struct {
108	I int
109	C *chan int
110}
111
112type C2 struct {
113	I int
114	C []chan int
115}
116
117type C3 struct {
118	C string
119}
120
121type c4 struct {
122	C string
123}
124
125type E struct{}
126
127type G0 struct {
128	G GeoPoint
129}
130
131type G1 struct {
132	G []GeoPoint
133}
134
135type K0 struct {
136	K *Key
137}
138
139type K1 struct {
140	K []*Key
141}
142
143type S struct {
144	St string
145}
146
147type NoOmit struct {
148	A string
149	B int  `datastore:"Bb"`
150	C bool `datastore:",noindex"`
151}
152
153type OmitAll struct {
154	A string    `datastore:",omitempty"`
155	B int       `datastore:"Bb,omitempty"`
156	C bool      `datastore:",omitempty,noindex"`
157	D time.Time `datastore:",omitempty"`
158	F []int     `datastore:",omitempty"`
159}
160
161type Omit struct {
162	A string    `datastore:",omitempty"`
163	B int       `datastore:"Bb,omitempty"`
164	C bool      `datastore:",omitempty,noindex"`
165	D time.Time `datastore:",omitempty"`
166	F []int     `datastore:",omitempty"`
167	S `datastore:",omitempty"`
168}
169
170type NoOmits struct {
171	No []NoOmit `datastore:",omitempty"`
172	S  `datastore:",omitempty"`
173	Ss S `datastore:",omitempty"`
174}
175
176type N0 struct {
177	X0
178	Nonymous X0
179	Ignore   string `datastore:"-"`
180	Other    string
181}
182
183type N1 struct {
184	X0
185	Nonymous []X0
186	Ignore   string `datastore:"-"`
187	Other    string
188}
189
190type N2 struct {
191	N1    `datastore:"red"`
192	Green N1 `datastore:"green"`
193	Blue  N1
194	White N1 `datastore:"-"`
195}
196
197type N3 struct {
198	C3 `datastore:"red"`
199}
200
201type N4 struct {
202	c4
203}
204
205type N5 struct {
206	c4 `datastore:"red"`
207}
208
209type O0 struct {
210	I int64
211}
212
213type O1 struct {
214	I int32
215}
216
217type U0 struct {
218	U uint
219}
220
221type U1 struct {
222	U string
223}
224
225type T struct {
226	T time.Time
227}
228
229type X0 struct {
230	S string
231	I int
232	i int
233}
234
235type X1 struct {
236	S myString
237	I int32
238	J int64
239}
240
241type X2 struct {
242	Z string
243}
244
245type X3 struct {
246	S bool
247	I int
248}
249
250type Y0 struct {
251	B bool
252	F []float64
253	G []float64
254}
255
256type Y1 struct {
257	B bool
258	F float64
259}
260
261type Y2 struct {
262	B bool
263	F []int64
264}
265
266type Pointers struct {
267	Pi *int
268	Ps *string
269	Pb *bool
270	Pf *float64
271	Pg *GeoPoint
272	Pt *time.Time
273}
274
275type PointersOmitEmpty struct {
276	Pi *int       `datastore:",omitempty"`
277	Ps *string    `datastore:",omitempty"`
278	Pb *bool      `datastore:",omitempty"`
279	Pf *float64   `datastore:",omitempty"`
280	Pg *GeoPoint  `datastore:",omitempty"`
281	Pt *time.Time `datastore:",omitempty"`
282}
283
284func populatedPointers() *Pointers {
285	var (
286		i int
287		s string
288		b bool
289		f float64
290		g GeoPoint
291		t time.Time
292	)
293	return &Pointers{
294		Pi: &i,
295		Ps: &s,
296		Pb: &b,
297		Pf: &f,
298		Pg: &g,
299		Pt: &t,
300	}
301}
302
303type Tagged struct {
304	A int   `datastore:"a,noindex"`
305	B []int `datastore:"b"`
306	C int   `datastore:",noindex"`
307	D int   `datastore:""`
308	E int
309	I int `datastore:"-"`
310	J int `datastore:",noindex" json:"j"`
311
312	Y0 `datastore:"-"`
313	Z  chan int `datastore:"-"`
314}
315
316type InvalidTagged1 struct {
317	I int `datastore:"\t"`
318}
319
320type InvalidTagged2 struct {
321	I int
322	J int `datastore:"I"`
323}
324
325type InvalidTagged3 struct {
326	X string `datastore:"-,noindex"`
327}
328
329type InvalidTagged4 struct {
330	X string `datastore:",garbage"`
331}
332
333type Inner1 struct {
334	W int32
335	X string
336}
337
338type Inner2 struct {
339	Y float64
340}
341
342type Inner3 struct {
343	Z bool
344}
345
346type Inner5 struct {
347	WW int
348}
349
350type Inner4 struct {
351	X Inner5
352}
353
354type Outer struct {
355	A int16
356	I []Inner1
357	J Inner2
358	Inner3
359}
360
361type OuterFlatten struct {
362	A      int16
363	I      []Inner1 `datastore:",flatten"`
364	J      Inner2   `datastore:",flatten,noindex"`
365	Inner3 `datastore:",flatten"`
366	K      Inner4  `datastore:",flatten"`
367	L      *Inner2 `datastore:",flatten"`
368}
369
370type OuterEquivalent struct {
371	A     int16
372	IDotW []int32  `datastore:"I.W"`
373	IDotX []string `datastore:"I.X"`
374	JDotY float64  `datastore:"J.Y"`
375	Z     bool
376}
377
378type Dotted struct {
379	A DottedA `datastore:"A0.A1.A2"`
380}
381
382type DottedA struct {
383	B DottedB `datastore:"B3"`
384}
385
386type DottedB struct {
387	C int `datastore:"C4.C5"`
388}
389
390type SliceOfSlices struct {
391	I int
392	S []struct {
393		J int
394		F []float64
395	} `datastore:",flatten"`
396}
397
398type Recursive struct {
399	I int
400	R []Recursive
401}
402
403type MutuallyRecursive0 struct {
404	I int
405	R []MutuallyRecursive1
406}
407
408type MutuallyRecursive1 struct {
409	I int
410	R []MutuallyRecursive0
411}
412
413type EntityWithKey struct {
414	I int
415	S string
416	K *Key `datastore:"__key__"`
417}
418
419type WithNestedEntityWithKey struct {
420	N EntityWithKey
421}
422
423type WithNonKeyField struct {
424	I int
425	K string `datastore:"__key__"`
426}
427
428type NestedWithNonKeyField struct {
429	N WithNonKeyField
430}
431
432type Basic struct {
433	A string
434}
435
436type PtrToStructField struct {
437	B *Basic
438	C *Basic `datastore:"c,noindex"`
439	*Basic
440	D []*Basic
441}
442
443type EmbeddedTime struct {
444	time.Time
445}
446
447type SpecialTime struct {
448	MyTime EmbeddedTime
449}
450
451type Doubler struct {
452	S string
453	I int64
454	B bool
455}
456
457type Repeat struct {
458	Key   string
459	Value []byte
460}
461
462type Repeated struct {
463	Repeats []Repeat
464}
465
466func (d *Doubler) Load(props []Property) error {
467	return LoadStruct(d, props)
468}
469
470func (d *Doubler) Save() ([]Property, error) {
471	// Save the default Property slice to an in-memory buffer (a PropertyList).
472	props, err := SaveStruct(d)
473	if err != nil {
474		return nil, err
475	}
476	var list PropertyList
477	if err := list.Load(props); err != nil {
478		return nil, err
479	}
480
481	// Edit that PropertyList, and send it on.
482	for i := range list {
483		switch v := list[i].Value.(type) {
484		case string:
485			// + means string concatenation.
486			list[i].Value = v + v
487		case int64:
488			// + means integer addition.
489			list[i].Value = v + v
490		}
491	}
492	return list.Save()
493}
494
495var _ PropertyLoadSaver = (*Doubler)(nil)
496
497type Deriver struct {
498	S, Derived, Ignored string
499}
500
501func (e *Deriver) Load(props []Property) error {
502	for _, p := range props {
503		if p.Name != "S" {
504			continue
505		}
506		e.S = p.Value.(string)
507		e.Derived = "derived+" + e.S
508	}
509	return nil
510}
511
512func (e *Deriver) Save() ([]Property, error) {
513	return []Property{
514		{
515			Name:  "S",
516			Value: e.S,
517		},
518	}, nil
519}
520
521var _ PropertyLoadSaver = (*Deriver)(nil)
522
523type BadMultiPropEntity struct{}
524
525func (e *BadMultiPropEntity) Load(props []Property) error {
526	return errors.New("unimplemented")
527}
528
529func (e *BadMultiPropEntity) Save() ([]Property, error) {
530	// Write multiple properties with the same name "I".
531	var props []Property
532	for i := 0; i < 3; i++ {
533		props = append(props, Property{
534			Name:  "I",
535			Value: int64(i),
536		})
537	}
538	return props, nil
539}
540
541var _ PropertyLoadSaver = (*BadMultiPropEntity)(nil)
542
543type testCase struct {
544	desc   string
545	src    interface{}
546	want   interface{}
547	putErr string
548	getErr string
549}
550
551var testCases = []testCase{
552	{
553		"chan save fails",
554		&C0{I: -1},
555		&E{},
556		"unsupported struct field",
557		"",
558	},
559	{
560		"*chan save fails",
561		&C1{I: -1},
562		&E{},
563		"unsupported struct field",
564		"",
565	},
566	{
567		"[]chan save fails",
568		&C2{I: -1, C: make([]chan int, 8)},
569		&E{},
570		"unsupported struct field",
571		"",
572	},
573	{
574		"chan load fails",
575		&C3{C: "not a chan"},
576		&C0{},
577		"",
578		"type mismatch",
579	},
580	{
581		"*chan load fails",
582		&C3{C: "not a *chan"},
583		&C1{},
584		"",
585		"type mismatch",
586	},
587	{
588		"[]chan load fails",
589		&C3{C: "not a []chan"},
590		&C2{},
591		"",
592		"type mismatch",
593	},
594	{
595		"empty struct",
596		&E{},
597		&E{},
598		"",
599		"",
600	},
601	{
602		"geopoint",
603		&G0{G: testGeoPt0},
604		&G0{G: testGeoPt0},
605		"",
606		"",
607	},
608	{
609		"geopoint invalid",
610		&G0{G: testBadGeoPt},
611		&G0{},
612		"invalid GeoPoint value",
613		"",
614	},
615	{
616		"geopoint as props",
617		&G0{G: testGeoPt0},
618		&PropertyList{
619			Property{Name: "G", Value: testGeoPt0, NoIndex: false},
620		},
621		"",
622		"",
623	},
624	{
625		"geopoint slice",
626		&G1{G: []GeoPoint{testGeoPt0, testGeoPt1}},
627		&G1{G: []GeoPoint{testGeoPt0, testGeoPt1}},
628		"",
629		"",
630	},
631	{
632		"omit empty, all",
633		&OmitAll{},
634		new(PropertyList),
635		"",
636		"",
637	},
638	{
639		"omit empty",
640		&Omit{},
641		&PropertyList{
642			Property{Name: "St", Value: "", NoIndex: false},
643		},
644		"",
645		"",
646	},
647	{
648		"omit empty, fields populated",
649		&Omit{
650			A: "a",
651			B: 10,
652			C: true,
653			F: []int{11},
654		},
655		&PropertyList{
656			Property{Name: "A", Value: "a", NoIndex: false},
657			Property{Name: "Bb", Value: int64(10), NoIndex: false},
658			Property{Name: "C", Value: true, NoIndex: true},
659			Property{Name: "F", Value: []interface{}{int64(11)}, NoIndex: false},
660			Property{Name: "St", Value: "", NoIndex: false},
661		},
662		"",
663		"",
664	},
665	{
666		"omit empty, fields populated",
667		&Omit{
668			A: "a",
669			B: 10,
670			C: true,
671			F: []int{11},
672			S: S{St: "string"},
673		},
674		&PropertyList{
675			Property{Name: "A", Value: "a", NoIndex: false},
676			Property{Name: "Bb", Value: int64(10), NoIndex: false},
677			Property{Name: "C", Value: true, NoIndex: true},
678			Property{Name: "F", Value: []interface{}{int64(11)}, NoIndex: false},
679			Property{Name: "St", Value: "string", NoIndex: false},
680		},
681		"",
682		"",
683	},
684	{
685		"omit empty does not propagate",
686		&NoOmits{
687			No: []NoOmit{
688				{},
689			},
690			S:  S{},
691			Ss: S{},
692		},
693		&PropertyList{
694			Property{Name: "No", Value: []interface{}{
695				&Entity{
696					Properties: []Property{
697						{Name: "A", Value: "", NoIndex: false},
698						{Name: "Bb", Value: int64(0), NoIndex: false},
699						{Name: "C", Value: false, NoIndex: true},
700					},
701				},
702			}, NoIndex: false},
703			Property{Name: "Ss", Value: &Entity{
704				Properties: []Property{
705					{Name: "St", Value: "", NoIndex: false},
706				},
707			}, NoIndex: false},
708			Property{Name: "St", Value: "", NoIndex: false},
709		},
710		"",
711		"",
712	},
713	{
714		"key",
715		&K0{K: testKey1a},
716		&K0{K: testKey1b},
717		"",
718		"",
719	},
720	{
721		"key with parent",
722		&K0{K: testKey2a},
723		&K0{K: testKey2b},
724		"",
725		"",
726	},
727	{
728		"nil key",
729		&K0{},
730		&K0{},
731		"",
732		"",
733	},
734	{
735		"all nil keys in slice",
736		&K1{[]*Key{nil, nil}},
737		&K1{[]*Key{nil, nil}},
738		"",
739		"",
740	},
741	{
742		"some nil keys in slice",
743		&K1{[]*Key{testKey1a, nil, testKey2a}},
744		&K1{[]*Key{testKey1b, nil, testKey2b}},
745		"",
746		"",
747	},
748	{
749		"overflow",
750		&O0{I: 1 << 48},
751		&O1{},
752		"",
753		"overflow",
754	},
755	{
756		"time",
757		&T{T: time.Unix(1e9, 0)},
758		&T{T: time.Unix(1e9, 0)},
759		"",
760		"",
761	},
762	{
763		"time as props",
764		&T{T: time.Unix(1e9, 0)},
765		&PropertyList{
766			Property{Name: "T", Value: time.Unix(1e9, 0), NoIndex: false},
767		},
768		"",
769		"",
770	},
771	{
772		"uint save",
773		&U0{U: 1},
774		&U0{},
775		"unsupported struct field",
776		"",
777	},
778	{
779		"uint load",
780		&U1{U: "not a uint"},
781		&U0{},
782		"",
783		"type mismatch",
784	},
785	{
786		"zero",
787		&X0{},
788		&X0{},
789		"",
790		"",
791	},
792	{
793		"basic",
794		&X0{S: "one", I: 2, i: 3},
795		&X0{S: "one", I: 2},
796		"",
797		"",
798	},
799	{
800		"save string/int load myString/int32",
801		&X0{S: "one", I: 2, i: 3},
802		&X1{S: "one", I: 2},
803		"",
804		"",
805	},
806	{
807		"missing fields",
808		&X0{S: "one", I: 2, i: 3},
809		&X2{},
810		"",
811		"no such struct field",
812	},
813	{
814		"save string load bool",
815		&X0{S: "one", I: 2, i: 3},
816		&X3{I: 2},
817		"",
818		"type mismatch",
819	},
820	{
821		"basic slice",
822		&Y0{B: true, F: []float64{7, 8, 9}},
823		&Y0{B: true, F: []float64{7, 8, 9}},
824		"",
825		"",
826	},
827	{
828		"save []float64 load float64",
829		&Y0{B: true, F: []float64{7, 8, 9}},
830		&Y1{B: true},
831		"",
832		"requires a slice",
833	},
834	{
835		"save []float64 load []int64",
836		&Y0{B: true, F: []float64{7, 8, 9}},
837		&Y2{B: true},
838		"",
839		"type mismatch",
840	},
841	{
842		"single slice is too long",
843		&Y0{F: make([]float64, maxIndexedProperties+1)},
844		&Y0{},
845		"too many indexed properties",
846		"",
847	},
848	{
849		"two slices are too long",
850		&Y0{F: make([]float64, maxIndexedProperties), G: make([]float64, maxIndexedProperties)},
851		&Y0{},
852		"too many indexed properties",
853		"",
854	},
855	{
856		"one slice and one scalar are too long",
857		&Y0{F: make([]float64, maxIndexedProperties), B: true},
858		&Y0{},
859		"too many indexed properties",
860		"",
861	},
862	{
863		"slice of slices of bytes",
864		&Repeated{
865			Repeats: []Repeat{
866				{
867					Key:   "key 1",
868					Value: []byte("value 1"),
869				},
870				{
871					Key:   "key 2",
872					Value: []byte("value 2"),
873				},
874			},
875		},
876		&Repeated{
877			Repeats: []Repeat{
878				{
879					Key:   "key 1",
880					Value: []byte("value 1"),
881				},
882				{
883					Key:   "key 2",
884					Value: []byte("value 2"),
885				},
886			},
887		},
888		"",
889		"",
890	},
891	{
892		"long blob",
893		&B0{B: makeUint8Slice(maxIndexedProperties + 1)},
894		&B0{B: makeUint8Slice(maxIndexedProperties + 1)},
895		"",
896		"",
897	},
898	{
899		"long []int8 is too long",
900		&B1{B: makeInt8Slice(maxIndexedProperties + 1)},
901		&B1{},
902		"too many indexed properties",
903		"",
904	},
905	{
906		"short []int8",
907		&B1{B: makeInt8Slice(3)},
908		&B1{B: makeInt8Slice(3)},
909		"",
910		"",
911	},
912	{
913		"long myBlob",
914		&B2{B: makeUint8Slice(maxIndexedProperties + 1)},
915		&B2{B: makeUint8Slice(maxIndexedProperties + 1)},
916		"",
917		"",
918	},
919	{
920		"short myBlob",
921		&B2{B: makeUint8Slice(3)},
922		&B2{B: makeUint8Slice(3)},
923		"",
924		"",
925	},
926	{
927		"long []myByte",
928		&B3{B: makeMyByteSlice(maxIndexedProperties + 1)},
929		&B3{B: makeMyByteSlice(maxIndexedProperties + 1)},
930		"",
931		"",
932	},
933	{
934		"short []myByte",
935		&B3{B: makeMyByteSlice(3)},
936		&B3{B: makeMyByteSlice(3)},
937		"",
938		"",
939	},
940	{
941		"slice of blobs",
942		&B4{B: [][]byte{
943			makeUint8Slice(3),
944			makeUint8Slice(4),
945			makeUint8Slice(5),
946		}},
947		&B4{B: [][]byte{
948			makeUint8Slice(3),
949			makeUint8Slice(4),
950			makeUint8Slice(5),
951		}},
952		"",
953		"",
954	},
955	{
956		"[]byte must be noindex",
957		&PropertyList{
958			Property{Name: "B", Value: makeUint8Slice(1501), NoIndex: false},
959		},
960		nil,
961		"[]byte property too long to index",
962		"",
963	},
964	{
965		"string must be noindex",
966		&PropertyList{
967			Property{Name: "B", Value: strings.Repeat("x", 1501), NoIndex: false},
968		},
969		nil,
970		"string property too long to index",
971		"",
972	},
973	{
974		"slice of []byte must be noindex",
975		&PropertyList{
976			Property{Name: "B", Value: []interface{}{
977				[]byte("short"),
978				makeUint8Slice(1501),
979			}, NoIndex: false},
980		},
981		nil,
982		"[]byte property too long to index",
983		"",
984	},
985	{
986		"slice of string must be noindex",
987		&PropertyList{
988			Property{Name: "B", Value: []interface{}{
989				"short",
990				strings.Repeat("x", 1501),
991			}, NoIndex: false},
992		},
993		nil,
994		"string property too long to index",
995		"",
996	},
997	{
998		"save tagged load props",
999		&Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6, J: 7},
1000		&PropertyList{
1001			// A and B are renamed to a and b; A and C are noindex, I is ignored.
1002			// Order is sorted as per byName.
1003			Property{Name: "C", Value: int64(3), NoIndex: true},
1004			Property{Name: "D", Value: int64(4), NoIndex: false},
1005			Property{Name: "E", Value: int64(5), NoIndex: false},
1006			Property{Name: "J", Value: int64(7), NoIndex: true},
1007			Property{Name: "a", Value: int64(1), NoIndex: true},
1008			Property{Name: "b", Value: []interface{}{int64(21), int64(22), int64(23)}, NoIndex: false},
1009		},
1010		"",
1011		"",
1012	},
1013	{
1014		"save tagged load tagged",
1015		&Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6, J: 7},
1016		&Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, J: 7},
1017		"",
1018		"",
1019	},
1020	{
1021		"invalid tagged1",
1022		&InvalidTagged1{I: 1},
1023		&InvalidTagged1{},
1024		"struct tag has invalid property name",
1025		"",
1026	},
1027	{
1028		"invalid tagged2",
1029		&InvalidTagged2{I: 1, J: 2},
1030		&InvalidTagged2{J: 2},
1031		"",
1032		"",
1033	},
1034	{
1035		"invalid tagged3",
1036		&InvalidTagged3{X: "hello"},
1037		&InvalidTagged3{},
1038		"struct tag has invalid property name: \"-\"",
1039		"",
1040	},
1041	{
1042		"invalid tagged4",
1043		&InvalidTagged4{X: "hello"},
1044		&InvalidTagged4{},
1045		"struct tag has invalid option: \"garbage\"",
1046		"",
1047	},
1048	{
1049		"doubler",
1050		&Doubler{S: "s", I: 1, B: true},
1051		&Doubler{S: "ss", I: 2, B: true},
1052		"",
1053		"",
1054	},
1055	{
1056		"save struct load props",
1057		&X0{S: "s", I: 1},
1058		&PropertyList{
1059			Property{Name: "I", Value: int64(1), NoIndex: false},
1060			Property{Name: "S", Value: "s", NoIndex: false},
1061		},
1062		"",
1063		"",
1064	},
1065	{
1066		"save props load struct",
1067		&PropertyList{
1068			Property{Name: "I", Value: int64(1), NoIndex: false},
1069			Property{Name: "S", Value: "s", NoIndex: false},
1070		},
1071		&X0{S: "s", I: 1},
1072		"",
1073		"",
1074	},
1075	{
1076		"nil-value props",
1077		&PropertyList{
1078			Property{Name: "I", Value: nil, NoIndex: false},
1079			Property{Name: "B", Value: nil, NoIndex: false},
1080			Property{Name: "S", Value: nil, NoIndex: false},
1081			Property{Name: "F", Value: nil, NoIndex: false},
1082			Property{Name: "K", Value: nil, NoIndex: false},
1083			Property{Name: "T", Value: nil, NoIndex: false},
1084			Property{Name: "J", Value: []interface{}{nil, int64(7), nil}, NoIndex: false},
1085		},
1086		&struct {
1087			I int64
1088			B bool
1089			S string
1090			F float64
1091			K *Key
1092			T time.Time
1093			J []int64
1094		}{
1095			J: []int64{0, 7, 0},
1096		},
1097		"",
1098		"",
1099	},
1100	{
1101		"save outer load props flatten",
1102		&OuterFlatten{
1103			A: 1,
1104			I: []Inner1{
1105				{10, "ten"},
1106				{20, "twenty"},
1107				{30, "thirty"},
1108			},
1109			J: Inner2{
1110				Y: 3.14,
1111			},
1112			Inner3: Inner3{
1113				Z: true,
1114			},
1115			K: Inner4{
1116				X: Inner5{
1117					WW: 12,
1118				},
1119			},
1120			L: &Inner2{
1121				Y: 2.71,
1122			},
1123		},
1124		&PropertyList{
1125			Property{Name: "A", Value: int64(1), NoIndex: false},
1126			Property{Name: "I.W", Value: []interface{}{int64(10), int64(20), int64(30)}, NoIndex: false},
1127			Property{Name: "I.X", Value: []interface{}{"ten", "twenty", "thirty"}, NoIndex: false},
1128			Property{Name: "J.Y", Value: float64(3.14), NoIndex: true},
1129			Property{Name: "K.X.WW", Value: int64(12), NoIndex: false},
1130			Property{Name: "L.Y", Value: float64(2.71), NoIndex: false},
1131			Property{Name: "Z", Value: true, NoIndex: false},
1132		},
1133		"",
1134		"",
1135	},
1136	{
1137		"load outer props flatten",
1138		&PropertyList{
1139			Property{Name: "A", Value: int64(1), NoIndex: false},
1140			Property{Name: "I.W", Value: []interface{}{int64(10), int64(20), int64(30)}, NoIndex: false},
1141			Property{Name: "I.X", Value: []interface{}{"ten", "twenty", "thirty"}, NoIndex: false},
1142			Property{Name: "J.Y", Value: float64(3.14), NoIndex: true},
1143			Property{Name: "L.Y", Value: float64(2.71), NoIndex: false},
1144			Property{Name: "Z", Value: true, NoIndex: false},
1145		},
1146		&OuterFlatten{
1147			A: 1,
1148			I: []Inner1{
1149				{10, "ten"},
1150				{20, "twenty"},
1151				{30, "thirty"},
1152			},
1153			J: Inner2{
1154				Y: 3.14,
1155			},
1156			Inner3: Inner3{
1157				Z: true,
1158			},
1159			L: &Inner2{
1160				Y: 2.71,
1161			},
1162		},
1163		"",
1164		"",
1165	},
1166	{
1167		"save outer load props",
1168		&Outer{
1169			A: 1,
1170			I: []Inner1{
1171				{10, "ten"},
1172				{20, "twenty"},
1173				{30, "thirty"},
1174			},
1175			J: Inner2{
1176				Y: 3.14,
1177			},
1178			Inner3: Inner3{
1179				Z: true,
1180			},
1181		},
1182		&PropertyList{
1183			Property{Name: "A", Value: int64(1), NoIndex: false},
1184			Property{Name: "I", Value: []interface{}{
1185				&Entity{
1186					Properties: []Property{
1187						{Name: "W", Value: int64(10), NoIndex: false},
1188						{Name: "X", Value: "ten", NoIndex: false},
1189					},
1190				},
1191				&Entity{
1192					Properties: []Property{
1193						{Name: "W", Value: int64(20), NoIndex: false},
1194						{Name: "X", Value: "twenty", NoIndex: false},
1195					},
1196				},
1197				&Entity{
1198					Properties: []Property{
1199						{Name: "W", Value: int64(30), NoIndex: false},
1200						{Name: "X", Value: "thirty", NoIndex: false},
1201					},
1202				},
1203			}, NoIndex: false},
1204			Property{Name: "J", Value: &Entity{
1205				Properties: []Property{
1206					{Name: "Y", Value: float64(3.14), NoIndex: false},
1207				},
1208			}, NoIndex: false},
1209			Property{Name: "Z", Value: true, NoIndex: false},
1210		},
1211		"",
1212		"",
1213	},
1214	{
1215		"save props load outer-equivalent",
1216		&PropertyList{
1217			Property{Name: "A", Value: int64(1), NoIndex: false},
1218			Property{Name: "I.W", Value: []interface{}{int64(10), int64(20), int64(30)}, NoIndex: false},
1219			Property{Name: "I.X", Value: []interface{}{"ten", "twenty", "thirty"}, NoIndex: false},
1220			Property{Name: "J.Y", Value: float64(3.14), NoIndex: false},
1221			Property{Name: "Z", Value: true, NoIndex: false},
1222		},
1223		&OuterEquivalent{
1224			A:     1,
1225			IDotW: []int32{10, 20, 30},
1226			IDotX: []string{"ten", "twenty", "thirty"},
1227			JDotY: 3.14,
1228			Z:     true,
1229		},
1230		"",
1231		"",
1232	},
1233	{
1234		"dotted names save",
1235		&Dotted{A: DottedA{B: DottedB{C: 88}}},
1236		&PropertyList{
1237			Property{Name: "A0.A1.A2", Value: &Entity{
1238				Properties: []Property{
1239					{Name: "B3", Value: &Entity{
1240						Properties: []Property{
1241							{Name: "C4.C5", Value: int64(88), NoIndex: false},
1242						},
1243					}, NoIndex: false},
1244				},
1245			}, NoIndex: false},
1246		},
1247		"",
1248		"",
1249	},
1250	{
1251		"dotted names load",
1252		&PropertyList{
1253			Property{Name: "A0.A1.A2", Value: &Entity{
1254				Properties: []Property{
1255					{Name: "B3", Value: &Entity{
1256						Properties: []Property{
1257							{Name: "C4.C5", Value: 99, NoIndex: false},
1258						},
1259					}, NoIndex: false},
1260				},
1261			}, NoIndex: false},
1262		},
1263		&Dotted{A: DottedA{B: DottedB{C: 99}}},
1264		"",
1265		"",
1266	},
1267	{
1268		"save struct load deriver",
1269		&X0{S: "s", I: 1},
1270		&Deriver{S: "s", Derived: "derived+s"},
1271		"",
1272		"",
1273	},
1274	{
1275		"save deriver load struct",
1276		&Deriver{S: "s", Derived: "derived+s", Ignored: "ignored"},
1277		&X0{S: "s"},
1278		"",
1279		"",
1280	},
1281	{
1282		"zero time.Time",
1283		&T{T: time.Time{}},
1284		&T{T: time.Time{}},
1285		"",
1286		"",
1287	},
1288	{
1289		"time.Time near Unix zero time",
1290		&T{T: time.Unix(0, 4e3)},
1291		&T{T: time.Unix(0, 4e3)},
1292		"",
1293		"",
1294	},
1295	{
1296		"time.Time, far in the future",
1297		&T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)},
1298		&T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)},
1299		"",
1300		"",
1301	},
1302	{
1303		"time.Time, very far in the past",
1304		&T{T: time.Date(-300000, 1, 1, 0, 0, 0, 0, time.UTC)},
1305		&T{},
1306		"time value out of range",
1307		"",
1308	},
1309	{
1310		"time.Time, very far in the future",
1311		&T{T: time.Date(294248, 1, 1, 0, 0, 0, 0, time.UTC)},
1312		&T{},
1313		"time value out of range",
1314		"",
1315	},
1316	{
1317		"structs",
1318		&N0{
1319			X0:       X0{S: "one", I: 2, i: 3},
1320			Nonymous: X0{S: "four", I: 5, i: 6},
1321			Ignore:   "ignore",
1322			Other:    "other",
1323		},
1324		&N0{
1325			X0:       X0{S: "one", I: 2},
1326			Nonymous: X0{S: "four", I: 5},
1327			Other:    "other",
1328		},
1329		"",
1330		"",
1331	},
1332	{
1333		"slice of structs",
1334		&N1{
1335			X0: X0{S: "one", I: 2, i: 3},
1336			Nonymous: []X0{
1337				{S: "four", I: 5, i: 6},
1338				{S: "seven", I: 8, i: 9},
1339				{S: "ten", I: 11, i: 12},
1340				{S: "thirteen", I: 14, i: 15},
1341			},
1342			Ignore: "ignore",
1343			Other:  "other",
1344		},
1345		&N1{
1346			X0: X0{S: "one", I: 2},
1347			Nonymous: []X0{
1348				{S: "four", I: 5},
1349				{S: "seven", I: 8},
1350				{S: "ten", I: 11},
1351				{S: "thirteen", I: 14},
1352			},
1353			Other: "other",
1354		},
1355		"",
1356		"",
1357	},
1358	{
1359		"structs with slices of structs",
1360		&N2{
1361			N1: N1{
1362				X0: X0{S: "rouge"},
1363				Nonymous: []X0{
1364					{S: "rosso0"},
1365					{S: "rosso1"},
1366				},
1367			},
1368			Green: N1{
1369				X0: X0{S: "vert"},
1370				Nonymous: []X0{
1371					{S: "verde0"},
1372					{S: "verde1"},
1373					{S: "verde2"},
1374				},
1375			},
1376			Blue: N1{
1377				X0: X0{S: "bleu"},
1378				Nonymous: []X0{
1379					{S: "blu0"},
1380					{S: "blu1"},
1381					{S: "blu2"},
1382					{S: "blu3"},
1383				},
1384			},
1385		},
1386		&N2{
1387			N1: N1{
1388				X0: X0{S: "rouge"},
1389				Nonymous: []X0{
1390					{S: "rosso0"},
1391					{S: "rosso1"},
1392				},
1393			},
1394			Green: N1{
1395				X0: X0{S: "vert"},
1396				Nonymous: []X0{
1397					{S: "verde0"},
1398					{S: "verde1"},
1399					{S: "verde2"},
1400				},
1401			},
1402			Blue: N1{
1403				X0: X0{S: "bleu"},
1404				Nonymous: []X0{
1405					{S: "blu0"},
1406					{S: "blu1"},
1407					{S: "blu2"},
1408					{S: "blu3"},
1409				},
1410			},
1411		},
1412		"",
1413		"",
1414	},
1415	{
1416		"save structs load props",
1417		&N2{
1418			N1: N1{
1419				X0: X0{S: "rouge"},
1420				Nonymous: []X0{
1421					{S: "rosso0"},
1422					{S: "rosso1"},
1423				},
1424			},
1425			Green: N1{
1426				X0: X0{S: "vert"},
1427				Nonymous: []X0{
1428					{S: "verde0"},
1429					{S: "verde1"},
1430					{S: "verde2"},
1431				},
1432			},
1433			Blue: N1{
1434				X0: X0{S: "bleu"},
1435				Nonymous: []X0{
1436					{S: "blu0"},
1437					{S: "blu1"},
1438					{S: "blu2"},
1439					{S: "blu3"},
1440				},
1441			},
1442		},
1443		&PropertyList{
1444			Property{Name: "Blue", Value: &Entity{
1445				Properties: []Property{
1446					{Name: "I", Value: int64(0), NoIndex: false},
1447					{Name: "Nonymous", Value: []interface{}{
1448						&Entity{
1449							Properties: []Property{
1450								{Name: "I", Value: int64(0), NoIndex: false},
1451								{Name: "S", Value: "blu0", NoIndex: false},
1452							},
1453						},
1454						&Entity{
1455							Properties: []Property{
1456								{Name: "I", Value: int64(0), NoIndex: false},
1457								{Name: "S", Value: "blu1", NoIndex: false},
1458							},
1459						},
1460						&Entity{
1461							Properties: []Property{
1462								{Name: "I", Value: int64(0), NoIndex: false},
1463								{Name: "S", Value: "blu2", NoIndex: false},
1464							},
1465						},
1466						&Entity{
1467							Properties: []Property{
1468								{Name: "I", Value: int64(0), NoIndex: false},
1469								{Name: "S", Value: "blu3", NoIndex: false},
1470							},
1471						},
1472					}, NoIndex: false},
1473					{Name: "Other", Value: "", NoIndex: false},
1474					{Name: "S", Value: "bleu", NoIndex: false},
1475				},
1476			}, NoIndex: false},
1477			Property{Name: "green", Value: &Entity{
1478				Properties: []Property{
1479					{Name: "I", Value: int64(0), NoIndex: false},
1480					{Name: "Nonymous", Value: []interface{}{
1481						&Entity{
1482							Properties: []Property{
1483								{Name: "I", Value: int64(0), NoIndex: false},
1484								{Name: "S", Value: "verde0", NoIndex: false},
1485							},
1486						},
1487						&Entity{
1488							Properties: []Property{
1489								{Name: "I", Value: int64(0), NoIndex: false},
1490								{Name: "S", Value: "verde1", NoIndex: false},
1491							},
1492						},
1493						&Entity{
1494							Properties: []Property{
1495								{Name: "I", Value: int64(0), NoIndex: false},
1496								{Name: "S", Value: "verde2", NoIndex: false},
1497							},
1498						},
1499					}, NoIndex: false},
1500					{Name: "Other", Value: "", NoIndex: false},
1501					{Name: "S", Value: "vert", NoIndex: false},
1502				},
1503			}, NoIndex: false},
1504			Property{Name: "red", Value: &Entity{
1505				Properties: []Property{
1506					{Name: "I", Value: int64(0), NoIndex: false},
1507					{Name: "Nonymous", Value: []interface{}{
1508						&Entity{
1509							Properties: []Property{
1510								{Name: "I", Value: int64(0), NoIndex: false},
1511								{Name: "S", Value: "rosso0", NoIndex: false},
1512							},
1513						},
1514						&Entity{
1515							Properties: []Property{
1516								{Name: "I", Value: int64(0), NoIndex: false},
1517								{Name: "S", Value: "rosso1", NoIndex: false},
1518							},
1519						},
1520					}, NoIndex: false},
1521					{Name: "Other", Value: "", NoIndex: false},
1522					{Name: "S", Value: "rouge", NoIndex: false},
1523				},
1524			}, NoIndex: false},
1525		},
1526		"",
1527		"",
1528	},
1529	{
1530		"nested entity with key",
1531		&WithNestedEntityWithKey{
1532			N: EntityWithKey{
1533				I: 12,
1534				S: "abcd",
1535				K: testKey0,
1536			},
1537		},
1538		&WithNestedEntityWithKey{
1539			N: EntityWithKey{
1540				I: 12,
1541				S: "abcd",
1542				K: testKey0,
1543			},
1544		},
1545		"",
1546		"",
1547	},
1548	{
1549		"entity with key at top level",
1550		&EntityWithKey{
1551			I: 12,
1552			S: "abc",
1553			K: testKey0,
1554		},
1555		&EntityWithKey{
1556			I: 12,
1557			S: "abc",
1558			K: testKey0,
1559		},
1560		"",
1561		"",
1562	},
1563	{
1564		"entity with key at top level (key is populated on load)",
1565		&EntityWithKey{
1566			I: 12,
1567			S: "abc",
1568		},
1569		&EntityWithKey{
1570			I: 12,
1571			S: "abc",
1572			K: testKey0,
1573		},
1574		"",
1575		"",
1576	},
1577	{
1578		"__key__ field not a *Key",
1579		&NestedWithNonKeyField{
1580			N: WithNonKeyField{
1581				I: 12,
1582				K: "abcd",
1583			},
1584		},
1585		&NestedWithNonKeyField{
1586			N: WithNonKeyField{
1587				I: 12,
1588				K: "abcd",
1589			},
1590		},
1591		"datastore: __key__ field on struct datastore.WithNonKeyField is not a *datastore.Key",
1592		"",
1593	},
1594	{
1595		"save struct with ptr to struct fields",
1596		&PtrToStructField{
1597			&Basic{
1598				A: "b",
1599			},
1600			&Basic{
1601				A: "c",
1602			},
1603			&Basic{
1604				A: "anon",
1605			},
1606			[]*Basic{
1607				{
1608					A: "slice0",
1609				},
1610				{
1611					A: "slice1",
1612				},
1613			},
1614		},
1615		&PropertyList{
1616			Property{Name: "A", Value: "anon", NoIndex: false},
1617			Property{Name: "B", Value: &Entity{
1618				Properties: []Property{
1619					{Name: "A", Value: "b", NoIndex: false},
1620				},
1621			}},
1622			Property{Name: "D", Value: []interface{}{
1623				&Entity{
1624					Properties: []Property{
1625						{Name: "A", Value: "slice0", NoIndex: false},
1626					},
1627				},
1628				&Entity{
1629					Properties: []Property{
1630						{Name: "A", Value: "slice1", NoIndex: false},
1631					},
1632				},
1633			}, NoIndex: false},
1634			Property{Name: "c", Value: &Entity{
1635				Properties: []Property{
1636					{Name: "A", Value: "c", NoIndex: true},
1637				},
1638			}, NoIndex: true},
1639		},
1640		"",
1641		"",
1642	},
1643	{
1644		"save and load struct with ptr to struct fields",
1645		&PtrToStructField{
1646			&Basic{
1647				A: "b",
1648			},
1649			&Basic{
1650				A: "c",
1651			},
1652			&Basic{
1653				A: "anon",
1654			},
1655			[]*Basic{
1656				{
1657					A: "slice0",
1658				},
1659				{
1660					A: "slice1",
1661				},
1662			},
1663		},
1664		&PtrToStructField{
1665			&Basic{
1666				A: "b",
1667			},
1668			&Basic{
1669				A: "c",
1670			},
1671			&Basic{
1672				A: "anon",
1673			},
1674			[]*Basic{
1675				{
1676					A: "slice0",
1677				},
1678				{
1679					A: "slice1",
1680				},
1681			},
1682		},
1683		"",
1684		"",
1685	},
1686	{
1687		"struct with nil ptr to struct fields",
1688		&PtrToStructField{
1689			nil,
1690			nil,
1691			nil,
1692			nil,
1693		},
1694		new(PropertyList),
1695		"",
1696		"",
1697	},
1698	{
1699		"nested load entity with key",
1700		&WithNestedEntityWithKey{
1701			N: EntityWithKey{
1702				I: 12,
1703				S: "abcd",
1704				K: testKey0,
1705			},
1706		},
1707		&PropertyList{
1708			Property{Name: "N", Value: &Entity{
1709				Key: testKey0,
1710				Properties: []Property{
1711					{Name: "I", Value: int64(12), NoIndex: false},
1712					{Name: "S", Value: "abcd", NoIndex: false},
1713				},
1714			},
1715				NoIndex: false},
1716		},
1717		"",
1718		"",
1719	},
1720	{
1721		"nested save entity with key",
1722		&PropertyList{
1723			Property{Name: "N", Value: &Entity{
1724				Key: testKey0,
1725				Properties: []Property{
1726					{Name: "I", Value: int64(12), NoIndex: false},
1727					{Name: "S", Value: "abcd", NoIndex: false},
1728				},
1729			}, NoIndex: false},
1730		},
1731
1732		&WithNestedEntityWithKey{
1733			N: EntityWithKey{
1734				I: 12,
1735				S: "abcd",
1736				K: testKey0,
1737			},
1738		},
1739		"",
1740		"",
1741	},
1742	{
1743		"anonymous field with tag",
1744		&N3{
1745			C3: C3{C: "s"},
1746		},
1747		&PropertyList{
1748			Property{Name: "red", Value: &Entity{
1749				Properties: []Property{
1750					{Name: "C", Value: "s", NoIndex: false},
1751				},
1752			}, NoIndex: false},
1753		},
1754		"",
1755		"",
1756	},
1757	{
1758		"unexported anonymous field",
1759		&N4{
1760			c4: c4{C: "s"},
1761		},
1762		&PropertyList{
1763			Property{Name: "C", Value: "s", NoIndex: false},
1764		},
1765		"",
1766		"",
1767	},
1768	{
1769		"unexported anonymous field with tag",
1770		&N5{
1771			c4: c4{C: "s"},
1772		},
1773		new(PropertyList),
1774		"",
1775		"",
1776	},
1777	{
1778		"save props load structs with ragged fields",
1779		&PropertyList{
1780			Property{Name: "red.S", Value: "rot", NoIndex: false},
1781			Property{Name: "green.Nonymous.I", Value: []interface{}{int64(10), int64(11), int64(12), int64(13)}, NoIndex: false},
1782			Property{Name: "Blue.Nonymous.I", Value: []interface{}{int64(20), int64(21)}, NoIndex: false},
1783			Property{Name: "Blue.Nonymous.S", Value: []interface{}{"blau0", "blau1", "blau2"}, NoIndex: false},
1784		},
1785		&N2{
1786			N1: N1{
1787				X0: X0{S: "rot"},
1788			},
1789			Green: N1{
1790				Nonymous: []X0{
1791					{I: 10},
1792					{I: 11},
1793					{I: 12},
1794					{I: 13},
1795				},
1796			},
1797			Blue: N1{
1798				Nonymous: []X0{
1799					{S: "blau0", I: 20},
1800					{S: "blau1", I: 21},
1801					{S: "blau2"},
1802				},
1803			},
1804		},
1805		"",
1806		"",
1807	},
1808	{
1809		"save structs with noindex tags",
1810		&struct {
1811			A struct {
1812				X string `datastore:",noindex"`
1813				Y string
1814			} `datastore:",noindex"`
1815			B struct {
1816				X string `datastore:",noindex"`
1817				Y string
1818			}
1819		}{},
1820		&PropertyList{
1821			Property{Name: "A", Value: &Entity{
1822				Properties: []Property{
1823					{Name: "X", Value: "", NoIndex: true},
1824					{Name: "Y", Value: "", NoIndex: true},
1825				},
1826			}, NoIndex: true},
1827			Property{Name: "B", Value: &Entity{
1828				Properties: []Property{
1829					{Name: "X", Value: "", NoIndex: true},
1830					{Name: "Y", Value: "", NoIndex: false},
1831				},
1832			}, NoIndex: false},
1833		},
1834		"",
1835		"",
1836	},
1837	{
1838		"embedded struct with name override",
1839		&struct {
1840			Inner1 `datastore:"foo"`
1841		}{},
1842		&PropertyList{
1843			Property{Name: "foo", Value: &Entity{
1844				Properties: []Property{
1845					{Name: "W", Value: int64(0), NoIndex: false},
1846					{Name: "X", Value: "", NoIndex: false},
1847				},
1848			}, NoIndex: false},
1849		},
1850		"",
1851		"",
1852	},
1853	{
1854		"slice of slices",
1855		&SliceOfSlices{},
1856		nil,
1857		"flattening nested structs leads to a slice of slices",
1858		"",
1859	},
1860	{
1861		"recursive struct",
1862		&Recursive{},
1863		&Recursive{},
1864		"",
1865		"",
1866	},
1867	{
1868		"mutually recursive struct",
1869		&MutuallyRecursive0{},
1870		&MutuallyRecursive0{},
1871		"",
1872		"",
1873	},
1874	{
1875		"non-exported struct fields",
1876		&struct {
1877			i, J int64
1878		}{i: 1, J: 2},
1879		&PropertyList{
1880			Property{Name: "J", Value: int64(2), NoIndex: false},
1881		},
1882		"",
1883		"",
1884	},
1885	{
1886		"json.RawMessage",
1887		&struct {
1888			J json.RawMessage
1889		}{
1890			J: json.RawMessage("rawr"),
1891		},
1892		&PropertyList{
1893			Property{Name: "J", Value: []byte("rawr"), NoIndex: false},
1894		},
1895		"",
1896		"",
1897	},
1898	{
1899		"json.RawMessage to myBlob",
1900		&struct {
1901			B json.RawMessage
1902		}{
1903			B: json.RawMessage("rawr"),
1904		},
1905		&B2{B: myBlob("rawr")},
1906		"",
1907		"",
1908	},
1909	{
1910		"repeated property names",
1911		&PropertyList{
1912			Property{Name: "A", Value: ""},
1913			Property{Name: "A", Value: ""},
1914		},
1915		nil,
1916		"duplicate Property",
1917		"",
1918	},
1919	{
1920		"embedded time field",
1921		&SpecialTime{MyTime: EmbeddedTime{ts}},
1922		&SpecialTime{MyTime: EmbeddedTime{ts}},
1923		"",
1924		"",
1925	},
1926	{
1927		"embedded time load",
1928		&PropertyList{
1929			Property{Name: "MyTime.Time", Value: ts},
1930		},
1931		&SpecialTime{MyTime: EmbeddedTime{ts}},
1932		"",
1933		"",
1934	},
1935	{
1936		"pointer fields: nil",
1937		&Pointers{},
1938		&Pointers{},
1939		"",
1940		"",
1941	},
1942	{
1943		"pointer fields: populated with zeroes",
1944		populatedPointers(),
1945		populatedPointers(),
1946		"",
1947		"",
1948	},
1949}
1950
1951// checkErr returns the empty string if either both want and err are zero,
1952// or if want is a non-empty substring of err's string representation.
1953func checkErr(want string, err error) string {
1954	if err != nil {
1955		got := err.Error()
1956		if want == "" || !strings.Contains(got, want) {
1957			return got
1958		}
1959	} else if want != "" {
1960		return fmt.Sprintf("want error %q", want)
1961	}
1962	return ""
1963}
1964
1965func TestRoundTrip(t *testing.T) {
1966	for _, tc := range testCases {
1967		p, err := saveEntity(testKey0, tc.src)
1968		if s := checkErr(tc.putErr, err); s != "" {
1969			t.Errorf("%s: save: %s", tc.desc, s)
1970			continue
1971		}
1972		if p == nil {
1973			continue
1974		}
1975		var got interface{}
1976		if _, ok := tc.want.(*PropertyList); ok {
1977			got = new(PropertyList)
1978		} else {
1979			got = reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
1980		}
1981		err = loadEntityProto(got, p)
1982		if s := checkErr(tc.getErr, err); s != "" {
1983			t.Errorf("%s: load: %s", tc.desc, s)
1984			continue
1985		}
1986		if pl, ok := got.(*PropertyList); ok {
1987			// Sort by name to make sure we have a deterministic order.
1988			sortPL(*pl)
1989		}
1990
1991		if !testutil.Equal(got, tc.want, cmp.AllowUnexported(X0{}, X2{})) {
1992			t.Errorf("%s: compare:\ngot:  %+#v\nwant: %+#v", tc.desc, got, tc.want)
1993			continue
1994		}
1995	}
1996}
1997
1998type aPtrPLS struct {
1999	Count int
2000}
2001
2002func (pls *aPtrPLS) Load([]Property) error {
2003	pls.Count++
2004	return nil
2005}
2006
2007func (pls *aPtrPLS) Save() ([]Property, error) {
2008	return []Property{{Name: "Count", Value: 4}}, nil
2009}
2010
2011type aValuePLS struct {
2012	Count int
2013}
2014
2015func (pls aValuePLS) Load([]Property) error {
2016	pls.Count += 2
2017	return nil
2018}
2019
2020func (pls aValuePLS) Save() ([]Property, error) {
2021	return []Property{{Name: "Count", Value: 8}}, nil
2022}
2023
2024type aValuePtrPLS struct {
2025	Count int
2026}
2027
2028func (pls *aValuePtrPLS) Load([]Property) error {
2029	pls.Count = 11
2030	return nil
2031}
2032
2033func (pls *aValuePtrPLS) Save() ([]Property, error) {
2034	return []Property{{Name: "Count", Value: 12}}, nil
2035}
2036
2037type aNotPLS struct {
2038	Count int
2039}
2040
2041type plsString string
2042
2043func (s *plsString) Load([]Property) error {
2044	*s = "LOADED"
2045	return nil
2046}
2047
2048func (s *plsString) Save() ([]Property, error) {
2049	return []Property{{Name: "SS", Value: "SAVED"}}, nil
2050}
2051
2052func ptrToplsString(s string) *plsString {
2053	plsStr := plsString(s)
2054	return &plsStr
2055}
2056
2057type aSubPLS struct {
2058	Foo string
2059	Bar *aPtrPLS
2060	Baz aValuePtrPLS
2061	S   plsString
2062}
2063
2064type aSubNotPLS struct {
2065	Foo string
2066	Bar *aNotPLS
2067}
2068
2069type aSubPLSErr struct {
2070	Foo string
2071	Bar aValuePLS
2072}
2073
2074type aSubPLSNoErr struct {
2075	Foo string
2076	Bar aPtrPLS
2077}
2078
2079type GrandparentFlatten struct {
2080	Parent Parent `datastore:",flatten"`
2081}
2082
2083type GrandparentOfPtrFlatten struct {
2084	Parent ParentOfPtr `datastore:",flatten"`
2085}
2086
2087type GrandparentOfSlice struct {
2088	Parent ParentOfSlice
2089}
2090
2091type GrandparentOfSlicePtrs struct {
2092	Parent ParentOfSlicePtrs
2093}
2094
2095type GrandparentOfSliceFlatten struct {
2096	Parent ParentOfSlice `datastore:",flatten"`
2097}
2098
2099type GrandparentOfSlicePtrsFlatten struct {
2100	Parent ParentOfSlicePtrs `datastore:",flatten"`
2101}
2102
2103type Grandparent struct {
2104	Parent Parent
2105}
2106
2107type Parent struct {
2108	Child  Child
2109	String plsString
2110}
2111
2112type ParentOfPtr struct {
2113	Child  *Child
2114	String *plsString
2115}
2116
2117type ParentOfSlice struct {
2118	Children []Child
2119	Strings  []plsString
2120}
2121
2122type ParentOfSlicePtrs struct {
2123	Children []*Child
2124	Strings  []*plsString
2125}
2126
2127type Child struct {
2128	I          int
2129	Grandchild Grandchild
2130}
2131
2132type Grandchild struct {
2133	S string
2134}
2135
2136func (c *Child) Load(props []Property) error {
2137	for _, p := range props {
2138		if p.Name == "I" {
2139			c.I++
2140		} else if p.Name == "Grandchild.S" {
2141			c.Grandchild.S = "grandchild loaded"
2142		}
2143	}
2144
2145	return nil
2146}
2147
2148func (c *Child) Save() ([]Property, error) {
2149	v := c.I + 1
2150	return []Property{
2151		{Name: "I", Value: v},
2152		{Name: "Grandchild.S", Value: fmt.Sprintf("grandchild saved %d", v)},
2153	}, nil
2154}
2155
2156func TestLoadSavePLS(t *testing.T) {
2157	type testCase struct {
2158		desc     string
2159		src      interface{}
2160		wantSave *pb.Entity
2161		wantLoad interface{}
2162		saveErr  string
2163		loadErr  string
2164	}
2165
2166	testCases := []testCase{
2167		{
2168			desc: "non-struct implements PLS (top-level)",
2169			src:  ptrToplsString("hello"),
2170			wantSave: &pb.Entity{
2171				Key: keyToProto(testKey0),
2172				Properties: map[string]*pb.Value{
2173					"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2174				},
2175			},
2176			wantLoad: ptrToplsString("LOADED"),
2177		},
2178		{
2179			desc: "substructs do implement PLS",
2180			src:  &aSubPLS{Foo: "foo", Bar: &aPtrPLS{Count: 2}, Baz: aValuePtrPLS{Count: 15}, S: "something"},
2181			wantSave: &pb.Entity{
2182				Key: keyToProto(testKey0),
2183				Properties: map[string]*pb.Value{
2184					"Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}},
2185					"Bar": {ValueType: &pb.Value_EntityValue{
2186						EntityValue: &pb.Entity{
2187							Properties: map[string]*pb.Value{
2188								"Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 4}},
2189							},
2190						},
2191					}},
2192					"Baz": {ValueType: &pb.Value_EntityValue{
2193						EntityValue: &pb.Entity{
2194							Properties: map[string]*pb.Value{
2195								"Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 12}},
2196							},
2197						},
2198					}},
2199					"S": {ValueType: &pb.Value_EntityValue{
2200						EntityValue: &pb.Entity{
2201							Properties: map[string]*pb.Value{
2202								"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2203							},
2204						},
2205					}},
2206				},
2207			},
2208			wantLoad: &aSubPLS{Foo: "foo", Bar: &aPtrPLS{Count: 1}, Baz: aValuePtrPLS{Count: 11}, S: "LOADED"},
2209		},
2210		{
2211			desc: "substruct (ptr) does implement PLS, nil valued substruct",
2212			src:  &aSubPLS{Foo: "foo", S: "something"},
2213			wantSave: &pb.Entity{
2214				Key: keyToProto(testKey0),
2215				Properties: map[string]*pb.Value{
2216					"Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}},
2217					"Baz": {ValueType: &pb.Value_EntityValue{
2218						EntityValue: &pb.Entity{
2219							Properties: map[string]*pb.Value{
2220								"Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 12}},
2221							},
2222						},
2223					}},
2224					"S": {ValueType: &pb.Value_EntityValue{
2225						EntityValue: &pb.Entity{
2226							Properties: map[string]*pb.Value{
2227								"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2228							},
2229						},
2230					}},
2231				},
2232			},
2233			wantLoad: &aSubPLS{Foo: "foo", Baz: aValuePtrPLS{Count: 11}, S: "LOADED"},
2234		},
2235		{
2236			desc: "substruct (ptr) does not implement PLS",
2237			src:  &aSubNotPLS{Foo: "foo", Bar: &aNotPLS{Count: 2}},
2238			wantSave: &pb.Entity{
2239				Key: keyToProto(testKey0),
2240				Properties: map[string]*pb.Value{
2241					"Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}},
2242					"Bar": {ValueType: &pb.Value_EntityValue{
2243						EntityValue: &pb.Entity{
2244							Properties: map[string]*pb.Value{
2245								"Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
2246							},
2247						},
2248					}},
2249				},
2250			},
2251			wantLoad: &aSubNotPLS{Foo: "foo", Bar: &aNotPLS{Count: 2}},
2252		},
2253		{
2254			desc:     "substruct (value) does implement PLS, error on save",
2255			src:      &aSubPLSErr{Foo: "foo", Bar: aValuePLS{Count: 2}},
2256			wantSave: (*pb.Entity)(nil),
2257			wantLoad: &aSubPLSErr{},
2258			saveErr:  "PropertyLoadSaver methods must be implemented on a pointer",
2259		},
2260		{
2261			desc: "substruct (value) does implement PLS, error on load",
2262			src:  &aSubPLSNoErr{Foo: "foo", Bar: aPtrPLS{Count: 2}},
2263			wantSave: &pb.Entity{
2264				Key: keyToProto(testKey0),
2265				Properties: map[string]*pb.Value{
2266					"Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}},
2267					"Bar": {ValueType: &pb.Value_EntityValue{
2268						EntityValue: &pb.Entity{
2269							Properties: map[string]*pb.Value{
2270								"Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 4}},
2271							},
2272						},
2273					}},
2274				},
2275			},
2276			wantLoad: &aSubPLSErr{},
2277			loadErr:  "PropertyLoadSaver methods must be implemented on a pointer",
2278		},
2279
2280		{
2281			desc: "parent does not have flatten option, child impl PLS",
2282			src: &Grandparent{
2283				Parent: Parent{
2284					Child: Child{
2285						I: 9,
2286						Grandchild: Grandchild{
2287							S: "BAD",
2288						},
2289					},
2290					String: plsString("something"),
2291				},
2292			},
2293			wantSave: &pb.Entity{
2294				Key: keyToProto(testKey0),
2295				Properties: map[string]*pb.Value{
2296					"Parent": {ValueType: &pb.Value_EntityValue{
2297						EntityValue: &pb.Entity{
2298							Properties: map[string]*pb.Value{
2299								"Child": {ValueType: &pb.Value_EntityValue{
2300									EntityValue: &pb.Entity{
2301										Properties: map[string]*pb.Value{
2302											"I":            {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}},
2303											"Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}},
2304										},
2305									},
2306								}},
2307								"String": {ValueType: &pb.Value_EntityValue{
2308									EntityValue: &pb.Entity{
2309										Properties: map[string]*pb.Value{
2310											"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2311										},
2312									},
2313								}},
2314							},
2315						},
2316					}},
2317				},
2318			},
2319			wantLoad: &Grandparent{
2320				Parent: Parent{
2321					Child: Child{
2322						I: 1,
2323						Grandchild: Grandchild{
2324							S: "grandchild loaded",
2325						},
2326					},
2327					String: "LOADED",
2328				},
2329			},
2330		},
2331		{
2332			desc: "parent has flatten option enabled, child impl PLS",
2333			src: &GrandparentFlatten{
2334				Parent: Parent{
2335					Child: Child{
2336						I: 7,
2337						Grandchild: Grandchild{
2338							S: "BAD",
2339						},
2340					},
2341					String: plsString("something"),
2342				},
2343			},
2344			wantSave: &pb.Entity{
2345				Key: keyToProto(testKey0),
2346				Properties: map[string]*pb.Value{
2347					"Parent.Child.I":            {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
2348					"Parent.Child.Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
2349					"Parent.String.SS":          {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2350				},
2351			},
2352			wantLoad: &GrandparentFlatten{
2353				Parent: Parent{
2354					Child: Child{
2355						I: 1,
2356						Grandchild: Grandchild{
2357							S: "grandchild loaded",
2358						},
2359					},
2360					String: "LOADED",
2361				},
2362			},
2363		},
2364
2365		{
2366			desc: "parent has flatten option enabled, child (ptr to) impl PLS",
2367			src: &GrandparentOfPtrFlatten{
2368				Parent: ParentOfPtr{
2369					Child: &Child{
2370						I: 7,
2371						Grandchild: Grandchild{
2372							S: "BAD",
2373						},
2374					},
2375					String: ptrToplsString("something"),
2376				},
2377			},
2378			wantSave: &pb.Entity{
2379				Key: keyToProto(testKey0),
2380				Properties: map[string]*pb.Value{
2381					"Parent.Child.I":            {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
2382					"Parent.Child.Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
2383					"Parent.String.SS":          {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2384				},
2385			},
2386			wantLoad: &GrandparentOfPtrFlatten{
2387				Parent: ParentOfPtr{
2388					Child: &Child{
2389						I: 1,
2390						Grandchild: Grandchild{
2391							S: "grandchild loaded",
2392						},
2393					},
2394					String: ptrToplsString("LOADED"),
2395				},
2396			},
2397		},
2398		{
2399			desc: "children (slice of) impl PLS",
2400			src: &GrandparentOfSlice{
2401				Parent: ParentOfSlice{
2402					Children: []Child{
2403						{
2404							I: 7,
2405							Grandchild: Grandchild{
2406								S: "BAD",
2407							},
2408						},
2409						{
2410							I: 9,
2411							Grandchild: Grandchild{
2412								S: "BAD2",
2413							},
2414						},
2415					},
2416					Strings: []plsString{
2417						"something1",
2418						"something2",
2419					},
2420				},
2421			},
2422			wantSave: &pb.Entity{
2423				Key: keyToProto(testKey0),
2424				Properties: map[string]*pb.Value{
2425					"Parent": {ValueType: &pb.Value_EntityValue{
2426						EntityValue: &pb.Entity{
2427							Properties: map[string]*pb.Value{
2428								"Children": {ValueType: &pb.Value_ArrayValue{
2429									ArrayValue: &pb.ArrayValue{Values: []*pb.Value{
2430										{ValueType: &pb.Value_EntityValue{
2431											EntityValue: &pb.Entity{
2432												Properties: map[string]*pb.Value{
2433													"I":            {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
2434													"Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
2435												},
2436											},
2437										}},
2438										{ValueType: &pb.Value_EntityValue{
2439											EntityValue: &pb.Entity{
2440												Properties: map[string]*pb.Value{
2441													"I":            {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}},
2442													"Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}},
2443												},
2444											},
2445										}},
2446									}},
2447								}},
2448								"Strings": {ValueType: &pb.Value_ArrayValue{
2449									ArrayValue: &pb.ArrayValue{Values: []*pb.Value{
2450										{ValueType: &pb.Value_EntityValue{
2451											EntityValue: &pb.Entity{
2452												Properties: map[string]*pb.Value{
2453													"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2454												},
2455											},
2456										}},
2457										{ValueType: &pb.Value_EntityValue{
2458											EntityValue: &pb.Entity{
2459												Properties: map[string]*pb.Value{
2460													"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2461												},
2462											},
2463										}},
2464									}},
2465								}},
2466							},
2467						},
2468					}},
2469				},
2470			},
2471			wantLoad: &GrandparentOfSlice{
2472				Parent: ParentOfSlice{
2473					Children: []Child{
2474						{
2475							I: 1,
2476							Grandchild: Grandchild{
2477								S: "grandchild loaded",
2478							},
2479						},
2480						{
2481							I: 1,
2482							Grandchild: Grandchild{
2483								S: "grandchild loaded",
2484							},
2485						},
2486					},
2487					Strings: []plsString{
2488						"LOADED",
2489						"LOADED",
2490					},
2491				},
2492			},
2493		},
2494		{
2495			desc: "children (slice of ptrs) impl PLS",
2496			src: &GrandparentOfSlicePtrs{
2497				Parent: ParentOfSlicePtrs{
2498					Children: []*Child{
2499						{
2500							I: 7,
2501							Grandchild: Grandchild{
2502								S: "BAD",
2503							},
2504						},
2505						{
2506							I: 9,
2507							Grandchild: Grandchild{
2508								S: "BAD2",
2509							},
2510						},
2511					},
2512					Strings: []*plsString{
2513						ptrToplsString("something1"),
2514						ptrToplsString("something2"),
2515					},
2516				},
2517			},
2518			wantSave: &pb.Entity{
2519				Key: keyToProto(testKey0),
2520				Properties: map[string]*pb.Value{
2521					"Parent": {ValueType: &pb.Value_EntityValue{
2522						EntityValue: &pb.Entity{
2523							Properties: map[string]*pb.Value{
2524								"Children": {ValueType: &pb.Value_ArrayValue{
2525									ArrayValue: &pb.ArrayValue{Values: []*pb.Value{
2526										{ValueType: &pb.Value_EntityValue{
2527											EntityValue: &pb.Entity{
2528												Properties: map[string]*pb.Value{
2529													"I":            {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
2530													"Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
2531												},
2532											},
2533										}},
2534										{ValueType: &pb.Value_EntityValue{
2535											EntityValue: &pb.Entity{
2536												Properties: map[string]*pb.Value{
2537													"I":            {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}},
2538													"Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}},
2539												},
2540											},
2541										}},
2542									}},
2543								}},
2544								"Strings": {ValueType: &pb.Value_ArrayValue{
2545									ArrayValue: &pb.ArrayValue{Values: []*pb.Value{
2546										{ValueType: &pb.Value_EntityValue{
2547											EntityValue: &pb.Entity{
2548												Properties: map[string]*pb.Value{
2549													"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2550												},
2551											},
2552										}},
2553										{ValueType: &pb.Value_EntityValue{
2554											EntityValue: &pb.Entity{
2555												Properties: map[string]*pb.Value{
2556													"SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2557												},
2558											},
2559										}},
2560									}},
2561								}},
2562							},
2563						},
2564					}},
2565				},
2566			},
2567			wantLoad: &GrandparentOfSlicePtrs{
2568				Parent: ParentOfSlicePtrs{
2569					Children: []*Child{
2570						{
2571							I: 1,
2572							Grandchild: Grandchild{
2573								S: "grandchild loaded",
2574							},
2575						},
2576						{
2577							I: 1,
2578							Grandchild: Grandchild{
2579								S: "grandchild loaded",
2580							},
2581						},
2582					},
2583					Strings: []*plsString{
2584						ptrToplsString("LOADED"),
2585						ptrToplsString("LOADED"),
2586					},
2587				},
2588			},
2589		},
2590		{
2591			desc: "parent has flatten option, children (slice of) impl PLS",
2592			src: &GrandparentOfSliceFlatten{
2593				Parent: ParentOfSlice{
2594					Children: []Child{
2595						{
2596							I: 7,
2597							Grandchild: Grandchild{
2598								S: "BAD",
2599							},
2600						},
2601						{
2602							I: 9,
2603							Grandchild: Grandchild{
2604								S: "BAD2",
2605							},
2606						},
2607					},
2608					Strings: []plsString{
2609						"something1",
2610						"something2",
2611					},
2612				},
2613			},
2614			wantSave: &pb.Entity{
2615				Key: keyToProto(testKey0),
2616				Properties: map[string]*pb.Value{
2617					"Parent.Children.I": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
2618						Values: []*pb.Value{
2619							{ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
2620							{ValueType: &pb.Value_IntegerValue{IntegerValue: 10}},
2621						},
2622					},
2623					}},
2624					"Parent.Children.Grandchild.S": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
2625						Values: []*pb.Value{
2626							{ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
2627							{ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}},
2628						},
2629					},
2630					}},
2631					"Parent.Strings.SS": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
2632						Values: []*pb.Value{
2633							{ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2634							{ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2635						},
2636					},
2637					}},
2638				},
2639			},
2640			wantLoad: &GrandparentOfSliceFlatten{
2641				Parent: ParentOfSlice{
2642					Children: []Child{
2643						{
2644							I: 1,
2645							Grandchild: Grandchild{
2646								S: "grandchild loaded",
2647							},
2648						},
2649						{
2650							I: 1,
2651							Grandchild: Grandchild{
2652								S: "grandchild loaded",
2653							},
2654						},
2655					},
2656					Strings: []plsString{
2657						"LOADED",
2658						"LOADED",
2659					},
2660				},
2661			},
2662		},
2663		{
2664			desc: "parent has flatten option, children (slice of ptrs) impl PLS",
2665			src: &GrandparentOfSlicePtrsFlatten{
2666				Parent: ParentOfSlicePtrs{
2667					Children: []*Child{
2668						{
2669							I: 7,
2670							Grandchild: Grandchild{
2671								S: "BAD",
2672							},
2673						},
2674						{
2675							I: 9,
2676							Grandchild: Grandchild{
2677								S: "BAD2",
2678							},
2679						},
2680					},
2681					Strings: []*plsString{
2682						ptrToplsString("something1"),
2683						ptrToplsString("something1"),
2684					},
2685				},
2686			},
2687			wantSave: &pb.Entity{
2688				Key: keyToProto(testKey0),
2689				Properties: map[string]*pb.Value{
2690					"Parent.Children.I": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
2691						Values: []*pb.Value{
2692							{ValueType: &pb.Value_IntegerValue{IntegerValue: 8}},
2693							{ValueType: &pb.Value_IntegerValue{IntegerValue: 10}},
2694						},
2695					},
2696					}},
2697					"Parent.Children.Grandchild.S": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
2698						Values: []*pb.Value{
2699							{ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}},
2700							{ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}},
2701						},
2702					},
2703					}},
2704					"Parent.Strings.SS": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{
2705						Values: []*pb.Value{
2706							{ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2707							{ValueType: &pb.Value_StringValue{StringValue: "SAVED"}},
2708						},
2709					},
2710					}},
2711				},
2712			},
2713			wantLoad: &GrandparentOfSlicePtrsFlatten{
2714				Parent: ParentOfSlicePtrs{
2715					Children: []*Child{
2716						{
2717							I: 1,
2718							Grandchild: Grandchild{
2719								S: "grandchild loaded",
2720							},
2721						},
2722						{
2723							I: 1,
2724							Grandchild: Grandchild{
2725								S: "grandchild loaded",
2726							},
2727						},
2728					},
2729					Strings: []*plsString{
2730						ptrToplsString("LOADED"),
2731						ptrToplsString("LOADED"),
2732					},
2733				},
2734			},
2735		},
2736	}
2737
2738	for _, tc := range testCases {
2739		e, err := saveEntity(testKey0, tc.src)
2740		if tc.saveErr == "" { // Want no error.
2741			if err != nil {
2742				t.Errorf("%s: save: %v", tc.desc, err)
2743				continue
2744			}
2745			if !testutil.Equal(e, tc.wantSave) {
2746				t.Errorf("%s: save: \ngot:  %+v\nwant: %+v", tc.desc, e, tc.wantSave)
2747				continue
2748			}
2749		} else { // Want error.
2750			if err == nil {
2751				t.Errorf("%s: save: want err", tc.desc)
2752				continue
2753			}
2754			if !strings.Contains(err.Error(), tc.saveErr) {
2755				t.Errorf("%s: save: \ngot err  '%s'\nwant err '%s'", tc.desc, err.Error(), tc.saveErr)
2756			}
2757			continue
2758		}
2759
2760		gota := reflect.New(reflect.TypeOf(tc.wantLoad).Elem()).Interface()
2761		err = loadEntityProto(gota, e)
2762		if tc.loadErr == "" { // Want no error.
2763			if err != nil {
2764				t.Errorf("%s: load: %v", tc.desc, err)
2765				continue
2766			}
2767			if !testutil.Equal(gota, tc.wantLoad) {
2768				t.Errorf("%s: load: \ngot:  %+v\nwant: %+v", tc.desc, gota, tc.wantLoad)
2769				continue
2770			}
2771		} else { // Want error.
2772			if err == nil {
2773				t.Errorf("%s: load: want err", tc.desc)
2774				continue
2775			}
2776			if !strings.Contains(err.Error(), tc.loadErr) {
2777				t.Errorf("%s: load: \ngot err  '%s'\nwant err '%s'", tc.desc, err.Error(), tc.loadErr)
2778			}
2779		}
2780	}
2781}
2782
2783func TestQueryConstruction(t *testing.T) {
2784	tests := []struct {
2785		q, exp *Query
2786		err    string
2787	}{
2788		{
2789			q: NewQuery("Foo"),
2790			exp: &Query{
2791				kind:  "Foo",
2792				limit: -1,
2793			},
2794		},
2795		{
2796			// Regular filtered query with standard spacing.
2797			q: NewQuery("Foo").Filter("foo >", 7),
2798			exp: &Query{
2799				kind: "Foo",
2800				filter: []filter{
2801					{
2802						FieldName: "foo",
2803						Op:        greaterThan,
2804						Value:     7,
2805					},
2806				},
2807				limit: -1,
2808			},
2809		},
2810		{
2811			// Filtered query with no spacing.
2812			q: NewQuery("Foo").Filter("foo=", 6),
2813			exp: &Query{
2814				kind: "Foo",
2815				filter: []filter{
2816					{
2817						FieldName: "foo",
2818						Op:        equal,
2819						Value:     6,
2820					},
2821				},
2822				limit: -1,
2823			},
2824		},
2825		{
2826			// Filtered query with funky spacing.
2827			q: NewQuery("Foo").Filter(" foo< ", 8),
2828			exp: &Query{
2829				kind: "Foo",
2830				filter: []filter{
2831					{
2832						FieldName: "foo",
2833						Op:        lessThan,
2834						Value:     8,
2835					},
2836				},
2837				limit: -1,
2838			},
2839		},
2840		{
2841			// Filtered query with multicharacter op.
2842			q: NewQuery("Foo").Filter("foo >=", 9),
2843			exp: &Query{
2844				kind: "Foo",
2845				filter: []filter{
2846					{
2847						FieldName: "foo",
2848						Op:        greaterEq,
2849						Value:     9,
2850					},
2851				},
2852				limit: -1,
2853			},
2854		},
2855		{
2856			// Query with ordering.
2857			q: NewQuery("Foo").Order("bar"),
2858			exp: &Query{
2859				kind: "Foo",
2860				order: []order{
2861					{
2862						FieldName: "bar",
2863						Direction: ascending,
2864					},
2865				},
2866				limit: -1,
2867			},
2868		},
2869		{
2870			// Query with reverse ordering, and funky spacing.
2871			q: NewQuery("Foo").Order(" - bar"),
2872			exp: &Query{
2873				kind: "Foo",
2874				order: []order{
2875					{
2876						FieldName: "bar",
2877						Direction: descending,
2878					},
2879				},
2880				limit: -1,
2881			},
2882		},
2883		{
2884			// Query with an empty ordering.
2885			q:   NewQuery("Foo").Order(""),
2886			err: "empty order",
2887		},
2888		{
2889			// Query with a + ordering.
2890			q:   NewQuery("Foo").Order("+bar"),
2891			err: "invalid order",
2892		},
2893	}
2894	for i, test := range tests {
2895		if test.q.err != nil {
2896			got := test.q.err.Error()
2897			if !strings.Contains(got, test.err) {
2898				t.Errorf("%d: error mismatch: got %q want something containing %q", i, got, test.err)
2899			}
2900			continue
2901		}
2902		if !testutil.Equal(test.q, test.exp, cmp.AllowUnexported(Query{})) {
2903			t.Errorf("%d: mismatch: got %v want %v", i, test.q, test.exp)
2904		}
2905	}
2906}
2907
2908func TestPutMultiTypes(t *testing.T) {
2909	ctx := context.Background()
2910	type S struct {
2911		A int
2912		B string
2913	}
2914
2915	testCases := []struct {
2916		desc    string
2917		src     interface{}
2918		wantErr bool
2919	}{
2920		// Test cases to check each of the valid input types for src.
2921		// Each case has the same elements.
2922		{
2923			desc: "type []struct",
2924			src: []S{
2925				{1, "one"}, {2, "two"},
2926			},
2927		},
2928		{
2929			desc: "type []*struct",
2930			src: []*S{
2931				{1, "one"}, {2, "two"},
2932			},
2933		},
2934		{
2935			desc: "type []interface{} with PLS elems",
2936			src: []interface{}{
2937				&PropertyList{Property{Name: "A", Value: 1}, Property{Name: "B", Value: "one"}},
2938				&PropertyList{Property{Name: "A", Value: 2}, Property{Name: "B", Value: "two"}},
2939			},
2940		},
2941		{
2942			desc: "type []interface{} with struct ptr elems",
2943			src: []interface{}{
2944				&S{1, "one"}, &S{2, "two"},
2945			},
2946		},
2947		{
2948			desc: "type []PropertyLoadSaver{}",
2949			src: []PropertyLoadSaver{
2950				&PropertyList{Property{Name: "A", Value: 1}, Property{Name: "B", Value: "one"}},
2951				&PropertyList{Property{Name: "A", Value: 2}, Property{Name: "B", Value: "two"}},
2952			},
2953		},
2954		{
2955			desc: "type []P (non-pointer, *P implements PropertyLoadSaver)",
2956			src: []PropertyList{
2957				{Property{Name: "A", Value: 1}, Property{Name: "B", Value: "one"}},
2958				{Property{Name: "A", Value: 2}, Property{Name: "B", Value: "two"}},
2959			},
2960		},
2961		// Test some invalid cases.
2962		{
2963			desc: "type []interface{} with struct elems",
2964			src: []interface{}{
2965				S{1, "one"}, S{2, "two"},
2966			},
2967			wantErr: true,
2968		},
2969		{
2970			desc: "PropertyList",
2971			src: PropertyList{
2972				Property{Name: "A", Value: 1},
2973				Property{Name: "B", Value: "one"},
2974			},
2975			wantErr: true,
2976		},
2977		{
2978			desc:    "type []int",
2979			src:     []int{1, 2},
2980			wantErr: true,
2981		},
2982		{
2983			desc:    "not a slice",
2984			src:     S{1, "one"},
2985			wantErr: true,
2986		},
2987	}
2988
2989	// Use the same keys and expected entities for all tests.
2990	keys := []*Key{
2991		NameKey("testKind", "first", nil),
2992		NameKey("testKind", "second", nil),
2993	}
2994	want := []*pb.Mutation{
2995		{Operation: &pb.Mutation_Upsert{
2996			Upsert: &pb.Entity{
2997				Key: keyToProto(keys[0]),
2998				Properties: map[string]*pb.Value{
2999					"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}},
3000					"B": {ValueType: &pb.Value_StringValue{StringValue: "one"}},
3001				},
3002			}}},
3003		{Operation: &pb.Mutation_Upsert{
3004			Upsert: &pb.Entity{
3005				Key: keyToProto(keys[1]),
3006				Properties: map[string]*pb.Value{
3007					"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
3008					"B": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
3009				},
3010			}}},
3011	}
3012
3013	for _, tt := range testCases {
3014		// Set up a fake client which captures upserts.
3015		var got []*pb.Mutation
3016		client := &Client{
3017			client: &fakeClient{
3018				commitFn: func(req *pb.CommitRequest) (*pb.CommitResponse, error) {
3019					got = req.Mutations
3020					return &pb.CommitResponse{}, nil
3021				},
3022			},
3023		}
3024
3025		_, err := client.PutMulti(ctx, keys, tt.src)
3026		if err != nil {
3027			if !tt.wantErr {
3028				t.Errorf("%s: error %v", tt.desc, err)
3029			}
3030			continue
3031		}
3032		if tt.wantErr {
3033			t.Errorf("%s: wanted error, but none returned", tt.desc)
3034			continue
3035		}
3036		if len(got) != len(want) {
3037			t.Errorf("%s: got %d entities, want %d", tt.desc, len(got), len(want))
3038			continue
3039		}
3040		for i, e := range got {
3041			if !proto.Equal(e, want[i]) {
3042				t.Logf("%s: entity %d doesn't match\ngot:  %v\nwant: %v", tt.desc, i, e, want[i])
3043			}
3044		}
3045	}
3046}
3047
3048func TestNoIndexOnSliceProperties(t *testing.T) {
3049	// Check that ExcludeFromIndexes is set on the inner elements,
3050	// rather than the top-level ArrayValue value.
3051	pl := PropertyList{
3052		Property{
3053			Name: "repeated",
3054			Value: []interface{}{
3055				123,
3056				false,
3057				"short",
3058				strings.Repeat("a", 1503),
3059			},
3060			NoIndex: true,
3061		},
3062	}
3063	key := NameKey("dummy", "dummy", nil)
3064
3065	entity, err := saveEntity(key, &pl)
3066	if err != nil {
3067		t.Fatalf("saveEntity: %v", err)
3068	}
3069
3070	want := &pb.Value{
3071		ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{Values: []*pb.Value{
3072			{ValueType: &pb.Value_IntegerValue{IntegerValue: 123}, ExcludeFromIndexes: true},
3073			{ValueType: &pb.Value_BooleanValue{BooleanValue: false}, ExcludeFromIndexes: true},
3074			{ValueType: &pb.Value_StringValue{StringValue: "short"}, ExcludeFromIndexes: true},
3075			{ValueType: &pb.Value_StringValue{StringValue: strings.Repeat("a", 1503)}, ExcludeFromIndexes: true},
3076		}}},
3077	}
3078	if got := entity.Properties["repeated"]; !proto.Equal(got, want) {
3079		t.Errorf("Entity proto differs\ngot:  %v\nwant: %v", got, want)
3080	}
3081}
3082
3083type byName PropertyList
3084
3085func (s byName) Len() int           { return len(s) }
3086func (s byName) Less(i, j int) bool { return s[i].Name < s[j].Name }
3087func (s byName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
3088
3089// sortPL sorts the property list by property name, and
3090// recursively sorts any nested property lists, or nested slices of
3091// property lists.
3092func sortPL(pl PropertyList) {
3093	sort.Stable(byName(pl))
3094	for _, p := range pl {
3095		switch p.Value.(type) {
3096		case *Entity:
3097			sortPL(p.Value.(*Entity).Properties)
3098		case []interface{}:
3099			for _, p2 := range p.Value.([]interface{}) {
3100				if nent, ok := p2.(*Entity); ok {
3101					sortPL(nent.Properties)
3102				}
3103			}
3104		}
3105	}
3106}
3107
3108func TestValidGeoPoint(t *testing.T) {
3109	testCases := []struct {
3110		desc string
3111		pt   GeoPoint
3112		want bool
3113	}{
3114		{
3115			"valid",
3116			GeoPoint{67.21, 13.37},
3117			true,
3118		},
3119		{
3120			"high lat",
3121			GeoPoint{-90.01, 13.37},
3122			false,
3123		},
3124		{
3125			"low lat",
3126			GeoPoint{90.01, 13.37},
3127			false,
3128		},
3129		{
3130			"high lng",
3131			GeoPoint{67.21, 182},
3132			false,
3133		},
3134		{
3135			"low lng",
3136			GeoPoint{67.21, -181},
3137			false,
3138		},
3139	}
3140
3141	for _, tc := range testCases {
3142		if got := tc.pt.Valid(); got != tc.want {
3143			t.Errorf("%s: got %v, want %v", tc.desc, got, tc.want)
3144		}
3145	}
3146}
3147
3148func TestPutInvalidEntity(t *testing.T) {
3149	// Test that trying to put an invalid entity always returns the correct error
3150	// type.
3151
3152	// Fake client that can pretend to start a transaction.
3153	fakeClient := &fakeDatastoreClient{
3154		beginTransaction: func(*pb.BeginTransactionRequest) (*pb.BeginTransactionResponse, error) {
3155			return &pb.BeginTransactionResponse{
3156				Transaction: []byte("deadbeef"),
3157			}, nil
3158		},
3159	}
3160	client := &Client{
3161		client: fakeClient,
3162	}
3163
3164	ctx := context.Background()
3165	key := IncompleteKey("kind", nil)
3166
3167	_, err := client.Put(ctx, key, "invalid entity")
3168	if err != ErrInvalidEntityType {
3169		t.Errorf("client.Put returned err %v, want %v", err, ErrInvalidEntityType)
3170	}
3171
3172	_, err = client.PutMulti(ctx, []*Key{key}, []interface{}{"invalid entity"})
3173	if me, ok := err.(MultiError); !ok {
3174		t.Errorf("client.PutMulti returned err %v, want MultiError type", err)
3175	} else if len(me) != 1 || me[0] != ErrInvalidEntityType {
3176		t.Errorf("client.PutMulti returned err %v, want MulitError{ErrInvalidEntityType}", err)
3177	}
3178
3179	client.RunInTransaction(ctx, func(tx *Transaction) error {
3180		_, err := tx.Put(key, "invalid entity")
3181		if err != ErrInvalidEntityType {
3182			t.Errorf("tx.Put returned err %v, want %v", err, ErrInvalidEntityType)
3183		}
3184
3185		_, err = tx.PutMulti([]*Key{key}, []interface{}{"invalid entity"})
3186		if me, ok := err.(MultiError); !ok {
3187			t.Errorf("tx.PutMulti returned err %v, want MultiError type", err)
3188		} else if len(me) != 1 || me[0] != ErrInvalidEntityType {
3189			t.Errorf("tx.PutMulti returned err %v, want MulitError{ErrInvalidEntityType}", err)
3190		}
3191
3192		return errors.New("bang") // Return error: we don't actually want to commit.
3193	})
3194}
3195
3196func TestDeferred(t *testing.T) {
3197	type Ent struct {
3198		A int
3199		B string
3200	}
3201
3202	keys := []*Key{
3203		NameKey("testKind", "first", nil),
3204		NameKey("testKind", "second", nil),
3205	}
3206
3207	entity1 := &pb.Entity{
3208		Key: keyToProto(keys[0]),
3209		Properties: map[string]*pb.Value{
3210			"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}},
3211			"B": {ValueType: &pb.Value_StringValue{StringValue: "one"}},
3212		},
3213	}
3214	entity2 := &pb.Entity{
3215		Key: keyToProto(keys[1]),
3216		Properties: map[string]*pb.Value{
3217			"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
3218			"B": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
3219		},
3220	}
3221
3222	// count keeps track of the number of times fakeClient.lookup has been
3223	// called.
3224	var count int
3225	// Fake client that will return Deferred keys in resp on the first call.
3226	fakeClient := &fakeDatastoreClient{
3227		lookup: func(*pb.LookupRequest) (*pb.LookupResponse, error) {
3228			count++
3229			// On the first call, we return deferred keys.
3230			if count == 1 {
3231				return &pb.LookupResponse{
3232					Found: []*pb.EntityResult{
3233						{
3234							Entity:  entity1,
3235							Version: 1,
3236						},
3237					},
3238					Deferred: []*pb.Key{
3239						keyToProto(keys[1]),
3240					},
3241				}, nil
3242			}
3243
3244			// On the second call, we do not return any more deferred keys.
3245			return &pb.LookupResponse{
3246				Found: []*pb.EntityResult{
3247					{
3248						Entity:  entity2,
3249						Version: 1,
3250					},
3251				},
3252			}, nil
3253		},
3254	}
3255	client := &Client{
3256		client: fakeClient,
3257	}
3258
3259	ctx := context.Background()
3260
3261	dst := make([]Ent, len(keys))
3262	err := client.GetMulti(ctx, keys, dst)
3263	if err != nil {
3264		t.Fatalf("client.Get: %v", err)
3265	}
3266
3267	if count != 2 {
3268		t.Fatalf("expected client.lookup to be called 2 times. Got %d", count)
3269	}
3270
3271	if len(dst) != 2 {
3272		t.Fatalf("expected 2 entities returned, got %d", len(dst))
3273	}
3274
3275	for _, e := range dst {
3276		if e.A == 1 {
3277			if e.B != "one" {
3278				t.Fatalf("unexpected entity %+v", e)
3279			}
3280		} else if e.A == 2 {
3281			if e.B != "two" {
3282				t.Fatalf("unexpected entity %+v", e)
3283			}
3284		} else {
3285			t.Fatalf("unexpected entity %+v", e)
3286		}
3287	}
3288
3289}
3290
3291type KeyLoaderEnt struct {
3292	A int
3293	K *Key
3294}
3295
3296func (e *KeyLoaderEnt) Load(p []Property) error {
3297	e.A = 2
3298	return nil
3299}
3300
3301func (e *KeyLoaderEnt) LoadKey(k *Key) error {
3302	e.K = k
3303	return nil
3304}
3305
3306func (e *KeyLoaderEnt) Save() ([]Property, error) {
3307	return []Property{{Name: "A", Value: int64(3)}}, nil
3308}
3309
3310func TestKeyLoaderEndToEnd(t *testing.T) {
3311	keys := []*Key{
3312		NameKey("testKind", "first", nil),
3313		NameKey("testKind", "second", nil),
3314	}
3315
3316	entity1 := &pb.Entity{
3317		Key: keyToProto(keys[0]),
3318		Properties: map[string]*pb.Value{
3319			"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}},
3320			"B": {ValueType: &pb.Value_StringValue{StringValue: "one"}},
3321		},
3322	}
3323	entity2 := &pb.Entity{
3324		Key: keyToProto(keys[1]),
3325		Properties: map[string]*pb.Value{
3326			"A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
3327			"B": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
3328		},
3329	}
3330
3331	fakeClient := &fakeDatastoreClient{
3332		lookup: func(*pb.LookupRequest) (*pb.LookupResponse, error) {
3333			return &pb.LookupResponse{
3334				Found: []*pb.EntityResult{
3335					{
3336						Entity:  entity1,
3337						Version: 1,
3338					},
3339					{
3340						Entity:  entity2,
3341						Version: 1,
3342					},
3343				},
3344			}, nil
3345		},
3346	}
3347	client := &Client{
3348		client: fakeClient,
3349	}
3350
3351	ctx := context.Background()
3352
3353	dst := make([]*KeyLoaderEnt, len(keys))
3354	err := client.GetMulti(ctx, keys, dst)
3355	if err != nil {
3356		t.Fatalf("client.Get: %v", err)
3357	}
3358
3359	for i := range dst {
3360		if !testutil.Equal(dst[i].K, keys[i]) {
3361			t.Fatalf("unexpected entity %d to have key %+v, got %+v", i, keys[i], dst[i].K)
3362		}
3363	}
3364}
3365
3366func TestDeferredMissing(t *testing.T) {
3367	type Ent struct {
3368		A int
3369		B string
3370	}
3371
3372	keys := []*Key{
3373		NameKey("testKind", "first", nil),
3374		NameKey("testKind", "second", nil),
3375	}
3376
3377	entity1 := &pb.Entity{
3378		Key: keyToProto(keys[0]),
3379	}
3380	entity2 := &pb.Entity{
3381		Key: keyToProto(keys[1]),
3382	}
3383
3384	var count int
3385	fakeClient := &fakeDatastoreClient{
3386		lookup: func(*pb.LookupRequest) (*pb.LookupResponse, error) {
3387			count++
3388
3389			if count == 1 {
3390				return &pb.LookupResponse{
3391					Missing: []*pb.EntityResult{
3392						{
3393							Entity:  entity1,
3394							Version: 1,
3395						},
3396					},
3397					Deferred: []*pb.Key{
3398						keyToProto(keys[1]),
3399					},
3400				}, nil
3401			}
3402
3403			return &pb.LookupResponse{
3404				Missing: []*pb.EntityResult{
3405					{
3406						Entity:  entity2,
3407						Version: 1,
3408					},
3409				},
3410			}, nil
3411		},
3412	}
3413	client := &Client{
3414		client: fakeClient,
3415	}
3416
3417	ctx := context.Background()
3418
3419	dst := make([]Ent, len(keys))
3420	err := client.GetMulti(ctx, keys, dst)
3421	errs, ok := err.(MultiError)
3422	if !ok {
3423		t.Fatalf("expected error returns to be MultiError; got %v", err)
3424	}
3425	if len(errs) != 2 {
3426		t.Fatalf("expected 2 errors returns, got %d", len(errs))
3427	}
3428	if errs[0] != ErrNoSuchEntity {
3429		t.Fatalf("expected error to be ErrNoSuchEntity; got %v", errs[0])
3430	}
3431	if errs[1] != ErrNoSuchEntity {
3432		t.Fatalf("expected error to be ErrNoSuchEntity; got %v", errs[1])
3433	}
3434
3435	if count != 2 {
3436		t.Fatalf("expected client.lookup to be called 2 times. Got %d", count)
3437	}
3438
3439	if len(dst) != 2 {
3440		t.Fatalf("expected 2 entities returned, got %d", len(dst))
3441	}
3442
3443	for _, e := range dst {
3444		if e.A != 0 || e.B != "" {
3445			t.Fatalf("unexpected entity %+v", e)
3446		}
3447	}
3448}
3449
3450func TestGetWithNilKey(t *testing.T) {
3451	client := &Client{}
3452	err := client.Get(context.Background(), nil, []Property{})
3453	if err != ErrInvalidKey {
3454		t.Fatalf("want ErrInvalidKey, got %v", err)
3455	}
3456}
3457
3458func TestGetMultiWithNilKey(t *testing.T) {
3459	client := &Client{}
3460	dest := make([]PropertyList, 1)
3461	err := client.GetMulti(context.Background(), []*Key{nil}, dest)
3462	if me, ok := err.(MultiError); !ok {
3463		t.Fatalf("want MultiError, got %v", err)
3464	} else if len(me) != 1 || me[0] != ErrInvalidKey {
3465		t.Fatalf("want MultiError{ErrInvalidKey}, got %v", me)
3466	}
3467}
3468
3469func TestGetWithIncompleteKey(t *testing.T) {
3470	client := &Client{}
3471	err := client.Get(context.Background(), &Key{Kind: "testKind"}, []Property{})
3472	if err == nil {
3473		t.Fatalf("want err, got nil")
3474	}
3475}
3476
3477func TestGetMultiWithIncompleteKey(t *testing.T) {
3478	client := &Client{}
3479	dest := make([]PropertyList, 1)
3480	err := client.GetMulti(context.Background(), []*Key{{Kind: "testKind"}}, dest)
3481	if me, ok := err.(MultiError); !ok {
3482		t.Fatalf("want MultiError, got %v", err)
3483	} else if len(me) != 1 || me[0] == nil {
3484		t.Fatalf("want MultiError{err}, got %v", me)
3485	}
3486}
3487
3488func TestDeleteWithNilKey(t *testing.T) {
3489	client := &Client{}
3490	err := client.Delete(context.Background(), nil)
3491	if err != ErrInvalidKey {
3492		t.Fatalf("want ErrInvalidKey, got %v", err)
3493	}
3494}
3495
3496func TestDeleteMultiWithNilKey(t *testing.T) {
3497	client := &Client{}
3498	err := client.DeleteMulti(context.Background(), []*Key{nil})
3499	if me, ok := err.(MultiError); !ok {
3500		t.Fatalf("want MultiError, got %v", err)
3501	} else if len(me) != 1 || me[0] != ErrInvalidKey {
3502		t.Fatalf("want MultiError{ErrInvalidKey}, got %v", me)
3503	}
3504}
3505
3506func TestDeleteWithIncompleteKey(t *testing.T) {
3507	client := &Client{}
3508	err := client.Delete(context.Background(), &Key{Kind: "testKind"})
3509	if err == nil {
3510		t.Fatalf("want err, got nil")
3511	}
3512}
3513
3514func TestDeleteMultiWithIncompleteKey(t *testing.T) {
3515	client := &Client{}
3516	err := client.DeleteMulti(context.Background(), []*Key{{Kind: "testKind"}})
3517	if me, ok := err.(MultiError); !ok {
3518		t.Fatalf("want MultiError, got %v", err)
3519	} else if len(me) != 1 || me[0] == nil {
3520		t.Fatalf("want MultiError{err}, got %v", me)
3521	}
3522}
3523
3524type fakeDatastoreClient struct {
3525	pb.DatastoreClient
3526
3527	// Optional handlers for the datastore methods.
3528	// Any handlers left undefined will return an error.
3529	lookup           func(*pb.LookupRequest) (*pb.LookupResponse, error)
3530	runQuery         func(*pb.RunQueryRequest) (*pb.RunQueryResponse, error)
3531	beginTransaction func(*pb.BeginTransactionRequest) (*pb.BeginTransactionResponse, error)
3532	commit           func(*pb.CommitRequest) (*pb.CommitResponse, error)
3533	rollback         func(*pb.RollbackRequest) (*pb.RollbackResponse, error)
3534	allocateIds      func(*pb.AllocateIdsRequest) (*pb.AllocateIdsResponse, error)
3535}
3536
3537func (c *fakeDatastoreClient) Lookup(ctx context.Context, in *pb.LookupRequest, opts ...grpc.CallOption) (*pb.LookupResponse, error) {
3538	if c.lookup == nil {
3539		return nil, errors.New("no lookup handler defined")
3540	}
3541	return c.lookup(in)
3542}
3543func (c *fakeDatastoreClient) RunQuery(ctx context.Context, in *pb.RunQueryRequest, opts ...grpc.CallOption) (*pb.RunQueryResponse, error) {
3544	if c.runQuery == nil {
3545		return nil, errors.New("no runQuery handler defined")
3546	}
3547	return c.runQuery(in)
3548}
3549func (c *fakeDatastoreClient) BeginTransaction(ctx context.Context, in *pb.BeginTransactionRequest, opts ...grpc.CallOption) (*pb.BeginTransactionResponse, error) {
3550	if c.beginTransaction == nil {
3551		return nil, errors.New("no beginTransaction handler defined")
3552	}
3553	return c.beginTransaction(in)
3554}
3555func (c *fakeDatastoreClient) Commit(ctx context.Context, in *pb.CommitRequest, opts ...grpc.CallOption) (*pb.CommitResponse, error) {
3556	if c.commit == nil {
3557		return nil, errors.New("no commit handler defined")
3558	}
3559	return c.commit(in)
3560}
3561func (c *fakeDatastoreClient) Rollback(ctx context.Context, in *pb.RollbackRequest, opts ...grpc.CallOption) (*pb.RollbackResponse, error) {
3562	if c.rollback == nil {
3563		return nil, errors.New("no rollback handler defined")
3564	}
3565	return c.rollback(in)
3566}
3567func (c *fakeDatastoreClient) AllocateIds(ctx context.Context, in *pb.AllocateIdsRequest, opts ...grpc.CallOption) (*pb.AllocateIdsResponse, error) {
3568	if c.allocateIds == nil {
3569		return nil, errors.New("no allocateIds handler defined")
3570	}
3571	return c.allocateIds(in)
3572}
3573