github.com/mjibson/goon@v1.1.0/goon_test.go (about) 1 /* 2 * Copyright (c) 2012 The Goon Authors 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 package goon 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "math/rand" 25 "reflect" 26 "strings" 27 "sync" 28 "testing" 29 "time" 30 31 "github.com/golang/protobuf/proto" 32 "google.golang.org/appengine" 33 "google.golang.org/appengine/aetest" 34 "google.golang.org/appengine/datastore" 35 "google.golang.org/appengine/memcache" 36 ) 37 38 func init() { 39 // The SDK emulators are extremely slow, so we can't use production timeouts 40 MemcachePutTimeoutSmall = 10 * time.Second 41 MemcacheGetTimeout = 10 * time.Second 42 // Make sure to propagate all errors for better testing 43 propagateMemcachePutError = true 44 } 45 46 // *[]S, *[]*S, *[]I, []S, []*S, []I, 47 // *[]PLS, *[]*PLS, *[]IPLS, []PLS, []*PLS, []IPLS 48 const ( 49 ivTypePtrToSliceOfStructs = iota 50 ivTypePtrToSliceOfPtrsToStruct 51 ivTypePtrToSliceOfInterfaces 52 ivTypeSliceOfStructs 53 ivTypeSliceOfPtrsToStruct 54 ivTypeSliceOfInterfaces 55 ivTypePtrToSliceOfPLS 56 ivTypePtrToSliceOfPtrsToPLS 57 ivTypePtrToSliceOfInterfacesPLS 58 ivTypeSliceOfPLS 59 ivTypeSliceOfPtrsToPLS 60 ivTypeSliceOfInterfacesPLS 61 ivTypeTotal 62 ) 63 64 const ( 65 ivModeDatastore = iota 66 ivModeMemcache 67 ivModeMemcacheAndDatastore 68 ivModeLocalcache 69 ivModeLocalcacheAndMemcache 70 ivModeLocalcacheAndDatastore 71 ivModeLocalcacheAndMemcacheAndDatastore 72 ivModeTotal 73 ) 74 75 func cloneKey(key *datastore.Key) *datastore.Key { 76 if key == nil { 77 return nil 78 } 79 dupe, err := datastore.DecodeKey(key.Encode()) 80 if err != nil { 81 panic(fmt.Sprintf("Failed to clone key: %v", err)) 82 } 83 return dupe 84 } 85 86 func cloneKeys(keys []*datastore.Key) []*datastore.Key { 87 if keys == nil { 88 return nil 89 } 90 dupe := make([]*datastore.Key, 0, len(keys)) 91 for _, key := range keys { 92 if key == nil { 93 dupe = append(dupe, nil) 94 } else { 95 dupe = append(dupe, cloneKey(key)) 96 } 97 } 98 return dupe 99 } 100 101 func TestCloneIVItem(t *testing.T) { 102 c, done, err := aetest.NewContext() 103 if err != nil { 104 t.Fatalf("Could not start aetest - %v", err) 105 } 106 defer done() 107 108 initializeIvItems(c) 109 110 for i := range ivItems { 111 clone := *ivItems[i].clone() 112 if !reflect.DeepEqual(ivItems[i], clone) { 113 t.Fatalf("ivItem clone failed!\n%v", getDiff(ivItems[i], clone, fmt.Sprintf("ivItems[%d]", i), "clone")) 114 } 115 } 116 } 117 118 // Have a bunch of different supported types to detect any wild errors 119 // https://cloud.google.com/appengine/docs/standard/go/datastore/reference 120 // 121 // - signed integers (int, int8, int16, int32 and int64), 122 // - bool, 123 // - string, 124 // - float32 and float64, 125 // - []byte (up to 1 megabyte in length), 126 // - any type whose underlying type is one of the above predeclared types, 127 // - ByteString, 128 // - *Key, 129 // - time.Time (stored with microsecond precision), 130 // - appengine.BlobKey, 131 // - appengine.GeoPoint, 132 // - structs whose fields are all valid value types, 133 // - slices of any of the above. 134 // 135 // In addition, although undocumented, there's also support for any type, 136 // whose underlying type is a legal slice. 137 type ivItem struct { 138 Id int64 `datastore:"-" goon:"id"` 139 Kind string `datastore:"-" goon:"kind,ivItem"` 140 Int int `datastore:"int,noindex"` 141 Int8 int8 `datastore:"int8,noindex"` 142 Int16 int16 `datastore:"int16,noindex"` 143 Int32 int32 `datastore:"int32,noindex"` 144 Int64 int64 `datastore:"int64,noindex"` 145 Bool bool `datastore:"bool,noindex"` 146 String string `datastore:"string,noindex"` 147 Float32 float32 `datastore:"float32,noindex"` 148 Float64 float64 `datastore:"float64,noindex"` 149 ByteSlice []byte `datastore:"byte_slice,noindex"` 150 CustomTypes ivItemCustom `datastore:"custom,noindex"` 151 BString datastore.ByteString `datastore:"bstr,noindex"` 152 Key *datastore.Key `datastore:"key,noindex"` 153 Time time.Time `datastore:"time,noindex"` 154 BlobKey appengine.BlobKey `datastore:"bk,noindex"` 155 GeoPoint appengine.GeoPoint `datastore:"gp,noindex"` 156 Sub ivItemSub `datastore:"sub,noindex"` 157 SliceTypes ivItemSlice `datastore:"slice,noindex"` 158 CustomSlices ivItemSliceCustom `datastore:"custom_slice,noindex"` 159 160 NoIndex int `datastore:",noindex"` 161 Casual string 162 Ζεύς string 163 ChildKey *datastore.Key 164 ZeroKey *datastore.Key 165 KeySliceNil []*datastore.Key 166 167 SaveCount int 168 LoadCount int 169 } 170 171 func (ivi ivItem) clone() *ivItem { 172 return &ivItem{ 173 Id: ivi.Id, 174 Int: ivi.Int, 175 Int8: ivi.Int8, 176 Int16: ivi.Int16, 177 Int32: ivi.Int32, 178 Int64: ivi.Int64, 179 Bool: ivi.Bool, 180 String: ivi.String, 181 Float32: ivi.Float32, 182 Float64: ivi.Float64, 183 ByteSlice: append(ivi.ByteSlice[:0:0], ivi.ByteSlice...), 184 CustomTypes: *ivi.CustomTypes.clone(), 185 BString: append(ivi.BString[:0:0], ivi.BString...), 186 Key: cloneKey(ivi.Key), 187 Time: ivi.Time, 188 BlobKey: ivi.BlobKey, 189 GeoPoint: ivi.GeoPoint, 190 Sub: *ivi.Sub.clone(), 191 SliceTypes: *ivi.SliceTypes.clone(), 192 CustomSlices: *ivi.CustomSlices.clone(), 193 NoIndex: ivi.NoIndex, 194 Casual: ivi.Casual, 195 Ζεύς: ivi.Ζεύς, 196 ChildKey: cloneKey(ivi.ChildKey), 197 ZeroKey: cloneKey(ivi.ZeroKey), 198 KeySliceNil: cloneKeys(ivi.KeySliceNil), 199 SaveCount: ivi.SaveCount, 200 LoadCount: ivi.LoadCount, 201 } 202 } 203 204 type ivItemInt int 205 type ivItemInt8 int8 206 type ivItemInt16 int16 207 type ivItemInt32 int32 208 type ivItemInt64 int64 209 type ivItemBool bool 210 type ivItemString string 211 type ivItemFloat32 float32 212 type ivItemFloat64 float64 213 type ivItemByteSlice []byte 214 215 type ivItemDeepInt ivItemInt 216 217 type ivItemCustom struct { 218 Int ivItemInt 219 Int8 ivItemInt8 220 Int16 ivItemInt16 221 Int32 ivItemInt32 222 Int64 ivItemInt64 223 Bool ivItemBool 224 String ivItemString 225 Float32 ivItemFloat32 226 Float64 ivItemFloat64 227 ByteSlice ivItemByteSlice 228 DeepInt ivItemDeepInt 229 } 230 231 func (ivic ivItemCustom) clone() *ivItemCustom { 232 return &ivItemCustom{ 233 Int: ivic.Int, 234 Int8: ivic.Int8, 235 Int16: ivic.Int16, 236 Int32: ivic.Int32, 237 Int64: ivic.Int64, 238 Bool: ivic.Bool, 239 String: ivic.String, 240 Float32: ivic.Float32, 241 Float64: ivic.Float64, 242 ByteSlice: append(ivic.ByteSlice[:0:0], ivic.ByteSlice...), 243 DeepInt: ivic.DeepInt, 244 } 245 } 246 247 type ivItemSlice struct { 248 Int []int 249 Int8 []int8 250 Int16 []int16 251 Int32 []int32 252 Int64 []int64 253 Bool []bool 254 String []string 255 Float32 []float32 256 Float64 []float64 257 BSSlice [][]byte 258 IntC []ivItemInt 259 Int8C []ivItemInt8 260 Int16C []ivItemInt16 261 Int32C []ivItemInt32 262 Int64C []ivItemInt64 263 BoolC []ivItemBool 264 StringC []ivItemString 265 Float32C []ivItemFloat32 266 Float64C []ivItemFloat64 267 BSSliceC []ivItemByteSlice 268 DeepInt []ivItemDeepInt 269 BStrSlice []datastore.ByteString 270 KeySlice []*datastore.Key 271 TimeSlice []time.Time 272 BKSlice []appengine.BlobKey 273 GPSlice []appengine.GeoPoint 274 Subs []ivItemSubs 275 } 276 277 func (ivis ivItemSlice) clone() *ivItemSlice { 278 bsSlice := ivis.BSSlice[:0:0] 279 for _, bs := range ivis.BSSlice { 280 bsSlice = append(bsSlice, append(bs[:0:0], bs...)) 281 } 282 bsSliceC := ivis.BSSliceC[:0:0] 283 for _, bsc := range ivis.BSSliceC { 284 bsSliceC = append(bsSliceC, append(bsc[:0:0], bsc...)) 285 } 286 bstrSlice := ivis.BStrSlice[:0:0] 287 for _, bstr := range ivis.BStrSlice { 288 bstrSlice = append(bstrSlice, append(bstr[:0:0], bstr...)) 289 } 290 subs := ivis.Subs[:0:0] 291 for _, sub := range ivis.Subs { 292 subs = append(subs, *sub.clone()) 293 } 294 295 return &ivItemSlice{ 296 Int: append(ivis.Int[:0:0], ivis.Int...), 297 Int8: append(ivis.Int8[:0:0], ivis.Int8...), 298 Int16: append(ivis.Int16[:0:0], ivis.Int16...), 299 Int32: append(ivis.Int32[:0:0], ivis.Int32...), 300 Int64: append(ivis.Int64[:0:0], ivis.Int64...), 301 Bool: append(ivis.Bool[:0:0], ivis.Bool...), 302 String: append(ivis.String[:0:0], ivis.String...), 303 Float32: append(ivis.Float32[:0:0], ivis.Float32...), 304 Float64: append(ivis.Float64[:0:0], ivis.Float64...), 305 BSSlice: bsSlice, 306 IntC: append(ivis.IntC[:0:0], ivis.IntC...), 307 Int8C: append(ivis.Int8C[:0:0], ivis.Int8C...), 308 Int16C: append(ivis.Int16C[:0:0], ivis.Int16C...), 309 Int32C: append(ivis.Int32C[:0:0], ivis.Int32C...), 310 Int64C: append(ivis.Int64C[:0:0], ivis.Int64C...), 311 BoolC: append(ivis.BoolC[:0:0], ivis.BoolC...), 312 StringC: append(ivis.StringC[:0:0], ivis.StringC...), 313 Float32C: append(ivis.Float32C[:0:0], ivis.Float32C...), 314 Float64C: append(ivis.Float64C[:0:0], ivis.Float64C...), 315 BSSliceC: bsSliceC, 316 DeepInt: append(ivis.DeepInt[:0:0], ivis.DeepInt...), 317 BStrSlice: bstrSlice, 318 KeySlice: cloneKeys(ivis.KeySlice), 319 TimeSlice: append(ivis.TimeSlice[:0:0], ivis.TimeSlice...), 320 BKSlice: append(ivis.BKSlice[:0:0], ivis.BKSlice...), 321 GPSlice: append(ivis.GPSlice[:0:0], ivis.GPSlice...), 322 Subs: subs, 323 } 324 } 325 326 type IntS []int 327 type Int8S []int8 328 type Int16S []int16 329 type Int32S []int32 330 type Int64S []int64 331 type BoolS []bool 332 type StringS []string 333 type Float32S []float32 334 type Float64S []float64 335 type BSSliceS [][]byte 336 type IntCS []ivItemInt 337 type Int8CS []ivItemInt8 338 type Int16CS []ivItemInt16 339 type Int32CS []ivItemInt32 340 type Int64CS []ivItemInt64 341 type BoolCS []ivItemBool 342 type StringCS []ivItemString 343 type Float32CS []ivItemFloat32 344 type Float64CS []ivItemFloat64 345 type BSSliceCS []ivItemByteSlice 346 type DeepIntS []ivItemDeepInt 347 type BStrSliceS []datastore.ByteString 348 type KeySliceS []*datastore.Key 349 type TimeSliceS []time.Time 350 type BKSliceS []appengine.BlobKey 351 type GPSliceS []appengine.GeoPoint 352 type SubsS []ivItemSubs 353 354 type ivItemSliceCustom struct { 355 Int IntS 356 Int8 Int8S 357 Int16 Int16S 358 Int32 Int32S 359 Int64 Int64S 360 Bool BoolS 361 String StringS 362 Float32 Float32S 363 Float64 Float64S 364 BSSlice BSSliceS 365 IntC IntCS 366 Int8C Int8CS 367 Int16C Int16CS 368 Int32C Int32CS 369 Int64C Int64CS 370 BoolC BoolCS 371 StringC StringCS 372 Float32C Float32CS 373 Float64C Float64CS 374 BSSliceC BSSliceCS 375 DeepInt DeepIntS 376 BStrSlice BStrSliceS 377 KeySlice KeySliceS 378 TimeSlice TimeSliceS 379 BKSlice BKSliceS 380 GPSlice GPSliceS 381 Subs SubsS 382 } 383 384 func (ivisc ivItemSliceCustom) clone() *ivItemSliceCustom { 385 bsSlice := ivisc.BSSlice[:0:0] 386 for _, bs := range ivisc.BSSlice { 387 bsSlice = append(bsSlice, append(bs[:0:0], bs...)) 388 } 389 bsSliceC := ivisc.BSSliceC[:0:0] 390 for _, bsc := range ivisc.BSSliceC { 391 bsSliceC = append(bsSliceC, append(bsc[:0:0], bsc...)) 392 } 393 bstrSlice := ivisc.BStrSlice[:0:0] 394 for _, bstr := range ivisc.BStrSlice { 395 bstrSlice = append(bstrSlice, append(bstr[:0:0], bstr...)) 396 } 397 subs := ivisc.Subs[:0:0] 398 for _, sub := range ivisc.Subs { 399 subs = append(subs, *sub.clone()) 400 } 401 402 return &ivItemSliceCustom{ 403 Int: append(ivisc.Int[:0:0], ivisc.Int...), 404 Int8: append(ivisc.Int8[:0:0], ivisc.Int8...), 405 Int16: append(ivisc.Int16[:0:0], ivisc.Int16...), 406 Int32: append(ivisc.Int32[:0:0], ivisc.Int32...), 407 Int64: append(ivisc.Int64[:0:0], ivisc.Int64...), 408 Bool: append(ivisc.Bool[:0:0], ivisc.Bool...), 409 String: append(ivisc.String[:0:0], ivisc.String...), 410 Float32: append(ivisc.Float32[:0:0], ivisc.Float32...), 411 Float64: append(ivisc.Float64[:0:0], ivisc.Float64...), 412 BSSlice: bsSlice, 413 IntC: append(ivisc.IntC[:0:0], ivisc.IntC...), 414 Int8C: append(ivisc.Int8C[:0:0], ivisc.Int8C...), 415 Int16C: append(ivisc.Int16C[:0:0], ivisc.Int16C...), 416 Int32C: append(ivisc.Int32C[:0:0], ivisc.Int32C...), 417 Int64C: append(ivisc.Int64C[:0:0], ivisc.Int64C...), 418 BoolC: append(ivisc.BoolC[:0:0], ivisc.BoolC...), 419 StringC: append(ivisc.StringC[:0:0], ivisc.StringC...), 420 Float32C: append(ivisc.Float32C[:0:0], ivisc.Float32C...), 421 Float64C: append(ivisc.Float64C[:0:0], ivisc.Float64C...), 422 BSSliceC: bsSliceC, 423 DeepInt: append(ivisc.DeepInt[:0:0], ivisc.DeepInt...), 424 BStrSlice: bstrSlice, 425 KeySlice: cloneKeys(ivisc.KeySlice), 426 TimeSlice: append(ivisc.TimeSlice[:0:0], ivisc.TimeSlice...), 427 BKSlice: append(ivisc.BKSlice[:0:0], ivisc.BKSlice...), 428 GPSlice: append(ivisc.GPSlice[:0:0], ivisc.GPSlice...), 429 Subs: subs, 430 } 431 } 432 433 type ivItemSub struct { 434 Data string `datastore:"data,noindex"` 435 Ints []int `datastore:"ints,noindex"` 436 } 437 438 func (ivis ivItemSub) clone() *ivItemSub { 439 return &ivItemSub{ 440 Data: ivis.Data, 441 Ints: append(ivis.Ints[:0:0], ivis.Ints...), 442 } 443 } 444 445 type ivItemSubs struct { 446 Key *datastore.Key `datastore:"key,noindex"` 447 Data string `datastore:"data,noindex"` 448 Extra string `datastore:",noindex"` 449 } 450 451 func (ivis ivItemSubs) clone() *ivItemSubs { 452 return &ivItemSubs{ 453 Key: cloneKey(ivis.Key), 454 Data: ivis.Data, 455 Extra: ivis.Extra, 456 } 457 } 458 459 func (ivi *ivItem) ForInterface() {} 460 func (ivi *ivItemPLS) ForInterface() {} 461 462 type ivItemI interface { 463 ForInterface() 464 } 465 466 // Implement the PropertyLoadSave interface for ivItem 467 type ivItemPLS ivItem 468 469 func (ivi *ivItemPLS) Save() ([]datastore.Property, error) { 470 ivi.SaveCount++ 471 return datastore.SaveStruct(ivi) 472 } 473 474 func (ivi *ivItemPLS) Load(props []datastore.Property) error { 475 err := datastore.LoadStruct(ivi, props) 476 ivi.LoadCount++ 477 return err 478 } 479 480 var ivItems []ivItem 481 var ivItemKeys []*datastore.Key 482 483 func initializeIvItems(c context.Context) { 484 // We force UTC, because the datastore API will always return UTC 485 t1 := time.Now().UTC().Truncate(time.Microsecond) 486 t2 := t1.Add(time.Second * 1) 487 t3 := t1.Add(time.Second * 2) 488 489 ivi1 := &ivItem{ 490 Id: 1, 491 Int: 123, 492 Int8: 77, 493 Int16: 13001, 494 Int32: 1234567890, 495 Int64: 123456789012345, 496 Bool: true, 497 String: "one", 498 Float32: (float32(10) / float32(3)), 499 Float64: (float64(10000000) / float64(9998)), 500 ByteSlice: []byte{0xDE, 0xAD}, 501 CustomTypes: ivItemCustom{ 502 Int: 123, 503 Int8: 77, 504 Int16: 13001, 505 Int32: 1234567890, 506 Int64: 123456789012345, 507 Bool: true, 508 String: "one", 509 Float32: ivItemFloat32(float32(10) / float32(3)), 510 Float64: ivItemFloat64(float64(10000000) / float64(9998)), 511 ByteSlice: ivItemByteSlice([]byte{0x01, 0x02, 0xAA}), 512 DeepInt: 1, 513 }, 514 BString: datastore.ByteString([]byte{0xAB}), 515 Key: datastore.NewKey(c, "Fruit", "Apple", 0, nil), 516 Time: t1, 517 BlobKey: appengine.BlobKey("fake #1"), 518 GeoPoint: appengine.GeoPoint{Lat: 1.1, Lng: 2.2}, 519 Sub: ivItemSub{ 520 Data: "yay #1", 521 Ints: []int{1, 2, 3}, 522 }, 523 SliceTypes: ivItemSlice{ 524 Int: []int{1, 2}, 525 Int8: []int8{1, 2}, 526 Int16: []int16{1, 2}, 527 Int32: []int32{1, 2}, 528 Int64: []int64{1, 2}, 529 Bool: []bool{true, false}, 530 String: []string{"one", "two"}, 531 Float32: []float32{1.0, 2.0}, 532 Float64: []float64{1.0, 2.0}, 533 BSSlice: [][]byte{{0x01, 0x02}, {0x03, 0x04}}, 534 IntC: []ivItemInt{1, 2}, 535 Int8C: []ivItemInt8{1, 2}, 536 Int16C: []ivItemInt16{1, 2}, 537 Int32C: []ivItemInt32{1, 2}, 538 Int64C: []ivItemInt64{1, 2}, 539 BoolC: []ivItemBool{true, false}, 540 StringC: []ivItemString{"one", "two"}, 541 Float32C: []ivItemFloat32{1.0, 2.0}, 542 Float64C: []ivItemFloat64{1.0, 2.0}, 543 BSSliceC: []ivItemByteSlice{{0x01, 0x02}, {0x03, 0x04}}, 544 DeepInt: []ivItemDeepInt{1, 2}, 545 BStrSlice: []datastore.ByteString{datastore.ByteString("one"), datastore.ByteString("two")}, 546 KeySlice: []*datastore.Key{datastore.NewKey(c, "Key", "", 1, nil), datastore.NewKey(c, "Key", "", 2, nil), datastore.NewKey(c, "Key", "", 3, nil)}, 547 TimeSlice: []time.Time{t1, t2, t3}, 548 BKSlice: []appengine.BlobKey{appengine.BlobKey("fake #1.1"), appengine.BlobKey("fake #1.2")}, 549 GPSlice: []appengine.GeoPoint{{Lat: 1.1, Lng: -2.2}, {Lat: -3.3, Lng: 4.4}}, 550 Subs: []ivItemSubs{ 551 {Key: datastore.NewKey(c, "Fruit", "Banana", 0, nil), Data: "sub #1.1", Extra: "xtra #1.1"}, 552 {Key: nil, Data: "sub #1.2", Extra: "xtra #1.2"}, 553 {Key: datastore.NewKey(c, "Fruit", "Cherry", 0, nil), Data: "sub #1.3", Extra: "xtra #1.3"}, 554 }, 555 }, 556 CustomSlices: ivItemSliceCustom{ 557 Int: IntS{1, 2}, 558 Int8: Int8S{1, 2}, 559 Int16: Int16S{1, 2}, 560 Int32: Int32S{1, 2}, 561 Int64: Int64S{1, 2}, 562 Bool: BoolS{true, false}, 563 String: StringS{"one", "two"}, 564 Float32: Float32S{1.0, 2.0}, 565 Float64: Float64S{1.0, 2.0}, 566 BSSlice: BSSliceS{[]byte{0x01, 0x02}, []byte{0x03, 0x04}}, 567 IntC: IntCS{1, 2}, 568 Int8C: Int8CS{1, 2}, 569 Int16C: Int16CS{1, 2}, 570 Int32C: Int32CS{1, 2}, 571 Int64C: Int64CS{1, 2}, 572 BoolC: BoolCS{true, false}, 573 StringC: StringCS{"one", "two"}, 574 Float32C: Float32CS{1.0, 2.0}, 575 Float64C: Float64CS{1.0, 2.0}, 576 BSSliceC: BSSliceCS{ivItemByteSlice{0x01, 0x02}, ivItemByteSlice{0x03, 0x04}}, 577 DeepInt: DeepIntS{1, 2}, 578 BStrSlice: BStrSliceS{datastore.ByteString("one"), datastore.ByteString("two")}, 579 KeySlice: KeySliceS{datastore.NewKey(c, "Key", "", 1, nil), datastore.NewKey(c, "Key", "", 2, nil), datastore.NewKey(c, "Key", "", 3, nil)}, 580 TimeSlice: TimeSliceS{t1, t2, t3}, 581 BKSlice: BKSliceS{appengine.BlobKey("fake #1.1"), appengine.BlobKey("fake #1.2")}, 582 GPSlice: GPSliceS{appengine.GeoPoint{Lat: 1.1, Lng: -2.2}, appengine.GeoPoint{Lat: -3.3, Lng: 4.4}}, 583 Subs: SubsS{ 584 {Key: datastore.NewKey(c, "Fruit", "Banana", 0, nil), Data: "sub #1.1", Extra: "xtra #1.1"}, 585 {Key: datastore.NewKey(c, "Fruit", "Cherry", 0, nil), Data: "sub #1.2", Extra: "xtra #1.2"}, 586 {Key: nil, Data: "sub #1.3", Extra: "xtra #1.3"}, 587 }, 588 }, 589 NoIndex: 1, 590 Casual: "clothes", 591 Ζεύς: "Zeus", 592 ChildKey: datastore.NewKey(c, "Person", "Jane", 0, datastore.NewKey(c, "Person", "John", 0, datastore.NewKey(c, "Person", "Jack", 0, nil))), 593 ZeroKey: nil, 594 KeySliceNil: []*datastore.Key{datastore.NewKey(c, "Number", "", 1, nil), nil, datastore.NewKey(c, "Number", "", 2, nil)}, 595 } 596 597 ivi2 := ivi1.clone() 598 ivi2.Id = 2 599 600 ivi3 := ivi1.clone() 601 ivi3.Id = 3 602 603 ivItems = append(ivItems, *ivi1) 604 ivItems = append(ivItems, *ivi2) 605 ivItems = append(ivItems, *ivi3) 606 607 g := FromContext(c) 608 for i := range ivItems { 609 ivItemKeys = append(ivItemKeys, g.Key(&ivItems[i])) 610 } 611 } 612 613 func getInputVarietyItem(t *testing.T, g *Goon, ivType int, empty bool, indices ...int) interface{} { 614 var result interface{} 615 616 getItem := func(index int) *ivItem { 617 if empty { 618 return &ivItem{Id: ivItems[index].Id} 619 } 620 return ivItems[index].clone() 621 } 622 623 switch ivType { 624 case ivTypePtrToSliceOfStructs: 625 s := []ivItem{} 626 for _, index := range indices { 627 s = append(s, *getItem(index)) 628 } 629 result = &s 630 case ivTypePtrToSliceOfPtrsToStruct: 631 s := []*ivItem{} 632 for _, index := range indices { 633 s = append(s, getItem(index)) 634 } 635 result = &s 636 case ivTypePtrToSliceOfInterfaces: 637 s := []ivItemI{} 638 for _, index := range indices { 639 s = append(s, getItem(index)) 640 } 641 result = &s 642 case ivTypeSliceOfStructs: 643 s := []ivItem{} 644 for _, index := range indices { 645 s = append(s, *getItem(index)) 646 } 647 result = s 648 case ivTypeSliceOfPtrsToStruct: 649 s := []*ivItem{} 650 for _, index := range indices { 651 s = append(s, getItem(index)) 652 } 653 result = s 654 case ivTypeSliceOfInterfaces: 655 s := []ivItemI{} 656 for _, index := range indices { 657 s = append(s, getItem(index)) 658 } 659 result = s 660 case ivTypePtrToSliceOfPLS: 661 s := []ivItemPLS{} 662 for _, index := range indices { 663 s = append(s, (ivItemPLS)(*getItem(index))) 664 } 665 result = &s 666 case ivTypePtrToSliceOfPtrsToPLS: 667 s := []*ivItemPLS{} 668 for _, index := range indices { 669 s = append(s, (*ivItemPLS)(getItem(index))) 670 } 671 result = &s 672 case ivTypePtrToSliceOfInterfacesPLS: 673 s := []ivItemI{} 674 for _, index := range indices { 675 s = append(s, (*ivItemPLS)(getItem(index))) 676 } 677 result = &s 678 case ivTypeSliceOfPLS: 679 s := []ivItemPLS{} 680 for _, index := range indices { 681 s = append(s, (ivItemPLS)(*getItem(index))) 682 } 683 result = s 684 case ivTypeSliceOfPtrsToPLS: 685 s := []*ivItemPLS{} 686 for _, index := range indices { 687 s = append(s, (*ivItemPLS)(getItem(index))) 688 } 689 result = s 690 case ivTypeSliceOfInterfacesPLS: 691 s := []ivItemI{} 692 for _, index := range indices { 693 s = append(s, (*ivItemPLS)(getItem(index))) 694 } 695 result = s 696 default: 697 t.Fatalf("Invalid input variety type! %v", ivType) 698 return nil 699 } 700 701 return result 702 } 703 704 func getPrettyIVMode(ivMode int) string { 705 result := "N/A" 706 707 switch ivMode { 708 case ivModeDatastore: 709 result = "DS" 710 case ivModeMemcache: 711 result = "MC" 712 case ivModeMemcacheAndDatastore: 713 result = "DS+MC" 714 case ivModeLocalcache: 715 result = "LC" 716 case ivModeLocalcacheAndMemcache: 717 result = "MC+LC" 718 case ivModeLocalcacheAndDatastore: 719 result = "DS+LC" 720 case ivModeLocalcacheAndMemcacheAndDatastore: 721 result = "DS+MC+LC" 722 } 723 724 return result 725 } 726 727 func getPrettyIVType(ivType int) string { 728 result := "N/A" 729 730 switch ivType { 731 case ivTypePtrToSliceOfStructs: 732 result = "*[]S" 733 case ivTypePtrToSliceOfPtrsToStruct: 734 result = "*[]*S" 735 case ivTypePtrToSliceOfInterfaces: 736 result = "*[]I" 737 case ivTypeSliceOfStructs: 738 result = "[]S" 739 case ivTypeSliceOfPtrsToStruct: 740 result = "[]*S" 741 case ivTypeSliceOfInterfaces: 742 result = "[]I" 743 case ivTypePtrToSliceOfPLS: 744 result = "*[]PLS" 745 case ivTypePtrToSliceOfPtrsToPLS: 746 result = "*[]*PLS" 747 case ivTypePtrToSliceOfInterfacesPLS: 748 result = "*[]IPLS" 749 case ivTypeSliceOfPLS: 750 result = "[]PLS" 751 case ivTypeSliceOfPtrsToPLS: 752 result = "[]*PLS" 753 case ivTypeSliceOfInterfacesPLS: 754 result = "[]IPLS" 755 } 756 757 return result 758 } 759 760 func isIVTypePLS(ivType int) bool { 761 switch ivType { 762 case ivTypePtrToSliceOfPLS, 763 ivTypePtrToSliceOfPtrsToPLS, 764 ivTypePtrToSliceOfInterfacesPLS, 765 ivTypeSliceOfPLS, 766 ivTypeSliceOfPtrsToPLS, 767 ivTypeSliceOfInterfacesPLS: 768 return true 769 } 770 return false 771 } 772 773 // getDiff is a helper function that returns string lines describing the differences between a & b 774 func getDiff(a, b interface{}, aName, bName string) string { 775 var buf bytes.Buffer 776 777 av := reflect.Indirect(reflect.ValueOf(a)) 778 bv := reflect.Indirect(reflect.ValueOf(b)) 779 780 switch av.Kind() { 781 case reflect.Slice: 782 if av.Len() != bv.Len() { 783 buf.WriteString(fmt.Sprintf("%v has len %v, but %v has len %v\n", aName, av.Len(), bName, bv.Len())) 784 } else { 785 for i := 0; i < av.Len(); i++ { 786 avi := av.Index(i).Interface() 787 bvi := bv.Index(i).Interface() 788 buf.WriteString(getDiff(avi, bvi, fmt.Sprintf("%s[%d]", aName, i), fmt.Sprintf("%s[%d]", bName, i))) 789 } 790 } 791 case reflect.Struct: 792 if av.NumField() != bv.NumField() { 793 buf.WriteString(fmt.Sprintf("%v has %v fields, but %v has %v fields\n", aName, av.NumField(), bName, bv.NumField())) 794 } else { 795 for i := 0; i < av.NumField(); i++ { 796 avf := av.Field(i) 797 bvf := bv.Field(i) 798 799 avft := av.Type().Field(i) 800 bvft := bv.Type().Field(i) 801 802 avftName := fmt.Sprintf("%s.%s", aName, avft.Name) 803 bvftName := fmt.Sprintf("%s.%s", bName, bvft.Name) 804 805 if avft.Type != bvft.Type { 806 buf.WriteString(fmt.Sprintf("%v has type %v, but %v has type %v\n", avftName, avft.Type, bvftName, bvft.Type)) 807 } else { 808 if avft.PkgPath == "" || avft.Anonymous || bvft.PkgPath == "" || bvft.Anonymous { 809 buf.WriteString(getDiff(avf.Interface(), bvf.Interface(), avftName, bvftName)) 810 } 811 } 812 } 813 } 814 default: 815 if !reflect.DeepEqual(a, b) { 816 buf.WriteString(fmt.Sprintf("MISMATCH: %v == %v | %v == %v\n", aName, a, bName, b)) 817 } 818 } 819 820 return buf.String() 821 } 822 823 func onlyErrNoSuchEntity(err error) bool { 824 if err == nil { 825 return false 826 } 827 merr, ok := err.(appengine.MultiError) 828 if !ok || len(merr) == 0 { 829 return false 830 } 831 for i := 0; i < len(merr); i++ { 832 if merr[i] != datastore.ErrNoSuchEntity { 833 return false 834 } 835 } 836 return true 837 } 838 839 func ivGetMulti(t *testing.T, g *Goon, ref, dst interface{}, prettyInfo string) error { 840 // Get our data back and make sure it's correct 841 if err := g.GetMulti(dst); err != nil { 842 t.Fatalf("%s > Unexpected error on GetMulti - %v", prettyInfo, err) 843 return err 844 } else { 845 dstLen := reflect.Indirect(reflect.ValueOf(dst)).Len() 846 refLen := reflect.Indirect(reflect.ValueOf(ref)).Len() 847 848 if dstLen != refLen { 849 t.Fatalf("%s > Unexpected dst len (%v) doesn't match ref len (%v)", prettyInfo, dstLen, refLen) 850 } else if !reflect.DeepEqual(ref, dst) { 851 t.Fatalf("%s > ivGetMulti didn't return what was expected:\n%s", prettyInfo, getDiff(ref, dst, "ref", "dst")) 852 } 853 } 854 return nil 855 } 856 857 func setPLSCounts(ref interface{}, saveCount, loadCount bool) { 858 // Confirm that Save() and Load() are called as specified 859 v := reflect.Indirect(reflect.ValueOf(ref)) 860 for i := 0; i < v.Len(); i++ { 861 vi := reflect.Indirect(v.Index(i)) 862 if vi.Kind() == reflect.Interface { 863 vi = reflect.Indirect(vi.Elem()) 864 } 865 if saveCount { 866 vi.FieldByName("SaveCount").SetInt(1) 867 } 868 if loadCount { 869 vi.FieldByName("LoadCount").SetInt(1) 870 } 871 } 872 } 873 874 // This function marks either all or the provided indices of target as dirty. 875 // It's purpose is to use it on entities fetched via Get, 876 // and then see if refetching those entities returns the dirty entities. 877 func makeDirty(target interface{}, indices ...int) { 878 if target == nil { 879 return 880 } 881 v := reflect.Indirect(reflect.ValueOf(target)) 882 for i := 0; i < v.Len(); i++ { 883 found := (len(indices) == 0) // If no indices are provided, we dirty everything 884 for _, index := range indices { 885 if index == i { 886 found = true 887 break 888 } 889 } 890 if !found { 891 continue 892 } 893 vi := reflect.Indirect(v.Index(i)) 894 if vi.Kind() == reflect.Interface { 895 vi = reflect.Indirect(vi.Elem()) 896 } 897 vi.FieldByName("String").SetString("dirty") 898 } 899 } 900 901 func validateInputVariety(t *testing.T, g *Goon, srcType, dstType, mode int, txn bool) { 902 if mode >= ivModeTotal { 903 t.Fatalf("Invalid input variety mode! %v >= %v", mode, ivModeTotal) 904 return 905 } 906 907 // Generate a nice debug info string for clear logging 908 prettyInfo := getPrettyIVType(srcType) + " " + getPrettyIVType(dstType) + " " + getPrettyIVMode(mode) 909 if txn { 910 prettyInfo += " TXN" 911 } 912 913 // Generate test data with the specified types 914 src := getInputVarietyItem(t, g, srcType, false, 0, 1, 2) 915 ref := getInputVarietyItem(t, g, dstType, false, 0, 1, 2) 916 dstA := getInputVarietyItem(t, g, dstType, true, 0, 1, 2) 917 dstB := getInputVarietyItem(t, g, dstType, true, 0, 1, 2) 918 dstC := getInputVarietyItem(t, g, dstType, true, 0, 1, 2) 919 920 setPLSCounts(ref, isIVTypePLS(srcType), isIVTypePLS(dstType)) 921 922 // Save our test data 923 if txn { 924 if err := g.RunInTransaction(func(tg *Goon) error { 925 _, err := tg.PutMulti(src) 926 return err 927 }, &datastore.TransactionOptions{XG: true}); err != nil { 928 t.Fatalf("%s > Unexpected error on PutMulti - %v", prettyInfo, err) 929 } 930 } else { 931 if _, err := g.PutMulti(src); err != nil { 932 t.Fatalf("%s > Unexpected error on PutMulti - %v", prettyInfo, err) 933 } 934 } 935 936 // Attempt an immediate get, which should catch any faulty Put-based caching 937 ivGetMulti(t, g, ref, dstA, prettyInfo+" PC") 938 939 // Clear the caches, as we're going to precisely set the caches via loadIVItem 940 // TODO: Instead of clear, fill the caches with invalid data 941 g.FlushLocalCache() 942 memcache.Flush(g.Context) 943 944 // This function just populates the cache via GetMulti 945 loadIVItem := func(indices ...int) { 946 dst := getInputVarietyItem(t, g, dstType, true, indices...) 947 if err := g.GetMulti(dst); err != nil { 948 t.Fatalf("%s > Unexpected error on GetMulti - %v", prettyInfo, err) 949 } 950 makeDirty(dst) // Make these dirty to confirm the cache doesn't reflect it 951 } 952 953 // Set the caches into proper state based on given mode 954 switch mode { 955 case ivModeDatastore: 956 // Caches already clear 957 case ivModeMemcache: 958 loadIVItem(0, 1, 2) // Left in memcache 959 g.FlushLocalCache() 960 case ivModeMemcacheAndDatastore: 961 loadIVItem(0, 1) // Left in memcache 962 g.FlushLocalCache() 963 case ivModeLocalcache: 964 loadIVItem(0, 1, 2) // Left in local cache 965 case ivModeLocalcacheAndMemcache: 966 loadIVItem(0) // Left in memcache 967 g.FlushLocalCache() 968 loadIVItem(1, 2) // Left in local cache 969 case ivModeLocalcacheAndDatastore: 970 loadIVItem(0, 1) // Left in local cache 971 case ivModeLocalcacheAndMemcacheAndDatastore: 972 loadIVItem(0) // Left in memcache 973 g.FlushLocalCache() 974 loadIVItem(1) // Left in local cache 975 } 976 977 // Get our data back and make sure it's correct 978 if txn { 979 if err := g.RunInTransaction(func(tg *Goon) error { 980 return ivGetMulti(t, tg, ref, dstB, prettyInfo+" GC") 981 }, &datastore.TransactionOptions{XG: true}); err != nil { 982 t.Fatalf("%s > Unexpected error on transaction - %v", prettyInfo, err) 983 } 984 } else { 985 ivGetMulti(t, g, ref, dstB, prettyInfo+" GC") 986 } 987 988 // Delete our data 989 if txn { 990 if err := g.RunInTransaction(func(tg *Goon) error { 991 return tg.DeleteMulti(ivItemKeys) 992 }, &datastore.TransactionOptions{XG: true}); err != nil { 993 t.Fatalf("%s > Unexpected error on DeleteMulti - %v", prettyInfo, err) 994 } 995 } else { 996 if err := g.DeleteMulti(ivItemKeys); err != nil { 997 t.Fatalf("%s > Unexpected error on DeleteMulti - %v", prettyInfo, err) 998 } 999 } 1000 1001 // Make sure our data isn't retrievable from any layer 1002 if err := g.GetMulti(dstC); !onlyErrNoSuchEntity(err) { 1003 t.Fatalf("%s > Expected ErrNoSuchEntity but got %v", prettyInfo, err) 1004 } 1005 1006 // Do final clean-up of any negative cache 1007 g.FlushLocalCache() 1008 memcache.Flush(g.Context) 1009 } 1010 1011 func TestInputVariety(t *testing.T) { 1012 c, done, err := aetest.NewContext() 1013 if err != nil { 1014 t.Fatalf("Could not start aetest - %v", err) 1015 } 1016 defer done() 1017 g := FromContext(c) 1018 1019 initializeIvItems(c) 1020 1021 for srcType := 0; srcType < ivTypeTotal; srcType++ { 1022 for dstType := 0; dstType < ivTypeTotal; dstType++ { 1023 for mode := 0; mode < ivModeTotal; mode++ { 1024 for txn := 0; txn < 2; txn++ { 1025 validateInputVariety(t, g, srcType, dstType, mode, txn == 1) 1026 } 1027 } 1028 } 1029 } 1030 } 1031 1032 func TestSerialization(t *testing.T) { 1033 c, done, err := aetest.NewContext() 1034 if err != nil { 1035 t.Fatalf("Could not start aetest - %v", err) 1036 } 1037 defer done() 1038 1039 initializeIvItems(c) 1040 1041 // Test that size is the same in back-to-back 1042 iviOut := ivItems[0].clone() 1043 iviOut.SaveCount = 1 // Set it to non-zero so that serialized size will match PLS 1044 data, err := serializeStruct(iviOut) 1045 if err != nil { 1046 t.Fatalf("Failed to serialize iviOut: %v", err) 1047 } 1048 dataB, err := serializeStruct(iviOut) 1049 if err != nil { 1050 t.Fatalf("Failed to serialize iviOut: %v", err) 1051 } 1052 if len(data) != len(dataB) { 1053 t.Fatalf("Back-to-back serialization returned different length data: %v != %v", len(data), len(dataB)) 1054 } 1055 1056 // Test that we can deserialize back to the struct 1057 iviIn := &ivItem{} 1058 err = deserializeStruct(iviIn, data) 1059 if err != nil { 1060 t.Fatalf("Failed to deserialize to iviIn: %v", err) 1061 } 1062 iviIn.Id = iviOut.Id // Manually set the id 1063 if !reflect.DeepEqual(iviOut, iviIn) { 1064 t.Errorf("Invalid result!\n%v", getDiff(iviOut, iviIn, "iviOut", "iviIn")) 1065 } 1066 1067 // PropertyLoadSaver serialization 1068 var iviplsOut *ivItemPLS 1069 iviplsOut = (*ivItemPLS)(ivItems[0].clone()) 1070 dataPLS, err := serializeStruct(iviplsOut) 1071 if err != nil { 1072 t.Fatalf("Failed to serialize iviplsOut: %v", err) 1073 } 1074 iviplsIn := &ivItemPLS{} 1075 err = deserializeStruct(iviplsIn, dataPLS) 1076 if err != nil { 1077 t.Fatalf("Failed to deserialize to iviplsIn: %v", err) 1078 } 1079 iviplsIn.Id = iviplsOut.Id // Manually set the id 1080 iviplsIn.LoadCount = iviplsOut.LoadCount // Reset the load count 1081 if !reflect.DeepEqual(iviplsOut, iviplsIn) { 1082 t.Errorf("Invalid PLS result!\n%v", getDiff(iviplsOut, iviplsIn, "iviplsOut", "iviplsIn")) 1083 1084 } 1085 1086 // Make sure both normal & PLS result in the same length data 1087 if len(data) != len(dataPLS) { 1088 t.Fatalf("Serialization returned different length data for normal vs PLS: %v != %v", len(data), len(dataPLS)) 1089 } 1090 1091 t.Logf("data size: %v", len(data)) 1092 1093 // Test that the retrieved data is stable 1094 s1, s2 := &HasId{Id: 1, Name: "qqq"}, &HasId{Id: 2, Name: "zzzz"} 1095 d1, err := serializeStruct(s1) 1096 if err != nil { 1097 t.Fatalf("Failed to serialize: %v", err) 1098 } 1099 d1Copy := make([]byte, len(d1)) 1100 copy(d1Copy, d1) 1101 _, err = serializeStruct(s2) 1102 if err != nil { 1103 t.Fatalf("Failed to serialize: %v", err) 1104 } 1105 if !bytes.Equal(d1, d1Copy) { 1106 t.Fatalf("Serialization bytes are not stable! Expected %x but got %x", d1Copy, d1) 1107 } 1108 } 1109 1110 type dummyPLS struct { 1111 Id int64 `datastore:"-" goon:"id"` 1112 ValueA string `datastore:"a"` 1113 ValueB string `datastore:"-"` 1114 } 1115 1116 func (d *dummyPLS) Save() ([]datastore.Property, error) { 1117 props, err := datastore.SaveStruct(d) 1118 if err != nil { 1119 return nil, err 1120 } 1121 props = append([]datastore.Property{{Name: "ValueB" + d.ValueB, NoIndex: true, Multiple: true, Value: nil}}, props...) 1122 return props, nil 1123 } 1124 1125 func (d *dummyPLS) Load(props []datastore.Property) error { 1126 for _, prop := range props { 1127 if strings.HasPrefix(prop.Name, "ValueB") && prop.NoIndex && prop.Multiple && prop.Value == nil { 1128 d.ValueB = prop.Name[len("ValueB"):] 1129 break 1130 } 1131 } 1132 return datastore.LoadStruct(d, props) 1133 } 1134 1135 // Tests that only matter for PLS and can't be done via ivItem 1136 func TestPropertyLoadSaver(t *testing.T) { 1137 c, done, err := aetest.NewContext() 1138 if err != nil { 1139 t.Fatalf("Could not start aetest - %v", err) 1140 } 1141 defer done() 1142 g := FromContext(c) 1143 1144 // Save the entity 1145 dA := &dummyPLS{Id: 1, ValueA: "one", ValueB: "two"} 1146 if _, err := g.Put(dA); err != nil { 1147 t.Fatalf("Unexpected error on Put: %v", err) 1148 } 1149 1150 for i := 0; i < 5; i++ { 1151 switch i { 1152 case 0: 1153 // Test immediately after Put, leave caches as is 1154 case 1: 1155 // Clear the local cache to test memcache 1156 g.FlushLocalCache() 1157 case 2: 1158 // Clear both caches to test datastore 1159 g.FlushLocalCache() 1160 memcache.Flush(g.Context) 1161 case 3: 1162 // Test local cache from the Get 1163 case 4: 1164 // Clear the local cache to test memcache from the Get 1165 g.FlushLocalCache() 1166 } 1167 1168 dB := &dummyPLS{Id: dA.Id} 1169 if err := g.Get(dB); err != nil { 1170 t.Fatalf("Unexpected error on Get #%d: %v", i, err) 1171 } 1172 if !reflect.DeepEqual(dA, dB) { 1173 t.Errorf("dA & dB don't match #%d!\n%s", i, getDiff(dA, dB, "dA", "dB")) 1174 } 1175 } 1176 } 1177 1178 type MigrationEntity interface { 1179 number() int32 1180 parent() *datastore.Key 1181 } 1182 1183 type MigrationA struct { 1184 _kind string `goon:"kind,Migration"` 1185 Parent *datastore.Key `datastore:"-" goon:"parent"` 1186 Id int64 `datastore:"-" goon:"id"` 1187 Number int32 `datastore:"number"` 1188 Word string `datastore:"word,noindex"` 1189 Car string `datastore:"car,noindex"` 1190 Holiday time.Time `datastore:"holiday,noindex"` 1191 α int `datastore:",noindex"` 1192 Level MigrationIntA `datastore:"level,noindex"` 1193 Floor MigrationIntA `datastore:"floor,noindex"` 1194 BunchOfBytes MigrationBSSA `datastore:"bb,noindex"` 1195 Sub MigrationSub `datastore:"sub,noindex"` 1196 Son MigrationPerson `datastore:"son,noindex"` 1197 Daughter MigrationPerson `datastore:"daughter,noindex"` 1198 Parents []MigrationPerson `datastore:"parents,noindex"` 1199 DeepSlice MigrationDeepA `datastore:"deep,noindex"` 1200 ZZs []ZigZag `datastore:"zigzag,noindex"` 1201 ZeroKey *datastore.Key `datastore:",noindex"` 1202 File []byte 1203 LoadCount int 1204 DeprecatedField string `datastore:"depf,noindex"` 1205 DeprecatedStruct MigrationSub `datastore:"deps,noindex"` 1206 FinalField string `datastore:"final,noindex"` // This should always be last, to test deprecating middle properties 1207 } 1208 1209 func (m MigrationA) parent() *datastore.Key { 1210 return m.Parent 1211 } 1212 1213 func (m MigrationA) number() int32 { 1214 return m.Number 1215 } 1216 1217 type MigrationSub struct { 1218 Data string `datastore:"data,noindex"` 1219 Noise []int `datastore:"noise,noindex"` 1220 Sub MigrationSubSub `datastore:"sub,noindex"` 1221 } 1222 1223 type MigrationSubSub struct { 1224 Data string `datastore:"data,noindex"` 1225 } 1226 1227 type MigrationPerson struct { 1228 Name string `datastore:"name,noindex"` 1229 Age int `datastore:"age,noindex"` 1230 } 1231 1232 type MigrationDeepA struct { 1233 Deep MigrationDeepB `datastore:"deep,noindex"` 1234 } 1235 1236 type MigrationDeepB struct { 1237 Deep MigrationDeepC `datastore:"deep,noindex"` 1238 } 1239 1240 type MigrationDeepC struct { 1241 Slice []int `datastore:"slice,noindex"` 1242 } 1243 1244 type ZigZag struct { 1245 Zig int `datastore:"zig,noindex"` 1246 Zag int `datastore:"zag,noindex"` 1247 } 1248 1249 type ZigZags struct { 1250 Zig []int `datastore:"zig,noindex"` 1251 Zag []int `datastore:"zag,noindex"` 1252 } 1253 1254 type MigrationIntA int 1255 type MigrationIntB int 1256 1257 type MigrationBSA []byte 1258 type MigrationBSB []byte 1259 type MigrationBSSA []MigrationBSA 1260 type MigrationBSSB []MigrationBSB 1261 1262 type MigrationB struct { 1263 _kind string `goon:"kind,Migration"` 1264 Parent *datastore.Key `datastore:"-" goon:"parent"` 1265 Identification int64 `datastore:"-" goon:"id"` 1266 FancyNumber int32 `datastore:"number"` 1267 Slang string `datastore:"word,noindex"` 1268 Cars []string `datastore:"car,noindex"` 1269 Holidays []time.Time `datastore:"holiday,noindex"` 1270 β int `datastore:"α,noindex"` 1271 Level MigrationIntB `datastore:"level,noindex"` 1272 Floors []MigrationIntB `datastore:"floor,noindex"` 1273 BunchOfBytes MigrationBSSB `datastore:"bb,noindex"` 1274 Animal string `datastore:"sub.data,noindex"` 1275 Music []int `datastore:"sub.noise,noindex"` 1276 Flower string `datastore:"sub.sub.data,noindex"` 1277 Sons []MigrationPerson `datastore:"son,noindex"` 1278 DaughterName string `datastore:"daughter.name,noindex"` 1279 DaughterAge int `datastore:"daughter.age,noindex"` 1280 OldFolks []MigrationPerson `datastore:"parents,noindex"` 1281 FarSlice MigrationDeepA `datastore:"deep,noindex"` 1282 ZZs ZigZags `datastore:"zigzag,noindex"` 1283 Keys []*datastore.Key `datastore:"ZeroKey,noindex"` 1284 Files [][]byte `datastore:"File,noindex"` 1285 LoadCount int `datastore:"LoadCount,noindex"` 1286 FinalField string `datastore:"final,noindex"` 1287 } 1288 1289 func (m MigrationB) parent() *datastore.Key { 1290 return m.Parent 1291 } 1292 1293 func (m MigrationB) number() int32 { 1294 return m.FancyNumber 1295 } 1296 1297 // MigrationA with PropertyLoadSaver interface 1298 type MigrationPlsA MigrationA 1299 1300 // MigrationB with PropertyLoadSaver interface 1301 type MigrationPlsB MigrationB 1302 1303 func (m MigrationPlsA) parent() *datastore.Key { 1304 return m.Parent 1305 } 1306 func (m MigrationPlsA) number() int32 { 1307 return m.Number 1308 } 1309 func (m *MigrationPlsA) Save() ([]datastore.Property, error) { 1310 return datastore.SaveStruct(m) 1311 } 1312 func (m *MigrationPlsA) Load(props []datastore.Property) error { 1313 err := datastore.LoadStruct(m, props) 1314 m.LoadCount++ 1315 return err 1316 } 1317 1318 func (m MigrationPlsB) parent() *datastore.Key { 1319 return m.Parent 1320 } 1321 func (m MigrationPlsB) number() int32 { 1322 return m.FancyNumber 1323 } 1324 func (m *MigrationPlsB) Save() ([]datastore.Property, error) { 1325 return datastore.SaveStruct(m) 1326 } 1327 func (m *MigrationPlsB) Load(props []datastore.Property) error { 1328 err := datastore.LoadStruct(m, props) 1329 m.LoadCount++ 1330 return err 1331 } 1332 1333 // Make sure these implement datastore.PropertyLoadSaver 1334 var _, _ datastore.PropertyLoadSaver = &MigrationPlsA{}, &MigrationPlsB{} 1335 1336 const ( 1337 migrationMethodGet = iota 1338 migrationMethodGetAll 1339 migrationMethodGetAllMulti 1340 migrationMethodNext 1341 migrationMethodCount 1342 ) 1343 1344 func TestMigration(t *testing.T) { 1345 c, done, err := aetest.NewContext() 1346 if err != nil { 1347 t.Fatalf("Could not start aetest - %v", err) 1348 } 1349 defer done() 1350 1351 origIFM := IgnoreFieldMismatch 1352 defer func() { 1353 IgnoreFieldMismatch = origIFM 1354 }() 1355 1356 g := FromContext(c) 1357 1358 // Create & save an entity with the original structure 1359 parentKey := g.Key(&HasId{Id: 9999}) 1360 migA := &MigrationA{Parent: parentKey, Id: 1, Number: 123, Word: "rabbit", Car: "BMW", 1361 Holiday: time.Now().UTC().Truncate(time.Microsecond), α: 1, Level: 9001, Floor: 5, 1362 BunchOfBytes: MigrationBSSA{MigrationBSA{0x01, 0x02}, MigrationBSA{0x03, 0x04}}, 1363 Sub: MigrationSub{Data: "fox", Noise: []int{1, 2, 3}, Sub: MigrationSubSub{Data: "rose"}}, 1364 Son: MigrationPerson{Name: "John", Age: 5}, Daughter: MigrationPerson{Name: "Nancy", Age: 6}, 1365 Parents: []MigrationPerson{{Name: "Sven", Age: 56}, {Name: "Sonya", Age: 49}}, 1366 DeepSlice: MigrationDeepA{Deep: MigrationDeepB{Deep: MigrationDeepC{Slice: []int{1, 2, 3}}}}, 1367 ZZs: []ZigZag{{Zig: 1}, {Zag: 1}}, File: []byte{0xF0, 0x0D}, 1368 DeprecatedField: "dep", DeprecatedStruct: MigrationSub{Data: "dep", Noise: []int{1, 2, 3}}, FinalField: "fin"} 1369 if _, err := g.Put(migA); err != nil { 1370 t.Fatalf("Unexpected error on Put: %v", err) 1371 } 1372 // Also save an already migrated structure 1373 migB := &MigrationB{Parent: migA.Parent, Identification: migA.Id + 1, FancyNumber: migA.Number + 1} 1374 if _, err := g.Put(migB); err != nil { 1375 t.Fatalf("Unexpected error on Put: %v", err) 1376 } 1377 1378 // Run migration tests with both IgnoreFieldMismatch on & off 1379 for _, IgnoreFieldMismatch = range []bool{true, false} { 1380 testcase := []struct { 1381 name string 1382 src MigrationEntity 1383 dst MigrationEntity 1384 }{ 1385 { 1386 name: "NormalCache -> NormalCache", 1387 src: &MigrationA{Parent: parentKey, Id: 1}, 1388 dst: &MigrationB{Parent: parentKey, Identification: 1}, 1389 }, 1390 { 1391 name: "PropertyListCache -> NormalCache", 1392 src: &MigrationPlsA{Parent: parentKey, Id: 1}, 1393 dst: &MigrationB{Parent: parentKey, Identification: 1}, 1394 }, 1395 { 1396 name: "PropertyListCache -> PropertyListCache", 1397 src: &MigrationPlsA{Parent: parentKey, Id: 1}, 1398 dst: &MigrationPlsB{Parent: parentKey, Identification: 1}, 1399 }, 1400 { 1401 name: "NormalCache -> PropertyListCache", 1402 src: &MigrationA{Parent: parentKey, Id: 1}, 1403 dst: &MigrationPlsB{Parent: parentKey, Identification: 1}, 1404 }, 1405 } 1406 for _, tt := range testcase { 1407 // Clear all the caches 1408 g.FlushLocalCache() 1409 memcache.Flush(c) 1410 1411 // Get it back, so it's in the cache 1412 if err := g.Get(tt.src); err != nil { 1413 t.Fatalf("Unexpected error on Get: %v", err) 1414 } 1415 1416 // Test whether local cache supports migration 1417 var fetched MigrationEntity 1418 debugInfo := fmt.Sprintf("%s LC-%v", tt.name, IgnoreFieldMismatch) 1419 fetched = verifyMigration(t, g, tt.src, tt.dst, migrationMethodGet, debugInfo) 1420 checkMigrationResult(t, g, tt.src, fetched, debugInfo) 1421 1422 // Clear the local cache, to test memcache 1423 g.FlushLocalCache() 1424 1425 // Test whether memcache supports migration 1426 debugInfo = fmt.Sprintf("%s MC-%v", tt.name, IgnoreFieldMismatch) 1427 fetched = verifyMigration(t, g, tt.src, tt.dst, migrationMethodGet, debugInfo) 1428 checkMigrationResult(t, g, tt.src, fetched, debugInfo) 1429 1430 // Test whether datastore supports migration 1431 for method := 0; method < migrationMethodCount; method++ { 1432 // Test both inside a transaction and outside 1433 for tx := 0; tx < 2; tx++ { 1434 // Clear all the caches 1435 g.FlushLocalCache() 1436 memcache.Flush(c) 1437 1438 debugInfo := fmt.Sprintf("%s DS-%v-%v-%v", tt.name, tx, method, IgnoreFieldMismatch) 1439 if tx == 1 { 1440 if err := g.RunInTransaction(func(tg *Goon) error { 1441 fetched = verifyMigration(t, tg, tt.src, tt.dst, method, debugInfo) 1442 return nil 1443 }, &datastore.TransactionOptions{XG: false}); err != nil { 1444 t.Fatalf("Unexpected error with TXN - %v", err) 1445 } 1446 } else { 1447 fetched = verifyMigration(t, g, tt.src, tt.dst, method, debugInfo) 1448 } 1449 checkMigrationResult(t, g, tt.src, fetched, debugInfo) 1450 } 1451 } 1452 } 1453 } 1454 } 1455 1456 func verifyMigration(t *testing.T, g *Goon, src, dst MigrationEntity, method int, debugInfo string) (fetched MigrationEntity) { 1457 sliceType := reflect.SliceOf(reflect.TypeOf(dst)) 1458 slicePtr := reflect.New(sliceType) 1459 slicePtr.Elem().Set(reflect.MakeSlice(sliceType, 0, 10)) 1460 slice := slicePtr.Interface() 1461 sliceVal := slicePtr.Elem() 1462 1463 switch method { 1464 case migrationMethodGet: 1465 if err := g.Get(dst); err != nil && (IgnoreFieldMismatch || !errFieldMismatch(err)) { 1466 t.Fatalf("%v > Unexpected error on Get: %v", debugInfo, err) 1467 return 1468 } 1469 return dst 1470 case migrationMethodGetAll: 1471 if _, err := g.GetAll(datastore.NewQuery("Migration").Ancestor(src.parent()).Filter("number=", src.number()), slice); err != nil && (IgnoreFieldMismatch || !errFieldMismatch(err)) { 1472 t.Fatalf("%v > Unexpected error on GetAll: %v", debugInfo, err) 1473 return 1474 } else if sliceVal.Len() != 1 { 1475 t.Fatalf("%v > Unexpected query result, expected %v entities, got %v", debugInfo, 1, sliceVal.Len()) 1476 return 1477 } 1478 return sliceVal.Index(0).Interface().(MigrationEntity) 1479 case migrationMethodGetAllMulti: 1480 // Get both Migration entities 1481 if _, err := g.GetAll(datastore.NewQuery("Migration").Ancestor(src.parent()).Order("number"), slice); err != nil && (IgnoreFieldMismatch || !errFieldMismatch(err)) { 1482 t.Fatalf("%v > Unexpected error on GetAll: %v", debugInfo, err) 1483 return 1484 } else if sliceVal.Len() != 2 { 1485 t.Fatalf("%v > Unexpected query result, expected %v entities, got %v", debugInfo, 2, sliceVal.Len()) 1486 return 1487 } 1488 return sliceVal.Index(0).Interface().(MigrationEntity) 1489 case migrationMethodNext: 1490 it := g.Run(datastore.NewQuery("Migration").Ancestor(src.parent()).Filter("number=", src.number())) 1491 if _, err := it.Next(dst); err != nil && (IgnoreFieldMismatch || !errFieldMismatch(err)) { 1492 t.Fatalf("%v > Unexpected error on Next: %v", debugInfo, err) 1493 return 1494 } 1495 // Make sure the iterator ends correctly 1496 if _, err := it.Next(dst); err != datastore.Done { 1497 t.Fatalf("Next: expected iterator to end with the error datastore.Done, got %v", err) 1498 } 1499 return dst 1500 } 1501 return nil 1502 } 1503 1504 func checkMigrationResult(t *testing.T, g *Goon, src, fetched interface{}, debugInfo string) { 1505 var migA *MigrationA 1506 switch v := src.(type) { 1507 case *MigrationA: 1508 migA = v 1509 case *MigrationPlsA: 1510 migA = (*MigrationA)(v) 1511 } 1512 var migB *MigrationB 1513 switch v := fetched.(type) { 1514 case *MigrationB: 1515 migB = v 1516 case *MigrationPlsB: 1517 migB = (*MigrationB)(v) 1518 1519 if migB.LoadCount != 1 { 1520 t.Errorf("%v > Expected LoadCount 1 but got %d", debugInfo, migB.LoadCount) 1521 } 1522 } 1523 1524 if migA.Id != migB.Identification { 1525 t.Errorf("%v > Ids don't match: %v != %v", debugInfo, migA.Id, migB.Identification) 1526 } else if migA.Number != migB.FancyNumber { 1527 t.Errorf("%v > Numbers don't match: %v != %v", debugInfo, migA.Number, migB.FancyNumber) 1528 } else if migA.Word != migB.Slang { 1529 t.Errorf("%v > Words don't match: %v != %v", debugInfo, migA.Word, migB.Slang) 1530 } else if len(migB.Cars) != 1 { 1531 t.Fatalf("%v > Expected 1 car! Got: %v", debugInfo, len(migB.Cars)) 1532 } else if migA.Car != migB.Cars[0] { 1533 t.Errorf("%v > Cars don't match: %v != %v", debugInfo, migA.Car, migB.Cars[0]) 1534 } else if len(migB.Holidays) != 1 { 1535 t.Fatalf("%v > Expected 1 holiday! Got: %v", debugInfo, len(migB.Holidays)) 1536 } else if migA.Holiday != migB.Holidays[0] { 1537 t.Errorf("%v > Holidays don't match: %v != %v", debugInfo, migA.Holiday, migB.Holidays[0]) 1538 } else if migA.α != migB.β { 1539 t.Errorf("%v > Greek doesn't match: %v != %v", debugInfo, migA.α, migB.β) 1540 } else if int(migA.Level) != int(migB.Level) { 1541 t.Errorf("%v > Level doesn't match: %v != %v", debugInfo, migA.Level, migB.Level) 1542 } else if len(migB.Floors) != 1 { 1543 t.Fatalf("%v > Expected 1 floor! Got: %v", debugInfo, len(migB.Floors)) 1544 } else if int(migA.Floor) != int(migB.Floors[0]) { 1545 t.Errorf("%v > Floor doesn't match: %v != %v", debugInfo, migA.Floor, migB.Floors[0]) 1546 } else if len(migA.BunchOfBytes) != len(migB.BunchOfBytes) || len(migA.BunchOfBytes) != 2 { 1547 t.Fatalf("%v > BunchOfBytes len doesn't match (expected 2): %v != %v", debugInfo, len(migA.BunchOfBytes), len(migB.BunchOfBytes)) 1548 } else if !reflect.DeepEqual([]byte(migA.BunchOfBytes[0]), []byte(migB.BunchOfBytes[0])) || 1549 !reflect.DeepEqual([]byte(migA.BunchOfBytes[1]), []byte(migB.BunchOfBytes[1])) { 1550 t.Errorf("%v > BunchOfBytes doesn't match: %+v != %+v", debugInfo, migA.BunchOfBytes, migB.BunchOfBytes) 1551 } else if migA.Sub.Data != migB.Animal { 1552 t.Errorf("%v > Animal doesn't match: %v != %v", debugInfo, migA.Sub.Data, migB.Animal) 1553 } else if !reflect.DeepEqual(migA.Sub.Noise, migB.Music) { 1554 t.Errorf("%v > Music doesn't match: %v != %v", debugInfo, migA.Sub.Noise, migB.Music) 1555 } else if migA.Sub.Sub.Data != migB.Flower { 1556 t.Errorf("%v > Flower doesn't match: %v != %v", debugInfo, migA.Sub.Sub.Data, migB.Flower) 1557 } else if len(migB.Sons) != 1 { 1558 t.Fatalf("%v > Expected 1 son! Got: %v", debugInfo, len(migB.Sons)) 1559 } else if migA.Son.Name != migB.Sons[0].Name { 1560 t.Errorf("%v > Son names don't match: %v != %v", debugInfo, migA.Son.Name, migB.Sons[0].Name) 1561 } else if migA.Son.Age != migB.Sons[0].Age { 1562 t.Errorf("%v > Son ages don't match: %v != %v", debugInfo, migA.Son.Age, migB.Sons[0].Age) 1563 } else if migA.Daughter.Name != migB.DaughterName { 1564 t.Errorf("%v > Daughter names don't match: %v != %v", debugInfo, migA.Daughter.Name, migB.DaughterName) 1565 } else if migA.Daughter.Age != migB.DaughterAge { 1566 t.Errorf("%v > Daughter ages don't match: %v != %v", debugInfo, migA.Daughter.Age, migB.DaughterAge) 1567 } else if !reflect.DeepEqual(migA.Parents, migB.OldFolks) { 1568 t.Errorf("%v > Parents don't match: %v != %v", debugInfo, migA.Parents, migB.OldFolks) 1569 } else if !reflect.DeepEqual(migA.DeepSlice, migB.FarSlice) { 1570 t.Errorf("%v > Deep slice doesn't match: %v != %v", debugInfo, migA.DeepSlice, migB.FarSlice) 1571 } else if len(migB.ZZs.Zig) != 2 { 1572 t.Fatalf("%v > Expected 2 Zigs, got: %v", debugInfo, len(migB.ZZs.Zig)) 1573 } else if len(migB.ZZs.Zag) != 2 { 1574 t.Fatalf("%v > Expected 2 Zags, got: %v", debugInfo, len(migB.ZZs.Zag)) 1575 } else if migA.ZZs[0].Zig != migB.ZZs.Zig[0] { 1576 t.Errorf("%v > Invalid zig #1: %v != %v", debugInfo, migA.ZZs[0].Zig, migB.ZZs.Zig[0]) 1577 } else if migA.ZZs[1].Zig != migB.ZZs.Zig[1] { 1578 t.Errorf("%v > Invalid zig #2: %v != %v", debugInfo, migA.ZZs[1].Zig, migB.ZZs.Zig[1]) 1579 } else if migA.ZZs[0].Zag != migB.ZZs.Zag[0] { 1580 t.Errorf("%v > Invalid zag #1: %v != %v", debugInfo, migA.ZZs[0].Zag, migB.ZZs.Zag[0]) 1581 } else if migA.ZZs[1].Zag != migB.ZZs.Zag[1] { 1582 t.Errorf("%v > Invalid zag #2: %v != %v", debugInfo, migA.ZZs[1].Zag, migB.ZZs.Zag[1]) 1583 } else if len(migB.Keys) != 1 { 1584 t.Fatalf("%v > Expected 1 keys, got %v", debugInfo, len(migB.Keys)) 1585 } else if len(migB.Files) != 1 { 1586 t.Fatalf("%v > Expected 1 file, got %v", debugInfo, len(migB.Files)) 1587 } else if !reflect.DeepEqual(migA.File, migB.Files[0]) { 1588 t.Errorf("%v > Files don't match: %v != %v", debugInfo, migA.File, migB.Files[0]) 1589 } else if migA.FinalField != migB.FinalField { 1590 t.Errorf("%v > FinalField doesn't match: %v != %v", debugInfo, migA.FinalField, migB.FinalField) 1591 } 1592 } 1593 1594 func TestTXNRace(t *testing.T) { 1595 c, done, err := aetest.NewContext() 1596 if err != nil { 1597 t.Fatalf("Could not start aetest - %v", err) 1598 } 1599 defer done() 1600 g := FromContext(c) 1601 1602 // Create & store some test data 1603 hid := &HasId{Id: 1, Name: "foo"} 1604 if _, err := g.Put(hid); err != nil { 1605 t.Fatalf("Unexpected error on Put %v", err) 1606 } 1607 1608 // Make sure the local cache is empty 1609 g.FlushLocalCache() 1610 1611 // Get this data back, to populate caches 1612 if err := g.Get(hid); err != nil { 1613 t.Fatalf("Unexpected error on Get %v", err) 1614 } 1615 1616 // Clear the local cache, as we are testing for proper memcache usage 1617 g.FlushLocalCache() 1618 1619 // Update the test data inside a transction 1620 if err := g.RunInTransaction(func(tg *Goon) error { 1621 // Get the current data 1622 thid := &HasId{Id: 1} 1623 if err := tg.Get(thid); err != nil { 1624 t.Fatalf("Unexpected error on TXN Get %v", err) 1625 return err 1626 } 1627 1628 // Update the data 1629 thid.Name = "bar" 1630 if _, err := tg.Put(thid); err != nil { 1631 t.Fatalf("Unexpected error on TXN Put %v", err) 1632 return err 1633 } 1634 1635 // Concurrent request emulation 1636 // We are running this inside the transaction block to always get the correct timing for testing. 1637 // In the real world, this concurrent request may run in another instance. 1638 // The transaction block may contain multiple other operations after the preceding Put(), 1639 // allowing for ample time for the concurrent request to run before the transaction is committed. 1640 hid = &HasId{Id: 1} 1641 if err := g.Get(hid); err != nil { 1642 t.Fatalf("Unexpected error on Get %v", err) 1643 } else if hid.Name != "foo" { 1644 t.Fatalf("Expected 'foo', got %v", hid.Name) 1645 } 1646 1647 // Commit the transaction 1648 return nil 1649 }, &datastore.TransactionOptions{XG: false}); err != nil { 1650 t.Fatalf("Unexpected error with TXN - %v", err) 1651 } 1652 1653 // Clear the local cache, as we are testing for proper memcache usage 1654 g.FlushLocalCache() 1655 1656 // Get the data back again, to confirm it was changed in the transaction 1657 hid = &HasId{Id: 1} 1658 if err := g.Get(hid); err != nil { 1659 t.Fatalf("Unexpected error on Get %v", err) 1660 } else if hid.Name != "bar" { 1661 t.Fatalf("Expected 'bar', got %v", hid.Name) 1662 } 1663 1664 // Clear the local cache, as we are testing for proper memcache usage 1665 g.FlushLocalCache() 1666 1667 // Delete the test data inside a transction 1668 if err := g.RunInTransaction(func(tg *Goon) error { 1669 thid := &HasId{Id: 1} 1670 if err := tg.Delete(thid); err != nil { 1671 t.Fatalf("Unexpected error on TXN Delete %v", err) 1672 return err 1673 } 1674 1675 // Concurrent request emulation 1676 hid = &HasId{Id: 1} 1677 if err := g.Get(hid); err != nil { 1678 t.Fatalf("Unexpected error on Get %v", err) 1679 } else if hid.Name != "bar" { 1680 t.Fatalf("Expected 'bar', got %v", hid.Name) 1681 } 1682 1683 // Commit the transaction 1684 return nil 1685 }, &datastore.TransactionOptions{XG: false}); err != nil { 1686 t.Fatalf("Unexpected error with TXN - %v", err) 1687 } 1688 1689 // Clear the local cache, as we are testing for proper memcache usage 1690 g.FlushLocalCache() 1691 1692 // Attempt to get the data back again, to confirm it was deleted in the transaction 1693 hid = &HasId{Id: 1} 1694 if err := g.Get(hid); err != datastore.ErrNoSuchEntity { 1695 t.Errorf("Expected ErrNoSuchEntity, got %v", err) 1696 } 1697 } 1698 1699 func TestNegativeCacheHit(t *testing.T) { 1700 c, done, err := aetest.NewContext() 1701 if err != nil { 1702 t.Fatalf("Could not start aetest - %v", err) 1703 } 1704 defer done() 1705 g := FromContext(c) 1706 1707 // Run twice to test local cache & memcache 1708 for _, mode := range []string{"MC", "LC"} { 1709 debugInfo := fmt.Sprintf("%s", mode) 1710 1711 hid := &HasId{Id: rand.Int63()} 1712 if err := g.Get(hid); err != datastore.ErrNoSuchEntity { 1713 t.Fatalf("%s > Expected ErrNoSuchEntity, got %v", debugInfo, err) 1714 } 1715 1716 // Do a sneaky save straight to the datastore 1717 if _, err := datastore.Put(c, datastore.NewKey(c, "HasId", "", hid.Id, nil), &HasId{Id: hid.Id, Name: "one"}); err != nil { 1718 t.Fatalf("%s > Unexpected error on datastore.Put: %v", debugInfo, err) 1719 } 1720 1721 switch mode { 1722 case "MC": 1723 g.FlushLocalCache() 1724 case "LC": 1725 memcache.Flush(c) 1726 } 1727 1728 // Get the entity again via goon, to make sure we cached the non-existance 1729 if err := g.Get(hid); err != datastore.ErrNoSuchEntity { 1730 t.Errorf("%s > Expected ErrNoSuchEntity, got %v", debugInfo, err) 1731 } 1732 } 1733 } 1734 1735 func TestNegativeCacheClear(t *testing.T) { 1736 c, done, err := aetest.NewContext() 1737 if err != nil { 1738 t.Fatalf("Could not start aetest - %v", err) 1739 } 1740 defer done() 1741 g := FromContext(c) 1742 1743 // Run twice to test local cache & memcache 1744 for _, mode := range []string{"MC", "LC"} { 1745 for tx := 0; tx < 2; tx++ { 1746 debugInfo := fmt.Sprintf("%s-%v", mode, tx == 1) 1747 1748 hid := &HasId{Name: "one"} 1749 var id int64 1750 1751 ided := make(chan bool) 1752 cached := make(chan bool) 1753 ended := make(chan bool) 1754 1755 go func() { 1756 var err error 1757 if tx == 0 { 1758 id = rand.Int63() 1759 hid.Id = id 1760 ided <- true 1761 <-cached 1762 _, err = g.Put(hid) 1763 } else { 1764 err = g.RunInTransaction(func(tg *Goon) error { 1765 _, err := tg.Put(hid) 1766 id = hid.Id 1767 ided <- true 1768 <-cached 1769 return err 1770 }, nil) 1771 } 1772 if err != nil { 1773 t.Errorf("%s > Unexpected error on RunInTransaction: %v", debugInfo, err) 1774 } 1775 ended <- true 1776 }() 1777 1778 // Populate the cache with a negative hit 1779 { 1780 <-ided 1781 negative := &HasId{Id: id} 1782 if err := g.Get(negative); err != datastore.ErrNoSuchEntity { 1783 t.Fatalf("%s > Expected ErrNoSuchEntity, got %v", debugInfo, err) 1784 } 1785 cached <- true 1786 } 1787 1788 // Make sure the negative cache no longer exists 1789 { 1790 <-ended 1791 want := &HasId{Id: id} 1792 if mode == "MC" { 1793 g.FlushLocalCache() 1794 } 1795 if err := g.Get(want); err != nil { 1796 t.Fatalf("%s > Unexpected error on get: %v", debugInfo, err) 1797 } 1798 if want.Name != hid.Name { 1799 t.Fatalf("%s > Expected '%v' but got '%v'", debugInfo, hid.Name, want.Name) 1800 } 1801 } 1802 } 1803 } 1804 } 1805 1806 func TestCaches(t *testing.T) { 1807 c, done, err := aetest.NewContext() 1808 if err != nil { 1809 t.Fatalf("Could not start aetest - %v", err) 1810 } 1811 defer done() 1812 g := FromContext(c) 1813 1814 // Put *struct{} 1815 phid := &HasId{Name: "cacheFail"} 1816 _, err = g.Put(phid) 1817 if err != nil { 1818 t.Fatalf("Unexpected error on put - %v", err) 1819 } 1820 1821 // Test the scenario where Put would populate local cache 1822 1823 // fetch *struct{} from cache 1824 ghid := &HasId{Id: phid.Id} 1825 err = g.Get(ghid) 1826 if err != nil { 1827 t.Fatalf("Unexpected error on get - %v", err) 1828 } 1829 if !reflect.DeepEqual(phid, ghid) { 1830 t.Fatalf("Invalid result!\n%s", getDiff(phid, ghid, "phid", "ghid")) 1831 } 1832 1833 // fetch []struct{} from cache 1834 ghids := []HasId{{Id: phid.Id}} 1835 err = g.GetMulti(&ghids) 1836 if err != nil { 1837 t.Fatalf("Unexpected error on get - %v", err) 1838 } 1839 if !reflect.DeepEqual(*phid, ghids[0]) { 1840 t.Fatalf("Invalid result!\n%s", getDiff(*phid, ghids[0], "*phid", "ghids[0]")) 1841 } 1842 1843 // Now flush localcache and fetch them again to test memcache 1844 g.FlushLocalCache() 1845 1846 // fetch *struct{} from memcache 1847 ghid = &HasId{Id: phid.Id} 1848 err = g.Get(ghid) 1849 if err != nil { 1850 t.Fatalf("Unexpected error on get - %v", err) 1851 } 1852 if !reflect.DeepEqual(phid, ghid) { 1853 t.Fatalf("Invalid result!\n%s", getDiff(phid, ghid, "phid", "ghid")) 1854 } 1855 1856 // Need to flush local cache again because it was populated by Get 1857 g.FlushLocalCache() 1858 1859 // fetch []struct{} from memcache 1860 ghids = []HasId{{Id: phid.Id}} 1861 err = g.GetMulti(&ghids) 1862 if err != nil { 1863 t.Fatalf("Unexpected error on get - %v", err) 1864 } 1865 if !reflect.DeepEqual(*phid, ghids[0]) { 1866 t.Fatalf("Invalid result!\n%s", getDiff(*phid, ghids[0], "*phid", "ghids[0]")) 1867 } 1868 1869 // Do a sneaky save straight to the datastore 1870 mhid := &HasId{Id: phid.Id, Name: "modified"} 1871 if _, err := datastore.Put(c, datastore.NewKey(c, "HasId", "", phid.Id, nil), mhid); err != nil { 1872 t.Fatalf("Unexpected error on datastore.Put: %v", err) 1873 } 1874 1875 // Clear the memcache entry specifically 1876 if err := g.ClearCache(phid, true, false); err != nil { 1877 t.Fatalf("Failed to clear cache: %v", err) 1878 } 1879 1880 // fetch *struct{} from cache 1881 ghid = &HasId{Id: phid.Id} 1882 err = g.Get(ghid) 1883 if err != nil { 1884 t.Fatalf("Unexpected error on get - %v", err) 1885 } 1886 if !reflect.DeepEqual(phid, ghid) { 1887 t.Fatalf("Invalid result!\n%s", getDiff(phid, ghid, "phid", "ghid")) 1888 } 1889 1890 // Clear the local cache entry specifically 1891 if err := g.ClearCache(phid, false, true); err != nil { 1892 t.Fatalf("Failed to clear cache: %v", err) 1893 } 1894 1895 // fetch *struct{} from datastore 1896 ghid = &HasId{Id: phid.Id} 1897 err = g.Get(ghid) 1898 if err != nil { 1899 t.Fatalf("Unexpected error on get - %v", err) 1900 } 1901 if !reflect.DeepEqual(mhid, ghid) { 1902 t.Fatalf("Invalid result!\n%s", getDiff(mhid, ghid, "mhid", "ghid")) 1903 } 1904 1905 // Do a sneaky save straight to the datastore 1906 nhid := &HasId{Id: phid.Id, Name: "nudged"} 1907 if _, err := datastore.Put(c, datastore.NewKey(c, "HasId", "", phid.Id, nil), nhid); err != nil { 1908 t.Fatalf("Unexpected error on datastore.Put: %v", err) 1909 } 1910 1911 // Clear the local cache entry specifically 1912 if err := g.ClearCache(phid, false, true); err != nil { 1913 t.Fatalf("Failed to clear cache: %v", err) 1914 } 1915 1916 // fetch *struct{} from memcache 1917 ghid = &HasId{Id: phid.Id} 1918 err = g.Get(ghid) 1919 if err != nil { 1920 t.Fatalf("Unexpected error on get - %v", err) 1921 } 1922 if !reflect.DeepEqual(mhid, ghid) { 1923 t.Fatalf("Invalid result!\n%s", getDiff(mhid, ghid, "mhid", "ghid")) 1924 } 1925 } 1926 1927 func TestGoon(t *testing.T) { 1928 c, done, err := aetest.NewContext() 1929 if err != nil { 1930 t.Fatalf("Could not start aetest - %v", err) 1931 } 1932 defer done() 1933 n := FromContext(c) 1934 1935 // key tests 1936 noid := NoId{} 1937 if k, err := n.KeyError(noid); err == nil && !k.Incomplete() { 1938 t.Fatalf("expected incomplete on noid") 1939 } 1940 if n.Key(noid) == nil { 1941 t.Fatalf("expected to find a key") 1942 } 1943 1944 var keyTests = []keyTest{ 1945 { 1946 HasDefaultKind{}, 1947 datastore.NewKey(c, "DefaultKind", "", 0, nil), 1948 }, 1949 { 1950 HasId{Id: 1}, 1951 datastore.NewKey(c, "HasId", "", 1, nil), 1952 }, 1953 { 1954 HasKind{Id: 1, Kind: "OtherKind"}, 1955 datastore.NewKey(c, "OtherKind", "", 1, nil), 1956 }, 1957 1958 { 1959 HasDefaultKind{Id: 1, Kind: "OtherKind"}, 1960 datastore.NewKey(c, "OtherKind", "", 1, nil), 1961 }, 1962 { 1963 HasDefaultKind{Id: 1}, 1964 datastore.NewKey(c, "DefaultKind", "", 1, nil), 1965 }, 1966 { 1967 HasString{Id: "new"}, 1968 datastore.NewKey(c, "HasString", "new", 0, nil), 1969 }, 1970 } 1971 1972 for _, kt := range keyTests { 1973 if k, err := n.KeyError(kt.obj); err != nil { 1974 t.Fatalf("error: %v", err) 1975 } else if !k.Equal(kt.key) { 1976 t.Fatalf("keys not equal") 1977 } 1978 } 1979 1980 if _, err := n.KeyError(TwoId{IntId: 1, StringId: "1"}); err == nil { 1981 t.Fatalf("expected key error") 1982 } 1983 1984 // datastore tests 1985 keys, _ := datastore.NewQuery("HasId").KeysOnly().GetAll(c, nil) 1986 datastore.DeleteMulti(c, keys) 1987 memcache.Flush(c) 1988 if err := n.Get(&HasId{Id: 0}); err == nil { 1989 t.Fatalf("ds: expected error, we're fetching from the datastore on an incomplete key!") 1990 } 1991 if err := n.Get(&HasId{Id: 1}); err != datastore.ErrNoSuchEntity { 1992 t.Fatalf("ds: expected no such entity") 1993 } 1994 es := []*HasId{ 1995 {Id: 1, Name: "one"}, 1996 {Id: 2, Name: "two"}, 1997 } 1998 var esk []*datastore.Key 1999 for _, e := range es { 2000 esk = append(esk, n.Key(e)) 2001 } 2002 nes := []*HasId{ 2003 {Id: 1}, 2004 {Id: 2}, 2005 } 2006 if err := n.GetMulti(es); err == nil { 2007 t.Fatalf("ds: expected error") 2008 } else if !NotFound(err, 0) { 2009 t.Fatalf("ds: not found error 0") 2010 } else if !NotFound(err, 1) { 2011 t.Fatalf("ds: not found error 1") 2012 } else if NotFound(err, 2) { 2013 t.Fatalf("ds: not found error 2") 2014 } 2015 2016 if keys, err := n.PutMulti(es); err != nil { 2017 t.Fatalf("put: unexpected error") 2018 } else if len(keys) != len(esk) { 2019 t.Fatalf("put: got unexpected number of keys") 2020 } else { 2021 for i, k := range keys { 2022 if !k.Equal(esk[i]) { 2023 t.Fatalf("put: got unexpected keys") 2024 } 2025 } 2026 } 2027 if err := n.GetMulti(nes); err != nil { 2028 t.Fatalf("put: unexpected error") 2029 } else if !reflect.DeepEqual(es, nes) { 2030 t.Fatalf("put: bad results\n%s", getDiff(es, nes, "es", "nes")) 2031 } else { 2032 nesk0 := n.Key(nes[0]) 2033 if !nesk0.Equal(datastore.NewKey(c, "HasId", "", 1, nil)) { 2034 t.Fatalf("put: bad key") 2035 } 2036 nesk1 := n.Key(nes[1]) 2037 if !nesk1.Equal(datastore.NewKey(c, "HasId", "", 2, nil)) { 2038 t.Fatalf("put: bad key") 2039 } 2040 } 2041 if _, err := n.Put(HasId{Id: 3}); err == nil { 2042 t.Fatalf("put: expected error") 2043 } 2044 // force partial fetch from memcache and then datastore 2045 memcache.Flush(c) 2046 if err := n.Get(nes[0]); err != nil { 2047 t.Fatalf("get: unexpected error") 2048 } 2049 if err := n.GetMulti(nes); err != nil { 2050 t.Fatalf("get: unexpected error") 2051 } 2052 2053 // put a HasId resource, then test pulling it from memory, memcache, and datastore 2054 hi := &HasId{Name: "hasid"} // no id given, should be automatically created by the datastore 2055 if _, err := n.Put(hi); err != nil { 2056 t.Fatalf("put: unexpected error - %v", err) 2057 } 2058 if n.Key(hi) == nil { 2059 t.Fatalf("key should not be nil") 2060 } else if n.Key(hi).Incomplete() { 2061 t.Fatalf("key should not be incomplete") 2062 } 2063 2064 hi2 := &HasId{Id: hi.Id} 2065 if err := n.Get(hi2); err != nil { 2066 t.Fatalf("get: unexpected error - %v", err) 2067 } 2068 if hi2.Name != hi.Name { 2069 t.Fatalf("Could not fetch HasId object from memory - %#v != %#v", hi, hi2) 2070 } 2071 2072 hi3 := &HasId{Id: hi.Id} 2073 n.cache.Delete(cacheKey(n.Key(hi))) 2074 if err := n.Get(hi3); err != nil { 2075 t.Fatalf("get: unexpected error - %v", err) 2076 } 2077 if hi3.Name != hi.Name { 2078 t.Fatalf("Could not fetch HasId object from memory - %#v != %#v", hi, hi3) 2079 } 2080 2081 hi4 := &HasId{Id: hi.Id} 2082 n.cache.Delete(cacheKey(n.Key(hi4))) 2083 if memcache.Flush(n.Context) != nil { 2084 t.Fatalf("Unable to flush memcache") 2085 } 2086 if err := n.Get(hi4); err != nil { 2087 t.Fatalf("get: unexpected error - %v", err) 2088 } 2089 if hi4.Name != hi.Name { 2090 t.Fatalf("Could not fetch HasId object from datastore- %#v != %#v", hi, hi4) 2091 } 2092 2093 // Now do the opposite also using hi 2094 // Test pulling from local cache and memcache when datastore result is different 2095 // Note that this shouldn't happen with real goon usage, 2096 // but this tests that goon isn't still pulling from the datastore (or memcache) unnecessarily 2097 // hi in datastore Name = hasid 2098 hiPull := &HasId{Id: hi.Id} 2099 { 2100 ckey := cacheKey(n.Key(hi)) 2101 hiTamper := &HasId{Id: hi.Id, Name: "changedincache"} 2102 data, err := serializeStruct(hiTamper) 2103 if err != nil { 2104 t.Fatalf("Unexpected error serializing: %v", err) 2105 } 2106 n.cache.Set(&cacheItem{key: ckey, value: data}) 2107 } 2108 if err := n.Get(hiPull); err != nil { 2109 t.Fatalf("get: unexpected error - %v", err) 2110 } 2111 if hiPull.Name != "changedincache" { 2112 t.Fatalf("hiPull.Name should be 'changedincache' but got %s", hiPull.Name) 2113 } 2114 { 2115 ckey := cacheKey(n.Key(hi)) 2116 hiPush := &HasId{Id: hi.Id, Name: "changedinmemcache"} 2117 data, err := serializeStruct(hiPush) 2118 if err != nil { 2119 t.Fatalf("Unexpected error serializing: %v", err) 2120 } 2121 n.putMemcache([]*cacheItem{{key: ckey, value: data}}) 2122 n.cache.Delete(ckey) 2123 } 2124 2125 hiPull = &HasId{Id: hi.Id} 2126 if err := n.Get(hiPull); err != nil { 2127 t.Fatalf("get: unexpected error - %v", err) 2128 } 2129 if hiPull.Name != "changedinmemcache" { 2130 t.Fatalf("hiPull.Name should be 'changedinmemcache' but got %s", hiPull.Name) 2131 } 2132 2133 // Since the datastore can't assign a key to a String ID, test to make sure goon stops it from happening 2134 hasString := new(HasString) 2135 _, err = n.Put(hasString) 2136 if err == nil { 2137 t.Fatalf("Cannot put an incomplete string Id object as the datastore will populate an int64 id instead- %v", hasString) 2138 } 2139 hasString.Id = "hello" 2140 _, err = n.Put(hasString) 2141 if err != nil { 2142 t.Fatalf("Error putting hasString object - %v", hasString) 2143 } 2144 2145 // Test various ways to delete an entity 2146 for i := int64(0); i < 4; i++ { 2147 hid := &HasId{Id: 551 + i} 2148 key, err := n.Put(hid) 2149 if err != nil { 2150 t.Fatalf("Put failed: %v", err) 2151 } 2152 switch i { 2153 case 0: 2154 err = n.Delete(hid) 2155 case 1: 2156 err = n.DeleteMulti([]*HasId{hid}) 2157 case 2: 2158 err = n.Delete(key) 2159 case 3: 2160 err = n.DeleteMulti([]*datastore.Key{key}) 2161 } 2162 if err != nil { 2163 t.Fatalf("Delete %d failed: %v", i, err) 2164 } 2165 if err := n.Get(hid); err != datastore.ErrNoSuchEntity { 2166 t.Fatalf("Expected ErrNoSuchEntity but got %v", err) 2167 } 2168 } 2169 2170 // Test queries! 2171 2172 // Test that zero result queries work properly 2173 qiZRes := []QueryItem{} 2174 if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem"), &qiZRes); err != nil { 2175 t.Fatalf("GetAll Zero: unexpected error: %v", err) 2176 } else if len(dskeys) != 0 { 2177 t.Fatalf("GetAll Zero: expected 0 keys, got %v", len(dskeys)) 2178 } 2179 2180 createEntities := func() { 2181 // Create some entities that we will query for 2182 if getKeys, err := n.PutMulti([]*QueryItem{{Id: 1, Data: "one"}, {Id: 2, Data: "two"}}); err != nil { 2183 t.Fatalf("PutMulti: unexpected error: %v", err) 2184 } else { 2185 // do a datastore Get by *Key so that data is written to the datstore and indexes generated before subsequent query 2186 if err := datastore.GetMulti(c, getKeys, make([]QueryItem, 2)); err != nil { 2187 t.Error(err) 2188 } 2189 } 2190 // Make sure we clear the cache, as we will fill it later with a query 2191 n.FlushLocalCache() 2192 } 2193 deleteEntities := func(ids ...int64) { 2194 if len(ids) == 0 { 2195 ids = []int64{1, 2} 2196 } 2197 var keys []*datastore.Key 2198 for _, id := range ids { 2199 keys = append(keys, datastore.NewKey(c, "QueryItem", "", id, nil)) 2200 } 2201 if err := datastore.DeleteMulti(c, keys); err != nil { 2202 t.Error(err) 2203 } 2204 } 2205 2206 createEntities() 2207 2208 // Get the entity using a slice of structs that needs to be appended but has garbage data 2209 qiSGRes := []QueryItem{{Id: 1, Data: "invalid cache"}, {Garbage: "uninitialized memory"}}[:1] 2210 if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "two"), &qiSGRes); err != nil { 2211 t.Fatalf("GetAll SoS: unexpected error: %v", err) 2212 } else if len(dskeys) != 1 { 2213 t.Fatalf("GetAll SoS: expected 1 key, got %v", len(dskeys)) 2214 } else if dskeys[0].IntID() != 2 { 2215 t.Fatalf("GetAll SoS: expected key IntID to be 2, got %v", dskeys[0].IntID()) 2216 } else if len(qiSGRes) != 2 { 2217 t.Fatalf("GetAll SoS: expected 2 results, got %v", len(qiSGRes)) 2218 } else if qiSGRes[1].Id != 2 { 2219 t.Fatalf("GetAll SoS: expected entity id to be 2, got %v", qiSGRes[1].Id) 2220 } else if qiSGRes[1].Data != "two" { 2221 t.Fatalf("GetAll SoS: expected entity data to be 'two', got '%v'", qiSGRes[1].Data) 2222 } else if qiSGRes[1].Garbage != "" { 2223 t.Fatalf("GetAll SoS: expected no garbage data, but got '%v'", qiSGRes[1].Garbage) 2224 } 2225 2226 n.FlushLocalCache() 2227 2228 // Get the entity using a slice of structs 2229 qiSRes := []QueryItem{} 2230 if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "one"), &qiSRes); err != nil { 2231 t.Fatalf("GetAll SoS: unexpected error: %v", err) 2232 } else if len(dskeys) != 1 { 2233 t.Fatalf("GetAll SoS: expected 1 key, got %v", len(dskeys)) 2234 } else if dskeys[0].IntID() != 1 { 2235 t.Fatalf("GetAll SoS: expected key IntID to be 1, got %v", dskeys[0].IntID()) 2236 } else if len(qiSRes) != 1 { 2237 t.Fatalf("GetAll SoS: expected 1 result, got %v", len(qiSRes)) 2238 } else if qiSRes[0].Id != 1 { 2239 t.Fatalf("GetAll SoS: expected entity id to be 1, got %v", qiSRes[0].Id) 2240 } else if qiSRes[0].Data != "one" { 2241 t.Fatalf("GetAll SoS: expected entity data to be 'one', got '%v'", qiSRes[0].Data) 2242 } 2243 2244 deleteEntities() 2245 2246 // Get the entity using normal Get to test local cache 2247 qiS := &QueryItem{Id: 1} 2248 if err := n.Get(qiS); err != nil { 2249 t.Fatalf("Get SoS: unexpected error: %v", err) 2250 } else if qiS.Id != 1 { 2251 t.Fatalf("Get SoS: expected entity id to be 1, got %v", qiS.Id) 2252 } else if qiS.Data != "one" { 2253 t.Fatalf("Get SoS: expected entity data to be 'one', got '%v'", qiS.Data) 2254 } 2255 2256 createEntities() 2257 2258 // Get the entity using a slice of pointers to struct 2259 qiPRes := []*QueryItem{} 2260 if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "one"), &qiPRes); err != nil { 2261 t.Fatalf("GetAll SoPtS: unexpected error: %v", err) 2262 } else if len(dskeys) != 1 { 2263 t.Fatalf("GetAll SoPtS: expected 1 key, got %v", len(dskeys)) 2264 } else if dskeys[0].IntID() != 1 { 2265 t.Fatalf("GetAll SoPtS: expected key IntID to be 1, got %v", dskeys[0].IntID()) 2266 } else if len(qiPRes) != 1 { 2267 t.Fatalf("GetAll SoPtS: expected 1 result, got %v", len(qiPRes)) 2268 } else if qiPRes[0].Id != 1 { 2269 t.Fatalf("GetAll SoPtS: expected entity id to be 1, got %v", qiPRes[0].Id) 2270 } else if qiPRes[0].Data != "one" { 2271 t.Fatalf("GetAll SoPtS: expected entity data to be 'one', got '%v'", qiPRes[0].Data) 2272 } 2273 2274 deleteEntities() 2275 2276 // Get the entity using normal Get to test local cache 2277 qiP := &QueryItem{Id: 1} 2278 if err := n.Get(qiP); err != nil { 2279 t.Fatalf("Get SoPtS: unexpected error: %v", err) 2280 } else if qiP.Id != 1 { 2281 t.Fatalf("Get SoPtS: expected entity id to be 1, got %v", qiP.Id) 2282 } else if qiP.Data != "one" { 2283 t.Fatalf("Get SoPtS: expected entity data to be 'one', got '%v'", qiP.Data) 2284 } 2285 2286 createEntities() 2287 2288 // Get the entity using an iterator 2289 qiIt := n.Run(datastore.NewQuery("QueryItem").Filter("data=", "one")) 2290 2291 qiItRes := &QueryItem{} 2292 if dskey, err := qiIt.Next(qiItRes); err != nil { 2293 t.Fatalf("Next: unexpected error: %v", err) 2294 } else if dskey.IntID() != 1 { 2295 t.Fatalf("Next: expected key IntID to be 1, got %v", dskey.IntID()) 2296 } else if qiItRes.Id != 1 { 2297 t.Fatalf("Next: expected entity id to be 1, got %v", qiItRes.Id) 2298 } else if qiItRes.Data != "one" { 2299 t.Fatalf("Next: expected entity data to be 'one', got '%v'", qiItRes.Data) 2300 } 2301 2302 // Make sure the iterator ends correctly 2303 if _, err := qiIt.Next(&QueryItem{}); err != datastore.Done { 2304 t.Fatalf("Next: expected iterator to end with the error datastore.Done, got %v", err) 2305 } 2306 2307 deleteEntities() 2308 2309 // Get the entity using normal Get to test local cache 2310 qiI := &QueryItem{Id: 1} 2311 if err := n.Get(qiI); err != nil { 2312 t.Fatalf("Get Iterator: unexpected error: %v", err) 2313 } else if qiI.Id != 1 { 2314 t.Fatalf("Get Iterator: expected entity id to be 1, got %v", qiI.Id) 2315 } else if qiI.Data != "one" { 2316 t.Fatalf("Get Iterator: expected entity data to be 'one', got '%v'", qiI.Data) 2317 } 2318 2319 createEntities() 2320 2321 // Get the entity using a non-zero slice of structs, to also test the cache being filled incorrectly 2322 qiNZSRes := []QueryItem{{Id: 1, Data: "invalid cache"}} 2323 if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "two"), &qiNZSRes); err != nil { 2324 t.Fatalf("GetAll NZSoS: unexpected error: %v", err) 2325 } else if len(dskeys) != 1 { 2326 t.Fatalf("GetAll NZSoS: expected 1 key, got %v", len(dskeys)) 2327 } else if dskeys[0].IntID() != 2 { 2328 t.Fatalf("GetAll NZSoS: expected key IntID to be 2, got %v", dskeys[0].IntID()) 2329 } else if len(qiNZSRes) != 2 { 2330 t.Fatalf("GetAll NZSoS: expected slice len to be 2, got %v", len(qiNZSRes)) 2331 } else if qiNZSRes[0].Id != 1 { 2332 t.Fatalf("GetAll NZSoS: expected entity id to be 1, got %v", qiNZSRes[0].Id) 2333 } else if qiNZSRes[0].Data != "invalid cache" { 2334 t.Fatalf("GetAll NZSoS: expected entity data to be 'invalid cache', got '%v'", qiNZSRes[0].Data) 2335 } else if qiNZSRes[1].Id != 2 { 2336 t.Fatalf("GetAll NZSoS: expected entity id to be 2, got %v", qiNZSRes[1].Id) 2337 } else if qiNZSRes[1].Data != "two" { 2338 t.Fatalf("GetAll NZSoS: expected entity data to be 'two', got '%v'", qiNZSRes[1].Data) 2339 } 2340 2341 deleteEntities(2) 2342 2343 // Get the entities using normal GetMulti to test local cache 2344 qiNZSs := []QueryItem{{Id: 1}, {Id: 2}} 2345 if err := n.GetMulti(qiNZSs); err != nil { 2346 t.Fatalf("GetMulti NZSoS: unexpected error: %v", err) 2347 } else if len(qiNZSs) != 2 { 2348 t.Fatalf("GetMulti NZSoS: expected slice len to be 2, got %v", len(qiNZSs)) 2349 } else if qiNZSs[0].Id != 1 { 2350 t.Fatalf("GetMulti NZSoS: expected entity id to be 1, got %v", qiNZSs[0].Id) 2351 } else if qiNZSs[0].Data != "one" { 2352 t.Fatalf("GetMulti NZSoS: expected entity data to be 'one', got '%v'", qiNZSs[0].Data) 2353 } else if qiNZSs[1].Id != 2 { 2354 t.Fatalf("GetMulti NZSoS: expected entity id to be 2, got %v", qiNZSs[1].Id) 2355 } else if qiNZSs[1].Data != "two" { 2356 t.Fatalf("GetMulti NZSoS: expected entity data to be 'two', got '%v'", qiNZSs[1].Data) 2357 } 2358 2359 createEntities() 2360 2361 // Get the entity using a non-zero slice of pointers to struct, to also test the cache being filled incorrectly 2362 qiNZPRes := []*QueryItem{{Id: 1, Data: "invalid cache"}} 2363 if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "two"), &qiNZPRes); err != nil { 2364 t.Fatalf("GetAll NZSoPtS: unexpected error: %v", err) 2365 } else if len(dskeys) != 1 { 2366 t.Fatalf("GetAll NZSoPtS: expected 1 key, got %v", len(dskeys)) 2367 } else if dskeys[0].IntID() != 2 { 2368 t.Fatalf("GetAll NZSoPtS: expected key IntID to be 2, got %v", dskeys[0].IntID()) 2369 } else if len(qiNZPRes) != 2 { 2370 t.Fatalf("GetAll NZSoPtS: expected slice len to be 2, got %v", len(qiNZPRes)) 2371 } else if qiNZPRes[0].Id != 1 { 2372 t.Fatalf("GetAll NZSoPtS: expected entity id to be 1, got %v", qiNZPRes[0].Id) 2373 } else if qiNZPRes[0].Data != "invalid cache" { 2374 t.Fatalf("GetAll NZSoPtS: expected entity data to be 'invalid cache', got '%v'", qiNZPRes[0].Data) 2375 } else if qiNZPRes[1].Id != 2 { 2376 t.Fatalf("GetAll NZSoPtS: expected entity id to be 2, got %v", qiNZPRes[1].Id) 2377 } else if qiNZPRes[1].Data != "two" { 2378 t.Fatalf("GetAll NZSoPtS: expected entity data to be 'two', got '%v'", qiNZPRes[1].Data) 2379 } 2380 2381 deleteEntities(2) 2382 2383 // Get the entities using normal GetMulti to test local cache 2384 qiNZPs := []*QueryItem{{Id: 1}, {Id: 2}} 2385 if err := n.GetMulti(qiNZPs); err != nil { 2386 t.Fatalf("GetMulti NZSoPtS: unexpected error: %v", err) 2387 } else if len(qiNZPs) != 2 { 2388 t.Fatalf("GetMulti NZSoPtS: expected slice len to be 2, got %v", len(qiNZPs)) 2389 } else if qiNZPs[0].Id != 1 { 2390 t.Fatalf("GetMulti NZSoPtS: expected entity id to be 1, got %v", qiNZPs[0].Id) 2391 } else if qiNZPs[0].Data != "one" { 2392 t.Fatalf("GetMulti NZSoPtS: expected entity data to be 'one', got '%v'", qiNZPs[0].Data) 2393 } else if qiNZPs[1].Id != 2 { 2394 t.Fatalf("GetMulti NZSoPtS: expected entity id to be 2, got %v", qiNZPs[1].Id) 2395 } else if qiNZPs[1].Data != "two" { 2396 t.Fatalf("GetMulti NZSoPtS: expected entity data to be 'two', got '%v'", qiNZPs[1].Data) 2397 } 2398 2399 createEntities() 2400 2401 // Get the entity using a keys-only iterator 2402 qiItKO := n.Run(datastore.NewQuery("QueryItem").Filter("data=", "one").KeysOnly()) 2403 2404 qiItKORes := &QueryItem{} 2405 if dskey, err := qiItKO.Next(qiItKORes); err != nil { 2406 t.Fatalf("Next: unexpected error: %v", err) 2407 } else if dskey.IntID() != 1 { 2408 t.Fatalf("Next: expected key IntID to be 1, got %v", dskey.IntID()) 2409 } else if qiItKORes.Id != 1 { 2410 t.Fatalf("Next: expected entity id to be 1, got %v", qiItKORes.Id) 2411 } else if qiItKORes.Data != "" { 2412 t.Fatalf("Next: expected entity data to be empty, got '%v'", qiItKORes.Data) 2413 } 2414 2415 // Make sure the iterator ends correctly 2416 if _, err := qiItKO.Next(&QueryItem{}); err != datastore.Done { 2417 t.Fatalf("Next: expected iterator to end with the error datastore.Done, got %v", err) 2418 } 2419 2420 // Get the entity using normal Get to test local cache 2421 qiIKO := &QueryItem{Id: 1} 2422 if err := n.Get(qiIKO); err != nil { 2423 t.Fatalf("Get Iterator: unexpected error: %v", err) 2424 } else if qiIKO.Id != 1 { 2425 t.Fatalf("Get Iterator: expected entity id to be 1, got %v", qiIKO.Id) 2426 } else if qiIKO.Data != "one" { 2427 t.Fatalf("Get Iterator: expected entity data to be 'one', got '%v'", qiIKO.Data) 2428 } 2429 2430 n.FlushLocalCache() 2431 2432 // Test the simplest keys-only query, also test the cache not being filled incorrectly by a keys-only query 2433 if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "one").KeysOnly(), nil); err != nil { 2434 t.Fatalf("GetAll KeysOnly: unexpected error: %v", err) 2435 } else if len(dskeys) != 1 { 2436 t.Fatalf("GetAll KeysOnly: expected 1 key, got %v", len(dskeys)) 2437 } else if dskeys[0].IntID() != 1 { 2438 t.Fatalf("GetAll KeysOnly: expected key IntID to be 1, got %v", dskeys[0].IntID()) 2439 } 2440 2441 // Get the entity using normal Get to test that the local cache wasn't filled with incomplete data 2442 qiKO := &QueryItem{Id: 1} 2443 if err := n.Get(qiKO); err != nil { 2444 t.Fatalf("Get KeysOnly: unexpected error: %v", err) 2445 } else if qiKO.Id != 1 { 2446 t.Fatalf("Get KeysOnly: expected entity id to be 1, got %v", qiKO.Id) 2447 } else if qiKO.Data != "one" { 2448 t.Fatalf("Get KeysOnly: expected entity data to be 'one', got '%v'", qiKO.Data) 2449 } 2450 2451 n.FlushLocalCache() 2452 2453 // Test the keys-only query with slice of structs, also test the cache not being filled incorrectly by a keys-only query 2454 qiKOSRes := []QueryItem{} 2455 if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "one").KeysOnly(), &qiKOSRes); err != nil { 2456 t.Fatalf("GetAll KeysOnly SoS: unexpected error: %v", err) 2457 } else if len(dskeys) != 1 { 2458 t.Fatalf("GetAll KeysOnly SoS: expected 1 key, got %v", len(dskeys)) 2459 } else if dskeys[0].IntID() != 1 { 2460 t.Fatalf("GetAll KeysOnly SoS: expected key IntID to be 1, got %v", dskeys[0].IntID()) 2461 } else if len(qiKOSRes) != 1 { 2462 t.Fatalf("GetAll KeysOnly SoS: expected 1 result, got %v", len(qiKOSRes)) 2463 } else if k := reflect.TypeOf(qiKOSRes[0]).Kind(); k != reflect.Struct { 2464 t.Fatalf("GetAll KeysOnly SoS: expected struct, got %v", k) 2465 } else if qiKOSRes[0].Id != 1 { 2466 t.Fatalf("GetAll KeysOnly SoS: expected entity id to be 1, got %v", qiKOSRes[0].Id) 2467 } else if qiKOSRes[0].Data != "" { 2468 t.Fatalf("GetAll KeysOnly SoS: expected entity data to be empty, got '%v'", qiKOSRes[0].Data) 2469 } 2470 2471 // Get the entity using normal Get to test that the local cache wasn't filled with incomplete data 2472 if err := n.GetMulti(qiKOSRes); err != nil { 2473 t.Fatalf("Get KeysOnly SoS: unexpected error: %v", err) 2474 } else if qiKOSRes[0].Id != 1 { 2475 t.Fatalf("Get KeysOnly SoS: expected entity id to be 1, got %v", qiKOSRes[0].Id) 2476 } else if qiKOSRes[0].Data != "one" { 2477 t.Fatalf("Get KeysOnly SoS: expected entity data to be 'one', got '%v'", qiKOSRes[0].Data) 2478 } 2479 2480 n.FlushLocalCache() 2481 2482 // Test the keys-only query with slice of pointers to struct, also test the cache not being filled incorrectly by a keys-only query 2483 qiKOPRes := []*QueryItem{} 2484 if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "one").KeysOnly(), &qiKOPRes); err != nil { 2485 t.Fatalf("GetAll KeysOnly SoPtS: unexpected error: %v", err) 2486 } else if len(dskeys) != 1 { 2487 t.Fatalf("GetAll KeysOnly SoPtS: expected 1 key, got %v", len(dskeys)) 2488 } else if dskeys[0].IntID() != 1 { 2489 t.Fatalf("GetAll KeysOnly SoPtS: expected key IntID to be 1, got %v", dskeys[0].IntID()) 2490 } else if len(qiKOPRes) != 1 { 2491 t.Fatalf("GetAll KeysOnly SoPtS: expected 1 result, got %v", len(qiKOPRes)) 2492 } else if k := reflect.TypeOf(qiKOPRes[0]).Kind(); k != reflect.Ptr { 2493 t.Fatalf("GetAll KeysOnly SoPtS: expected pointer, got %v", k) 2494 } else if qiKOPRes[0].Id != 1 { 2495 t.Fatalf("GetAll KeysOnly SoPtS: expected entity id to be 1, got %v", qiKOPRes[0].Id) 2496 } else if qiKOPRes[0].Data != "" { 2497 t.Fatalf("GetAll KeysOnly SoPtS: expected entity data to be empty, got '%v'", qiKOPRes[0].Data) 2498 } 2499 2500 // Get the entity using normal Get to test that the local cache wasn't filled with incomplete data 2501 if err := n.GetMulti(qiKOPRes); err != nil { 2502 t.Fatalf("Get KeysOnly SoPtS: unexpected error: %v", err) 2503 } else if qiKOPRes[0].Id != 1 { 2504 t.Fatalf("Get KeysOnly SoPtS: expected entity id to be 1, got %v", qiKOPRes[0].Id) 2505 } else if qiKOPRes[0].Data != "one" { 2506 t.Fatalf("Get KeysOnly SoPtS: expected entity data to be 'one', got '%v'", qiKOPRes[0].Data) 2507 } 2508 2509 n.FlushLocalCache() 2510 2511 // Test the keys-only query with non-zero slice of structs 2512 qiKONZSRes := []QueryItem{{Id: 1, Data: "invalid cache"}} 2513 if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "two").KeysOnly(), &qiKONZSRes); err != nil { 2514 t.Fatalf("GetAll KeysOnly NZSoS: unexpected error: %v", err) 2515 } else if len(dskeys) != 1 { 2516 t.Fatalf("GetAll KeysOnly NZSoS: expected 1 key, got %v", len(dskeys)) 2517 } else if dskeys[0].IntID() != 2 { 2518 t.Fatalf("GetAll KeysOnly NZSoS: expected key IntID to be 2, got %v", dskeys[0].IntID()) 2519 } else if len(qiKONZSRes) != 2 { 2520 t.Fatalf("GetAll KeysOnly NZSoS: expected slice len to be 2, got %v", len(qiKONZSRes)) 2521 } else if qiKONZSRes[0].Id != 1 { 2522 t.Fatalf("GetAll KeysOnly NZSoS: expected entity id to be 1, got %v", qiKONZSRes[0].Id) 2523 } else if qiKONZSRes[0].Data != "invalid cache" { 2524 t.Fatalf("GetAll KeysOnly NZSoS: expected entity data to be 'invalid cache', got '%v'", qiKONZSRes[0].Data) 2525 } else if k := reflect.TypeOf(qiKONZSRes[1]).Kind(); k != reflect.Struct { 2526 t.Fatalf("GetAll KeysOnly NZSoS: expected struct, got %v", k) 2527 } else if qiKONZSRes[1].Id != 2 { 2528 t.Fatalf("GetAll KeysOnly NZSoS: expected entity id to be 2, got %v", qiKONZSRes[1].Id) 2529 } else if qiKONZSRes[1].Data != "" { 2530 t.Fatalf("GetAll KeysOnly NZSoS: expected entity data to be empty, got '%v'", qiKONZSRes[1].Data) 2531 } 2532 2533 // Get the entities using normal GetMulti to test local cache 2534 if err := n.GetMulti(qiKONZSRes); err != nil { 2535 t.Fatalf("GetMulti NZSoS: unexpected error: %v", err) 2536 } else if len(qiKONZSRes) != 2 { 2537 t.Fatalf("GetMulti NZSoS: expected slice len to be 2, got %v", len(qiKONZSRes)) 2538 } else if qiKONZSRes[0].Id != 1 { 2539 t.Fatalf("GetMulti NZSoS: expected entity id to be 1, got %v", qiKONZSRes[0].Id) 2540 } else if qiKONZSRes[0].Data != "one" { 2541 t.Fatalf("GetMulti NZSoS: expected entity data to be 'one', got '%v'", qiKONZSRes[0].Data) 2542 } else if qiKONZSRes[1].Id != 2 { 2543 t.Fatalf("GetMulti NZSoS: expected entity id to be 2, got %v", qiKONZSRes[1].Id) 2544 } else if qiKONZSRes[1].Data != "two" { 2545 t.Fatalf("GetMulti NZSoS: expected entity data to be 'two', got '%v'", qiKONZSRes[1].Data) 2546 } 2547 2548 n.FlushLocalCache() 2549 2550 // Test the keys-only query with non-zero slice of pointers to struct 2551 qiKONZPRes := []*QueryItem{{Id: 1, Data: "invalid cache"}} 2552 if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "two").KeysOnly(), &qiKONZPRes); err != nil { 2553 t.Fatalf("GetAll KeysOnly NZSoPtS: unexpected error: %v", err) 2554 } else if len(dskeys) != 1 { 2555 t.Fatalf("GetAll KeysOnly NZSoPtS: expected 1 key, got %v", len(dskeys)) 2556 } else if dskeys[0].IntID() != 2 { 2557 t.Fatalf("GetAll KeysOnly NZSoPtS: expected key IntID to be 2, got %v", dskeys[0].IntID()) 2558 } else if len(qiKONZPRes) != 2 { 2559 t.Fatalf("GetAll KeysOnly NZSoPtS: expected slice len to be 2, got %v", len(qiKONZPRes)) 2560 } else if qiKONZPRes[0].Id != 1 { 2561 t.Fatalf("GetAll KeysOnly NZSoPtS: expected entity id to be 1, got %v", qiKONZPRes[0].Id) 2562 } else if qiKONZPRes[0].Data != "invalid cache" { 2563 t.Fatalf("GetAll KeysOnly NZSoPtS: expected entity data to be 'invalid cache', got '%v'", qiKONZPRes[0].Data) 2564 } else if k := reflect.TypeOf(qiKONZPRes[1]).Kind(); k != reflect.Ptr { 2565 t.Fatalf("GetAll KeysOnly NZSoPtS: expected pointer, got %v", k) 2566 } else if qiKONZPRes[1].Id != 2 { 2567 t.Fatalf("GetAll KeysOnly NZSoPtS: expected entity id to be 2, got %v", qiKONZPRes[1].Id) 2568 } else if qiKONZPRes[1].Data != "" { 2569 t.Fatalf("GetAll KeysOnly NZSoPtS: expected entity data to be empty, got '%v'", qiKONZPRes[1].Data) 2570 } 2571 2572 // Get the entities using normal GetMulti to test local cache 2573 if err := n.GetMulti(qiKONZPRes); err != nil { 2574 t.Fatalf("GetMulti NZSoPtS: unexpected error: %v", err) 2575 } else if len(qiKONZPRes) != 2 { 2576 t.Fatalf("GetMulti NZSoPtS: expected slice len to be 2, got %v", len(qiKONZPRes)) 2577 } else if qiKONZPRes[0].Id != 1 { 2578 t.Fatalf("GetMulti NZSoPtS: expected entity id to be 1, got %v", qiKONZPRes[0].Id) 2579 } else if qiKONZPRes[0].Data != "one" { 2580 t.Fatalf("GetMulti NZSoPtS: expected entity data to be 'one', got '%v'", qiKONZPRes[0].Data) 2581 } else if qiKONZPRes[1].Id != 2 { 2582 t.Fatalf("GetMulti NZSoPtS: expected entity id to be 2, got %v", qiKONZPRes[1].Id) 2583 } else if qiKONZPRes[1].Data != "two" { 2584 t.Fatalf("GetMulti NZSoPtS: expected entity data to be 'two', got '%v'", qiKONZPRes[1].Data) 2585 } 2586 } 2587 2588 type keyTest struct { 2589 obj interface{} 2590 key *datastore.Key 2591 } 2592 2593 type NoId struct { 2594 } 2595 2596 type HasId struct { 2597 Id int64 `datastore:"-" goon:"id"` 2598 Name string 2599 } 2600 2601 type HasKind struct { 2602 Id int64 `datastore:"-" goon:"id"` 2603 Kind string `datastore:"-" goon:"kind"` 2604 Name string 2605 } 2606 2607 type HasDefaultKind struct { 2608 Id int64 `datastore:"-" goon:"id"` 2609 Kind string `datastore:"-" goon:"kind,DefaultKind"` 2610 Name string 2611 } 2612 2613 type QueryItem struct { 2614 Id int64 `datastore:"-" goon:"id"` 2615 Data string `datastore:"data"` 2616 Extra string `datastore:"extra"` 2617 Garbage string `datastore:"-"` 2618 } 2619 2620 type HasString struct { 2621 Id string `datastore:"-" goon:"id"` 2622 } 2623 2624 type TwoId struct { 2625 IntId int64 `goon:"id"` 2626 StringId string `goon:"id"` 2627 } 2628 2629 type PutGet struct { 2630 ID int64 `datastore:"-" goon:"id"` 2631 Value int32 2632 } 2633 2634 type HasData struct { 2635 Id int64 `datastore:"-" goon:"id"` 2636 Data []byte 2637 } 2638 2639 // This test won't fail but if run with -race flag, it will show known race conditions 2640 // Using multiple goroutines per http request is recommended here: 2641 // http://talks.golang.org/2013/highperf.slide#22 2642 func TestRace(t *testing.T) { 2643 c, done, err := aetest.NewContext() 2644 if err != nil { 2645 t.Fatalf("Could not start aetest - %v", err) 2646 } 2647 defer done() 2648 g := FromContext(c) 2649 2650 var hasIdSlice []*HasId 2651 for x := 1; x <= 4000; x++ { 2652 hasIdSlice = append(hasIdSlice, &HasId{Id: int64(x), Name: "Race"}) 2653 } 2654 _, err = g.PutMulti(hasIdSlice) 2655 if err != nil { 2656 t.Fatalf("Could not put Race entities - %v", err) 2657 } 2658 hasIdSlice = hasIdSlice[:0] 2659 for x := 1; x <= 4000; x++ { 2660 hasIdSlice = append(hasIdSlice, &HasId{Id: int64(x)}) 2661 } 2662 var wg sync.WaitGroup 2663 wg.Add(3) 2664 go func() { 2665 err := g.Get(hasIdSlice[0]) 2666 if err != nil { 2667 t.Errorf("Error fetching id #0 - %v", err) 2668 } 2669 wg.Done() 2670 }() 2671 go func() { 2672 err := g.GetMulti(hasIdSlice[1:1500]) 2673 if err != nil { 2674 t.Errorf("Error fetching ids 1 through 1499 - %v", err) 2675 } 2676 wg.Done() 2677 }() 2678 go func() { 2679 err := g.GetMulti(hasIdSlice[1500:]) 2680 if err != nil { 2681 t.Errorf("Error fetching id #1500 through 4000 - %v", err) 2682 } 2683 wg.Done() 2684 }() 2685 wg.Wait() 2686 for x, hi := range hasIdSlice { 2687 if hi.Name != "Race" { 2688 t.Fatalf("Object #%d not fetched properly, fetched instead - %v", x, hi) 2689 } 2690 } 2691 2692 // in case of datastore failure 2693 errInternalCall := errors.New("internal call error") 2694 withErrorContext := func(ctx context.Context, multiLimit int) context.Context { 2695 return appengine.WithAPICallFunc(ctx, func(ctx context.Context, service, method string, in, out proto.Message) error { 2696 if service != "datastore_v3" || (method != "Put" && method != "Get" && method != "Delete") { 2697 return appengine.APICall(ctx, service, method, in, out) 2698 } 2699 errs := make(appengine.MultiError, multiLimit) 2700 for x := 0; x < multiLimit; x++ { 2701 errs[x] = errInternalCall 2702 } 2703 return errs 2704 }) 2705 } 2706 2707 g.Context = withErrorContext(g.Context, datastorePutMultiMaxItems) 2708 _, err = g.PutMulti(hasIdSlice) 2709 if err != errInternalCall { 2710 t.Fatalf("Expected %v, got %v", errInternalCall, err) 2711 } 2712 2713 g.FlushLocalCache() 2714 g.Context = withErrorContext(g.Context, datastoreGetMultiMaxItems) 2715 err = g.GetMulti(hasIdSlice) 2716 if err != errInternalCall { 2717 t.Fatalf("Expected %v, got %v", errInternalCall, err) 2718 } 2719 2720 g.Context = withErrorContext(g.Context, datastoreDeleteMultiMaxItems) 2721 err = g.DeleteMulti(hasIdSlice) 2722 if err != errInternalCall { 2723 t.Fatalf("Expected %v, got %v", errInternalCall, err) 2724 } 2725 } 2726 2727 func TestPutGet(t *testing.T) { 2728 c, done, err := aetest.NewContext() 2729 if err != nil { 2730 t.Fatalf("Could not start aetest - %v", err) 2731 } 2732 defer done() 2733 g := FromContext(c) 2734 2735 key, err := g.Put(&PutGet{ID: 12, Value: 15}) 2736 if err != nil { 2737 t.Fatal(err) 2738 } 2739 if key.IntID() != 12 { 2740 t.Fatal("ID should be 12 but is", key.IntID()) 2741 } 2742 2743 // Datastore Get 2744 dsPutGet := &PutGet{} 2745 err = datastore.Get(c, 2746 datastore.NewKey(c, "PutGet", "", 12, nil), dsPutGet) 2747 if err != nil { 2748 t.Fatal(err) 2749 } 2750 if dsPutGet.Value != 15 { 2751 t.Fatal("dsPutGet.Value should be 15 but is", 2752 dsPutGet.Value) 2753 } 2754 2755 // Goon Get 2756 goonPutGet := &PutGet{ID: 12} 2757 err = g.Get(goonPutGet) 2758 if err != nil { 2759 t.Fatal(err) 2760 } 2761 if goonPutGet.ID != 12 { 2762 t.Fatal("goonPutGet.ID should be 12 but is", goonPutGet.ID) 2763 } 2764 if goonPutGet.Value != 15 { 2765 t.Fatal("goonPutGet.Value should be 15 but is", 2766 goonPutGet.Value) 2767 } 2768 } 2769 2770 func prefixKindName(src interface{}) string { 2771 return "prefix." + DefaultKindName(src) 2772 } 2773 2774 func TestCustomKindName(t *testing.T) { 2775 c, done, err := aetest.NewContext() 2776 if err != nil { 2777 t.Fatalf("Could not start aetest - %v", err) 2778 } 2779 defer done() 2780 g := FromContext(c) 2781 2782 hi := HasId{Name: "Foo"} 2783 2784 //gate 2785 if kind := g.Kind(hi); kind != "HasId" { 2786 t.Fatal("HasId King should not have a prefix, but instead is, ", kind) 2787 } 2788 2789 g.KindNameResolver = prefixKindName 2790 2791 if kind := g.Kind(hi); kind != "prefix.HasId" { 2792 t.Fatal("HasId King should have a prefix, but instead is, ", kind) 2793 } 2794 2795 _, err = g.Put(&hi) 2796 2797 if err != nil { 2798 t.Fatal("Should be able to put a record: ", err) 2799 } 2800 2801 // Due to eventual consistency, we need to wait a bit. The old aetest package 2802 // had an option to enable strong consistency that has been removed. This 2803 // is currently the best way I'm aware of to do this. 2804 time.Sleep(time.Second) 2805 reget1 := []HasId{} 2806 query := datastore.NewQuery("prefix.HasId") 2807 query.GetAll(c, ®et1) 2808 if len(reget1) != 1 { 2809 t.Fatal("Should have 1 record stored in datastore ", reget1) 2810 } 2811 if reget1[0].Name != "Foo" { 2812 t.Fatal("Name should be Foo ", reget1[0].Name) 2813 } 2814 } 2815 2816 func TestMultis(t *testing.T) { 2817 c, done, err := aetest.NewContext() 2818 if err != nil { 2819 t.Fatalf("Could not start aetest - %v", err) 2820 } 2821 defer done() 2822 n := FromContext(c) 2823 2824 testAmounts := []int{1, 999, 1000, 1001, 1999, 2000, 2001, 2510} 2825 for _, x := range testAmounts { 2826 memcache.Flush(c) 2827 objects := make([]*HasId, x) 2828 for y := 0; y < x; y++ { 2829 objects[y] = &HasId{Id: int64(y + 1)} 2830 } 2831 if keys, err := n.PutMulti(objects); err != nil { 2832 t.Fatalf("Error in PutMulti for %d objects - %v", x, err) 2833 } else if len(keys) != len(objects) { 2834 t.Fatalf("Expected %v keys, got %v", len(objects), len(keys)) 2835 } else { 2836 for i, key := range keys { 2837 if key.IntID() != int64(i+1) { 2838 t.Fatalf("Expected object #%v key to be %v, got %v", i, (i + 1), key.IntID()) 2839 } 2840 } 2841 } 2842 n.FlushLocalCache() // Put just put them in the local cache, get rid of it before doing the Get 2843 if err := n.GetMulti(objects); err != nil { 2844 t.Fatalf("Error in GetMulti - %v", err) 2845 } 2846 } 2847 2848 // check if the returned keys match the struct keys for autogenerated keys 2849 for _, x := range testAmounts { 2850 memcache.Flush(c) 2851 objects := make([]*HasId, x) 2852 for y := 0; y < x; y++ { 2853 objects[y] = &HasId{} 2854 } 2855 if keys, err := n.PutMulti(objects); err != nil { 2856 t.Fatalf("Error in PutMulti for %d objects - %v", x, err) 2857 } else if len(keys) != len(objects) { 2858 t.Fatalf("Expected %v keys, got %v", len(objects), len(keys)) 2859 } else { 2860 for i, key := range keys { 2861 if key.IntID() != objects[i].Id { 2862 t.Errorf("Expected object #%v key to be %v, got %v", i, objects[i].Id, key.IntID()) 2863 } 2864 } 2865 } 2866 n.FlushLocalCache() 2867 } 2868 2869 // do it again, but only write numbers divisible by 100 2870 for _, x := range testAmounts { 2871 memcache.Flush(c) 2872 getobjects := make([]*HasId, 0, x) 2873 putobjects := make([]*HasId, 0, x/100+1) 2874 keys := make([]*datastore.Key, x) 2875 for y := 0; y < x; y++ { 2876 keys[y] = datastore.NewKey(c, "HasId", "", int64(y+1), nil) 2877 } 2878 if err := n.DeleteMulti(keys); err != nil { 2879 t.Fatalf("Error deleting keys - %v", err) 2880 } 2881 for y := 0; y < x; y++ { 2882 getobjects = append(getobjects, &HasId{Id: int64(y + 1)}) 2883 if y%100 == 0 { 2884 putobjects = append(putobjects, &HasId{Id: int64(y + 1)}) 2885 } 2886 } 2887 2888 _, err := n.PutMulti(putobjects) 2889 if err != nil { 2890 t.Fatalf("Error in PutMulti for %d objects - %v", x, err) 2891 } 2892 n.FlushLocalCache() // Put just put them in the local cache, get rid of it before doing the Get 2893 err = n.GetMulti(getobjects) 2894 if err == nil && x != 1 { // a test size of 1 has no objects divisible by 100, so there's no cache miss to return 2895 t.Fatalf("Should be receiving a multiError on %d objects, but got no errors", x) 2896 continue 2897 } 2898 2899 merr, ok := err.(appengine.MultiError) 2900 if ok { 2901 if len(merr) != len(getobjects) { 2902 t.Fatalf("Should have received a MultiError object of length %d but got length %d instead", len(getobjects), len(merr)) 2903 } 2904 for x := range merr { 2905 switch { // record good conditions, fail in other conditions 2906 case merr[x] == nil && x%100 == 0: 2907 case merr[x] != nil && x%100 != 0: 2908 default: 2909 t.Fatalf("Found bad condition on object[%d] and error %v", x+1, merr[x]) 2910 } 2911 } 2912 } else if x != 1 { 2913 t.Fatalf("Did not return a multierror on fetch but when fetching %d objects, received - %v", x, merr) 2914 } 2915 } 2916 } 2917 2918 type root struct { 2919 Id int64 `datastore:"-" goon:"id"` 2920 Data int 2921 } 2922 2923 type normalChild struct { 2924 Id int64 `datastore:"-" goon:"id"` 2925 Parent *datastore.Key `datastore:"-" goon:"parent"` 2926 Data int 2927 } 2928 2929 type coolKey *datastore.Key 2930 2931 type derivedChild struct { 2932 Id int64 `datastore:"-" goon:"id"` 2933 Parent coolKey `datastore:"-" goon:"parent"` 2934 Data int 2935 } 2936 2937 func TestParents(t *testing.T) { 2938 c, done, err := aetest.NewContext() 2939 if err != nil { 2940 t.Fatalf("Could not start aetest - %v", err) 2941 } 2942 defer done() 2943 n := FromContext(c) 2944 2945 r := &root{1, 10} 2946 rootKey, err := n.Put(r) 2947 if err != nil { 2948 t.Fatalf("couldn't Put(%+v)", r) 2949 } 2950 2951 // Put exercises both get and set, since Id is uninitialized 2952 nc := &normalChild{0, rootKey, 20} 2953 nk, err := n.Put(nc) 2954 if err != nil { 2955 t.Fatalf("couldn't Put(%+v)", nc) 2956 } 2957 if nc.Parent == rootKey { 2958 t.Fatalf("derived parent key pointer value didn't change") 2959 } 2960 if !(*datastore.Key)(nc.Parent).Equal(rootKey) { 2961 t.Fatalf("parent of key not equal '%s' v '%s'! ", (*datastore.Key)(nc.Parent), rootKey) 2962 } 2963 if !nk.Parent().Equal(rootKey) { 2964 t.Fatalf("parent of key not equal '%s' v '%s'! ", nk, rootKey) 2965 } 2966 2967 dc := &derivedChild{0, (coolKey)(rootKey), 12} 2968 dk, err := n.Put(dc) 2969 if err != nil { 2970 t.Fatalf("couldn't Put(%+v)", dc) 2971 } 2972 if dc.Parent == rootKey { 2973 t.Fatalf("derived parent key pointer value didn't change") 2974 } 2975 if !(*datastore.Key)(dc.Parent).Equal(rootKey) { 2976 t.Fatalf("parent of key not equal '%s' v '%s'! ", (*datastore.Key)(dc.Parent), rootKey) 2977 } 2978 if !dk.Parent().Equal(rootKey) { 2979 t.Fatalf("parent of key not equal '%s' v '%s'! ", dk, rootKey) 2980 } 2981 } 2982 2983 func TestProjection(t *testing.T) { 2984 c, done, err := aetest.NewContext() 2985 if err != nil { 2986 t.Fatalf("Could not start aetest - %v", err) 2987 } 2988 defer done() 2989 g := FromContext(c) 2990 2991 // Store some items 2992 if _, err := g.PutMulti([]*QueryItem{{Id: 1, Data: "foo", Extra: "zoo"}, {Id: 2, Data: "bar", Extra: "woo"}}); err != nil { 2993 t.Fatalf("failed to put query items: %v", err) 2994 } 2995 2996 // Helps test if the cache has been poisoned by the incomplete results of the projection query 2997 testCache := func(spot string) { 2998 // Get these items by key 2999 qis := []*QueryItem{{Id: 1}, {Id: 2}} 3000 if err := g.GetMulti(qis); err != nil { 3001 t.Fatalf("[%v] failed to fetch query items by key: %v", spot, err) 3002 } 3003 // Make sure the data is correct 3004 if qis[0].Data != "foo" { 3005 t.Errorf("[%v] first query item's data is incorrect: %v", spot, qis[0].Data) 3006 } 3007 if qis[1].Data != "bar" { 3008 t.Errorf("[%v] second query item's data is incorrect: %v", spot, qis[1].Data) 3009 } 3010 if qis[0].Extra != "zoo" { 3011 t.Errorf("[%v] unexpected extra value: %v", spot, qis[0].Extra) 3012 } 3013 if qis[1].Extra != "woo" { 3014 t.Errorf("[%v] unexpected extra value: %v", spot, qis[1].Extra) 3015 } 3016 } 3017 3018 // Clear the caches 3019 g.FlushLocalCache() 3020 memcache.Flush(c) 3021 3022 // Need to wait due to eventual consistency 3023 time.Sleep(time.Second) 3024 3025 // Do a projection query for these 3026 qis := []*QueryItem{} 3027 if _, err := g.GetAll(datastore.NewQuery("QueryItem").Project("data").Order("-data"), &qis); err != nil { 3028 t.Fatalf("failed to fetch query items: %v", err) 3029 } 3030 3031 if len(qis) != 2 { 3032 t.Fatalf("failed to fetch both query items (%v)", len(qis)) 3033 } 3034 if qis[0].Data != "foo" { 3035 t.Errorf("first query item's data is incorrect: %v", qis[0].Data) 3036 } 3037 if qis[1].Data != "bar" { 3038 t.Errorf("second query item's data is incorrect: %v", qis[1].Data) 3039 } 3040 if qis[0].Extra != "" { 3041 t.Errorf("unexpected extra value: %v", qis[0].Extra) 3042 } 3043 if qis[1].Extra != "" { 3044 t.Errorf("unexpected extra value: %v", qis[1].Extra) 3045 } 3046 3047 testCache("GetAll") 3048 3049 // Clear the caches 3050 g.FlushLocalCache() 3051 memcache.Flush(c) 3052 3053 // Do another projection query 3054 it := g.Run(datastore.NewQuery("QueryItem").Project("data").Order("-data")) 3055 3056 qi1 := &QueryItem{} 3057 if _, err := it.Next(qi1); err != nil { 3058 t.Fatalf("failed to fetch first query item: %v", err) 3059 } 3060 qi2 := &QueryItem{} 3061 if _, err := it.Next(qi2); err != nil { 3062 t.Fatalf("failed to fetch second query item: %v", err) 3063 } 3064 if _, err := it.Next(nil); err != datastore.Done { 3065 t.Errorf("query didn't properly finish: %v", err) 3066 } 3067 3068 if qi1.Data != "foo" { 3069 t.Errorf("first query item's data is incorrect: %v", qi1.Data) 3070 } 3071 if qi2.Data != "bar" { 3072 t.Errorf("second query item's data is incorrect: %v", qi2.Data) 3073 } 3074 if qi1.Extra != "" { 3075 t.Errorf("unexpected extra value: %v", qi1.Extra) 3076 } 3077 if qi2.Extra != "" { 3078 t.Errorf("unexpected extra value: %v", qi2.Extra) 3079 } 3080 3081 testCache("Next") 3082 } 3083 3084 type ContainerStruct struct { 3085 Id string `datastore:"-" goon:"id"` 3086 embeddedStructA 3087 embeddedStructB `datastore:"w"` 3088 } 3089 3090 type embeddedStructA struct { 3091 X int 3092 y int 3093 } 3094 3095 type embeddedStructB struct { 3096 Z1 int 3097 Z2 int `datastore:"z2fancy"` 3098 } 3099 3100 func TestEmbeddedStruct(t *testing.T) { 3101 c, done, err := aetest.NewContext() 3102 if err != nil { 3103 t.Fatalf("Could not start aetest - %v", err) 3104 } 3105 defer done() 3106 g := FromContext(c) 3107 3108 // Store some data with an embedded unexported struct 3109 pcs := &ContainerStruct{Id: "foo"} 3110 pcs.X, pcs.y, pcs.Z1, pcs.Z2 = 1, 2, 3, 4 3111 _, err = g.Put(pcs) 3112 if err != nil { 3113 t.Fatalf("Unexpected error on put - %v", err) 3114 } 3115 3116 // First run fetches from the datastore (as Put() only caches to the local cache) 3117 // Second run fetches from memcache (as our first run here called Get() which caches into memcache) 3118 for i := 1; i <= 2; i++ { 3119 // Clear the local cache 3120 g.FlushLocalCache() 3121 3122 // Fetch it and confirm the values 3123 gcs := &ContainerStruct{Id: pcs.Id} 3124 err = g.Get(gcs) 3125 if err != nil { 3126 t.Fatalf("#%v - Unexpected error on get - %v", i, err) 3127 } 3128 // The exported field must have the correct value 3129 if gcs.X != pcs.X { 3130 t.Fatalf("#%v - Expected - %v, got %v", i, pcs.X, gcs.X) 3131 } 3132 if gcs.Z1 != pcs.Z1 { 3133 t.Fatalf("#%v - Expected - %v, got %v", i, pcs.Z1, gcs.Z1) 3134 } 3135 if gcs.Z2 != pcs.Z2 { 3136 t.Fatalf("#%v - Expected - %v, got %v", i, pcs.Z2, gcs.Z2) 3137 } 3138 // The unexported field must be zero-valued 3139 if gcs.y != 0 { 3140 t.Fatalf("#%v - Expected - %v, got %v", i, 0, gcs.y) 3141 } 3142 } 3143 } 3144 3145 func TestMemcachePutTimeout(t *testing.T) { 3146 c, done, err := aetest.NewContext() 3147 if err != nil { 3148 t.Fatalf("Could not start aetest - %v", err) 3149 } 3150 defer done() 3151 3152 origMPTS := MemcachePutTimeoutSmall 3153 origMPTL := MemcachePutTimeoutLarge 3154 origMPTT := MemcachePutTimeoutThreshold 3155 origMGT := MemcacheGetTimeout 3156 origPMPE := propagateMemcachePutError 3157 defer func() { 3158 MemcachePutTimeoutSmall = origMPTS 3159 MemcachePutTimeoutLarge = origMPTL 3160 MemcachePutTimeoutThreshold = origMPTT 3161 MemcacheGetTimeout = origMGT 3162 propagateMemcachePutError = origPMPE 3163 }() 3164 3165 propagateMemcachePutError = false 3166 3167 g := FromContext(c) 3168 MemcachePutTimeoutSmall = time.Second 3169 // put a HasId resource, then test pulling it from memory, memcache, and datastore 3170 hi := &HasId{Name: "hasid"} // no id given, should be automatically created by the datastore 3171 if _, err := g.Put(hi); err != nil { 3172 t.Fatalf("put: unexpected error - %v", err) 3173 } 3174 3175 // Generate the cache entry 3176 data, err := serializeStruct(hi) 3177 if err != nil { 3178 t.Fatalf("Unexpected error on serialize: %v", err) 3179 } 3180 ci := &cacheItem{ 3181 key: cacheKey(g.Key(hi)), 3182 value: data, 3183 } 3184 cis := []*cacheItem{ci} 3185 3186 MemcachePutTimeoutSmall = 0 3187 MemcachePutTimeoutLarge = 0 3188 MemcachePutTimeoutThreshold = 1 3189 if err := g.putMemcache(cis); !appengine.IsTimeoutError(err) { 3190 t.Fatalf("Request should timeout - err = %v", err) 3191 } 3192 3193 MemcachePutTimeoutLarge = time.Second 3194 if err := g.putMemcache(cis); err != nil { 3195 t.Fatalf("putMemcache: unexpected error - %v", err) 3196 } 3197 3198 g.FlushLocalCache() 3199 memcache.Flush(c) 3200 // time out Get 3201 MemcacheGetTimeout = 0 3202 // time out Put too 3203 MemcachePutTimeoutSmall = 0 3204 MemcachePutTimeoutThreshold = 1 3205 MemcachePutTimeoutLarge = 0 3206 hiResult := &HasId{Id: hi.Id} 3207 if err := g.Get(hiResult); err != nil { 3208 t.Fatalf("Request should not timeout cause we'll fetch from the datastore but got error %v", err) 3209 // Put timing out should also error, but it won't be returned here, just logged 3210 } 3211 if !reflect.DeepEqual(hi, hiResult) { 3212 t.Fatalf("Fetched object isn't accurate\n%s", getDiff(hi, hiResult, "hi", "hiResult")) 3213 } 3214 3215 hiResult = &HasId{Id: hi.Id} 3216 g.FlushLocalCache() 3217 MemcacheGetTimeout = time.Second 3218 if err := g.Get(hiResult); err != nil { 3219 t.Fatalf("Request should not timeout cause we'll fetch from memcache successfully but got error %v", err) 3220 } 3221 if !reflect.DeepEqual(hi, hiResult) { 3222 t.Fatalf("Fetched object isn't accurate\n%s", getDiff(hi, hiResult, "hi", "hiResult")) 3223 } 3224 } 3225 3226 func TestChangeMemcacheKey(t *testing.T) { 3227 c, done, err := aetest.NewContext() 3228 if err != nil { 3229 t.Fatalf("Could not start aetest - %v", err) 3230 } 3231 defer done() 3232 3233 originalMemcacheKey := MemcacheKey 3234 defer func() { 3235 MemcacheKey = originalMemcacheKey 3236 }() 3237 verID := "some-version" 3238 MemcacheKey = func(k *datastore.Key) string { 3239 return "custom:" + verID + ":" + k.Encode() 3240 } 3241 3242 g := FromContext(c) 3243 3244 key, err := g.Put(&PutGet{ID: 12, Value: 15}) 3245 if err != nil { 3246 t.Fatal(err) 3247 } 3248 g.FlushLocalCache() 3249 err = g.Get(&PutGet{ID: 12}) 3250 if err != nil { 3251 t.Fatal(err) 3252 } 3253 3254 ckey := cacheKey(key) 3255 3256 _, err = memcache.Get(c, ckey) 3257 if err != nil { 3258 t.Fatal(err) 3259 } 3260 3261 if !strings.HasSuffix(ckey, "custom:"+verID+":"+key.Encode()) { 3262 t.Fatal("cache key should have 'custom:`versionID`:`encodedKey` suffix", err) 3263 } 3264 } 3265 3266 func TestMemcacheLimits(t *testing.T) { 3267 c, done, err := aetest.NewContext() 3268 if err != nil { 3269 t.Fatalf("Could not start aetest - %v", err) 3270 } 3271 defer done() 3272 3273 // Confirm that the cacheKey function respects the output limit 3274 pk := datastore.NewKey(c, "Parent", strings.Repeat("p", memcacheMaxKeySize/2), 0, nil) 3275 ck := datastore.NewKey(c, "Child", strings.Repeat("c", memcacheMaxKeySize/2), 0, pk) 3276 if mk := cacheKey(ck); len(mk) > memcacheMaxKeySize { 3277 t.Fatalf("cacheKey returned key with a length of %d exceeding the maximum of %d", len(mk), memcacheMaxKeySize) 3278 } 3279 3280 // Confirm that single maximum size fits 3281 maxItem := &memcache.Item{ 3282 Key: strings.Repeat("k", memcacheMaxKeySize), 3283 Value: make([]byte, memcacheMaxValueSize), 3284 Flags: 1<<32 - 1, 3285 Expiration: time.Duration(1<<63 - 1), 3286 } 3287 if err := memcache.Set(c, maxItem); err != nil { 3288 t.Fatalf("Expected maximum sized item to pass! Got error: %v", err) 3289 } 3290 if _, err := memcache.Get(c, maxItem.Key); err != nil { 3291 t.Fatalf("Unexpected error on Get: %v", err) 3292 } 3293 3294 // Helper function to test RPC batch size limit 3295 testRPCBatchSize := func(maxValueSize int, readBack bool) { 3296 var items []*memcache.Item 3297 itemSize := memcacheOverhead + memcacheMaxKeySize + maxValueSize 3298 extra := memcacheOverhead + memcacheMaxKeySize + 1 // at least 1 byte for value 3299 count := (memcacheMaxRPCSize - extra) / itemSize 3300 extra += (memcacheMaxRPCSize - extra) % itemSize 3301 for i := 0; i < count; i++ { 3302 items = append(items, &memcache.Item{ 3303 Key: strings.Repeat("k", memcacheMaxKeySize-30) + fmt.Sprintf("%030d", i), 3304 Value: make([]byte, maxValueSize), 3305 Flags: 1<<32 - 1, 3306 Expiration: time.Duration(1<<63 - 1), 3307 }) 3308 } 3309 extraValueSize := extra - memcacheOverhead - memcacheMaxKeySize 3310 items = append(items, &memcache.Item{ 3311 Key: strings.Repeat("x", memcacheMaxKeySize), 3312 Value: make([]byte, extraValueSize), 3313 Flags: 1<<32 - 1, 3314 Expiration: time.Duration(1<<63 - 1), 3315 }) 3316 if err := memcache.SetMulti(c, items); err != nil { 3317 t.Fatalf("Expected maximum sized batch to pass with maxValueSize %d! Got error: %v", maxValueSize, err) 3318 } 3319 if readBack { 3320 var keys []string 3321 for _, item := range items { 3322 keys = append(keys, item.Key) 3323 } 3324 if results, err := memcache.GetMulti(c, keys); err != nil { 3325 t.Fatalf("Unexpected error on GetMulti with maxValueSize %d: %v", maxValueSize, err) 3326 } else if len(keys) != len(results) { 3327 t.Fatalf("Expected %d results but got %d with maxValueSize %d", len(keys), len(results), maxValueSize) 3328 } 3329 } 3330 } 3331 3332 // Confirm that large entities filling up RPC works 3333 testRPCBatchSize(memcacheMaxValueSize, true) 3334 3335 // Confirm with middle-sized entities 3336 testRPCBatchSize(100000, true) 3337 3338 // Confirm overhead not exceeding our expectations by filling up RPC with tiny entities 3339 // NOTE: We don't read back because the memcache emulator is way too slow to do this 3340 testRPCBatchSize(1000, false) 3341 3342 // Test RPC limit handling via goon 3343 g := FromContext(c) 3344 3345 // Aim for roughly 80% of max entity size 3346 data := bytes.Repeat([]byte{1, 2, 3, 4}, memcacheMaxItemSize/5) 3347 // Aim for the maximum RPC size that we can get with datastore GetMulti, 3348 // which should still be larger than the maximum memcache RPC size 3349 count := int64(datastoreGetMultiMaxRPCSize / len(data)) 3350 hds := make([]*HasData, 0, count) 3351 for i := int64(1); i <= count; i++ { 3352 hds = append(hds, &HasData{Id: i, Data: data}) 3353 } 3354 3355 // Save all of these 3356 // TODO: Use PutMulti once goon supports splitting up too large PutMulti requests 3357 // keys, err := g.PutMulti(hds) 3358 // if err != nil { 3359 // t.Fatalf("Unexpected error on PutMulti: %v", err) 3360 // } 3361 var keys []*datastore.Key 3362 for _, hd := range hds { 3363 if key, err := g.Put(hd); err != nil { 3364 t.Fatalf("Unexpected error on Put: %v", err) 3365 } else { 3366 keys = append(keys, key) 3367 } 3368 } 3369 3370 // Flush all the caches 3371 g.FlushLocalCache() 3372 memcache.Flush(c) 3373 3374 // Fetch them all back to confirm datastore works, 3375 // and to also populate memcache 3376 for _, hd := range hds { 3377 hd.Data = nil 3378 } 3379 if err := g.GetMulti(hds); err != nil { 3380 t.Fatalf("Unexpected error on GetMulti: %v", err) 3381 } 3382 for _, hd := range hds { 3383 if !bytes.Equal(hd.Data, data) { 3384 t.Fatalf("Invalid data! %v", hd.Id) 3385 } 3386 hd.Data = nil 3387 } 3388 3389 // Flush local cache & datastore 3390 g.FlushLocalCache() 3391 if err := datastore.DeleteMulti(c, keys); err != nil { 3392 t.Fatalf("Unexpected error on DeleteMulti: %v", err) 3393 } 3394 3395 // Fetch them all back again, this time from memcache 3396 if err := g.GetMulti(hds); err != nil { 3397 t.Fatalf("Unexpected error on GetMulti: %v", err) 3398 } 3399 for _, hd := range hds { 3400 if !bytes.Equal(hd.Data, data) { 3401 t.Fatalf("Invalid data! %v", hd.Id) 3402 } 3403 hd.Data = nil 3404 } 3405 }