| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 // adapted from github.com/golang/appengine/datastore | |
| 6 | |
| 7 package helper | |
| 8 | |
| 9 import ( | |
| 10 "bytes" | |
| 11 "encoding/json" | |
| 12 "fmt" | |
| 13 "math" | |
| 14 "reflect" | |
| 15 "strconv" | |
| 16 "strings" | |
| 17 "testing" | |
| 18 "time" | |
| 19 | |
| 20 "github.com/luci/gae" | |
| 21 . "github.com/smartystreets/goconvey/convey" | |
| 22 ) | |
| 23 | |
| 24 var ( | |
| 25 mp = gae.MkDSProperty | |
| 26 mpNI = gae.MkDSPropertyNI | |
| 27 ) | |
| 28 | |
| 29 const testAppID = "testApp" | |
| 30 | |
| 31 type ( | |
| 32 myBlob []byte | |
| 33 myByte byte | |
| 34 myString string | |
| 35 ) | |
| 36 | |
| 37 func makeMyByteSlice(n int) []myByte { | |
| 38 b := make([]myByte, n) | |
| 39 for i := range b { | |
| 40 b[i] = myByte(i) | |
| 41 } | |
| 42 return b | |
| 43 } | |
| 44 | |
| 45 func makeInt8Slice(n int) []int8 { | |
| 46 b := make([]int8, n) | |
| 47 for i := range b { | |
| 48 b[i] = int8(i) | |
| 49 } | |
| 50 return b | |
| 51 } | |
| 52 | |
| 53 func makeUint8Slice(n int) []uint8 { | |
| 54 b := make([]uint8, n) | |
| 55 for i := range b { | |
| 56 b[i] = uint8(i) | |
| 57 } | |
| 58 return b | |
| 59 } | |
| 60 | |
| 61 var ( | |
| 62 testKey0 = mkKey("aid", "", "kind", "name0") | |
| 63 testKey1a = mkKey("aid", "", "kind", "name1") | |
| 64 testKey1b = mkKey("aid", "", "kind", "name1") | |
| 65 testKey2a = mkKey("aid", "", "kind", "name0", "kind", "name2") | |
| 66 testKey2b = mkKey("aid", "", "kind", "name0", "kind", "name2") | |
| 67 testGeoPt0 = gae.DSGeoPoint{Lat: 1.2, Lng: 3.4} | |
| 68 testGeoPt1 = gae.DSGeoPoint{Lat: 5, Lng: 10} | |
| 69 testBadGeoPt = gae.DSGeoPoint{Lat: 1000, Lng: 34} | |
| 70 ) | |
| 71 | |
| 72 type B0 struct { | |
| 73 B []byte | |
| 74 } | |
| 75 | |
| 76 type B1 struct { | |
| 77 B []int8 | |
| 78 } | |
| 79 | |
| 80 type B2 struct { | |
| 81 B myBlob | |
| 82 } | |
| 83 | |
| 84 type B3 struct { | |
| 85 B []myByte | |
| 86 } | |
| 87 | |
| 88 type B4 struct { | |
| 89 B [][]byte | |
| 90 } | |
| 91 | |
| 92 type B5 struct { | |
| 93 B gae.DSByteString | |
| 94 } | |
| 95 | |
| 96 type C0 struct { | |
| 97 I int | |
| 98 C chan int | |
| 99 } | |
| 100 | |
| 101 type C1 struct { | |
| 102 I int | |
| 103 C *chan int | |
| 104 } | |
| 105 | |
| 106 type C2 struct { | |
| 107 I int | |
| 108 C []chan int | |
| 109 } | |
| 110 | |
| 111 type C3 struct { | |
| 112 C string | |
| 113 } | |
| 114 | |
| 115 type E struct{} | |
| 116 | |
| 117 type G0 struct { | |
| 118 G gae.DSGeoPoint | |
| 119 } | |
| 120 | |
| 121 type G1 struct { | |
| 122 G []gae.DSGeoPoint | |
| 123 } | |
| 124 | |
| 125 type K0 struct { | |
| 126 K gae.DSKey | |
| 127 } | |
| 128 | |
| 129 type K1 struct { | |
| 130 K []gae.DSKey | |
| 131 } | |
| 132 | |
| 133 type N0 struct { | |
| 134 X0 | |
| 135 ID int64 `gae:"$id"` | |
| 136 _kind string `gae:"$kind,whatnow"` | |
| 137 Nonymous X0 | |
| 138 Ignore string `gae:"-"` | |
| 139 Other string | |
| 140 } | |
| 141 | |
| 142 type N1 struct { | |
| 143 X0 | |
| 144 Nonymous []X0 | |
| 145 Ignore string `gae:"-"` | |
| 146 Other string | |
| 147 } | |
| 148 | |
| 149 type N2 struct { | |
| 150 N1 `gae:"red"` | |
| 151 Green N1 `gae:"green"` | |
| 152 Blue N1 | |
| 153 White N1 `gae:"-"` | |
| 154 } | |
| 155 | |
| 156 type O0 struct { | |
| 157 I int64 | |
| 158 } | |
| 159 | |
| 160 type O1 struct { | |
| 161 I int32 | |
| 162 } | |
| 163 | |
| 164 type U0 struct { | |
| 165 U uint | |
| 166 } | |
| 167 | |
| 168 type U1 struct { | |
| 169 U string | |
| 170 } | |
| 171 | |
| 172 type T struct { | |
| 173 T time.Time | |
| 174 } | |
| 175 | |
| 176 type X0 struct { | |
| 177 S string | |
| 178 I int | |
| 179 i int | |
| 180 } | |
| 181 | |
| 182 type X1 struct { | |
| 183 S myString | |
| 184 I int32 | |
| 185 J int64 | |
| 186 } | |
| 187 | |
| 188 type X2 struct { | |
| 189 Z string | |
| 190 i int | |
| 191 } | |
| 192 | |
| 193 type X3 struct { | |
| 194 S bool | |
| 195 I int | |
| 196 } | |
| 197 | |
| 198 type Y0 struct { | |
| 199 B bool | |
| 200 F []float64 | |
| 201 G []float64 | |
| 202 } | |
| 203 | |
| 204 type Y1 struct { | |
| 205 B bool | |
| 206 F float64 | |
| 207 } | |
| 208 | |
| 209 type Y2 struct { | |
| 210 B bool | |
| 211 F []int64 | |
| 212 } | |
| 213 | |
| 214 type Y3 struct { | |
| 215 B bool | |
| 216 F int64 | |
| 217 } | |
| 218 | |
| 219 type Tagged struct { | |
| 220 A int `gae:"a,noindex"` | |
| 221 B []int `gae:"b1"` | |
| 222 C int `gae:",noindex"` | |
| 223 D int `gae:""` | |
| 224 E int | |
| 225 I int `gae:"-"` | |
| 226 J int `gae:",noindex" json:"j"` | |
| 227 | |
| 228 Y0 `gae:"-"` | |
| 229 Z chan int `gae:"-,"` | |
| 230 } | |
| 231 | |
| 232 type InvalidTagged1 struct { | |
| 233 I int `gae:"\t"` | |
| 234 } | |
| 235 | |
| 236 type InvalidTagged2 struct { | |
| 237 I int | |
| 238 J int `gae:"I"` | |
| 239 } | |
| 240 | |
| 241 type InvalidTagged3 struct { | |
| 242 I int `gae:"a\t"` | |
| 243 } | |
| 244 | |
| 245 type InvalidTagged4 struct { | |
| 246 I int `gae:"a."` | |
| 247 } | |
| 248 | |
| 249 type InvalidTaggedSub struct { | |
| 250 I int | |
| 251 } | |
| 252 | |
| 253 type InvalidTagged5 struct { | |
| 254 I int `gae:"V.I"` | |
| 255 V []InvalidTaggedSub | |
| 256 } | |
| 257 | |
| 258 type Inner1 struct { | |
| 259 W int32 | |
| 260 X string | |
| 261 } | |
| 262 | |
| 263 type Inner2 struct { | |
| 264 Y float64 | |
| 265 } | |
| 266 | |
| 267 type Inner3 struct { | |
| 268 Z bool | |
| 269 } | |
| 270 | |
| 271 type Outer struct { | |
| 272 A int16 | |
| 273 I []Inner1 | |
| 274 J Inner2 | |
| 275 Inner3 | |
| 276 } | |
| 277 | |
| 278 type OuterEquivalent struct { | |
| 279 A int16 | |
| 280 IDotW []int32 `gae:"I.W"` | |
| 281 IDotX []string `gae:"I.X"` | |
| 282 JDotY float64 `gae:"J.Y"` | |
| 283 Z bool | |
| 284 } | |
| 285 | |
| 286 type Dotted struct { | |
| 287 A DottedA `gae:"A0.A1.A2"` | |
| 288 } | |
| 289 | |
| 290 type DottedA struct { | |
| 291 B DottedB `gae:"B3"` | |
| 292 } | |
| 293 | |
| 294 type DottedB struct { | |
| 295 C int `gae:"C4.C5"` | |
| 296 } | |
| 297 | |
| 298 type SliceOfSlices struct { | |
| 299 I int | |
| 300 S []struct { | |
| 301 J int | |
| 302 F []float64 | |
| 303 } | |
| 304 } | |
| 305 | |
| 306 type Recursive struct { | |
| 307 I int | |
| 308 R []Recursive | |
| 309 } | |
| 310 | |
| 311 type MutuallyRecursive0 struct { | |
| 312 I int | |
| 313 R []MutuallyRecursive1 | |
| 314 } | |
| 315 | |
| 316 type MutuallyRecursive1 struct { | |
| 317 I int | |
| 318 R []MutuallyRecursive0 | |
| 319 } | |
| 320 | |
| 321 type ExoticTypes struct { | |
| 322 BS gae.BSKey | |
| 323 DSBS gae.DSByteString | |
| 324 } | |
| 325 | |
| 326 type Underspecified struct { | |
| 327 Iface gae.DSPropertyConverter | |
| 328 } | |
| 329 | |
| 330 type MismatchTypes struct { | |
| 331 S string | |
| 332 B bool | |
| 333 F float32 | |
| 334 K gae.DSKey | |
| 335 T time.Time | |
| 336 G gae.DSGeoPoint | |
| 337 IS []int | |
| 338 } | |
| 339 | |
| 340 type BadSpecial struct { | |
| 341 ID int64 `gae:"$id"` | |
| 342 id string `gae:"$id"` | |
| 343 } | |
| 344 | |
| 345 type Doubler struct { | |
| 346 S string | |
| 347 I int64 | |
| 348 B bool | |
| 349 } | |
| 350 | |
| 351 func (d *Doubler) Load(props gae.DSPropertyMap) error { | |
| 352 return GetPLS(d).Load(props) | |
| 353 } | |
| 354 | |
| 355 func (d *Doubler) Save(withMeta bool) (gae.DSPropertyMap, error) { | |
| 356 pls := GetPLS(d) | |
| 357 propMap, err := pls.Save(withMeta) | |
| 358 if err != nil { | |
| 359 return nil, err | |
| 360 } | |
| 361 | |
| 362 // Edit that map and send it on. | |
| 363 for _, props := range propMap { | |
| 364 for i := range props { | |
| 365 switch v := props[i].Value().(type) { | |
| 366 case string: | |
| 367 // + means string concatenation. | |
| 368 props[i].SetValue(v+v, props[i].IndexSetting()) | |
| 369 case int64: | |
| 370 // + means integer addition. | |
| 371 props[i].SetValue(v+v, props[i].IndexSetting()) | |
| 372 } | |
| 373 } | |
| 374 } | |
| 375 return propMap, nil | |
| 376 } | |
| 377 | |
| 378 func (d *Doubler) GetMeta(string) (interface{}, error) { return nil, gae.ErrDSMe
taFieldUnset } | |
| 379 func (d *Doubler) SetMeta(string, interface{}) error { return gae.ErrDSMetaFie
ldUnset } | |
| 380 func (d *Doubler) Problem() error { return nil } | |
| 381 | |
| 382 var _ gae.DSPropertyLoadSaver = (*Doubler)(nil) | |
| 383 | |
| 384 type Deriver struct { | |
| 385 S, Derived, Ignored string | |
| 386 } | |
| 387 | |
| 388 func (d *Deriver) Load(props gae.DSPropertyMap) error { | |
| 389 for name, p := range props { | |
| 390 if name != "S" { | |
| 391 continue | |
| 392 } | |
| 393 d.S = p[0].Value().(string) | |
| 394 d.Derived = "derived+" + d.S | |
| 395 } | |
| 396 return nil | |
| 397 } | |
| 398 | |
| 399 func (d *Deriver) Save(withMeta bool) (gae.DSPropertyMap, error) { | |
| 400 return map[string][]gae.DSProperty{ | |
| 401 "S": {mp(d.S)}, | |
| 402 }, nil | |
| 403 } | |
| 404 | |
| 405 func (d *Deriver) GetMeta(string) (interface{}, error) { return nil, gae.ErrDSMe
taFieldUnset } | |
| 406 func (d *Deriver) SetMeta(string, interface{}) error { return gae.ErrDSMetaFie
ldUnset } | |
| 407 func (d *Deriver) Problem() error { return nil } | |
| 408 | |
| 409 var _ gae.DSPropertyLoadSaver = (*Deriver)(nil) | |
| 410 | |
| 411 type BK struct { | |
| 412 Key gae.BSKey | |
| 413 } | |
| 414 | |
| 415 type Convertable []int64 | |
| 416 | |
| 417 var _ gae.DSPropertyConverter = (*Convertable)(nil) | |
| 418 | |
| 419 func (c *Convertable) ToDSProperty() (ret gae.DSProperty, err error) { | |
| 420 buf := make([]string, len(*c)) | |
| 421 for i, v := range *c { | |
| 422 buf[i] = strconv.FormatInt(v, 10) | |
| 423 } | |
| 424 err = ret.SetValue(strings.Join(buf, ","), gae.NoIndex) | |
| 425 return | |
| 426 } | |
| 427 | |
| 428 func (c *Convertable) FromDSProperty(pv gae.DSProperty) error { | |
| 429 if sval, ok := pv.Value().(string); ok { | |
| 430 for _, t := range strings.Split(sval, ",") { | |
| 431 ival, err := strconv.ParseInt(t, 10, 64) | |
| 432 if err != nil { | |
| 433 return err | |
| 434 } | |
| 435 *c = append(*c, ival) | |
| 436 } | |
| 437 return nil | |
| 438 } | |
| 439 return fmt.Errorf("nope") | |
| 440 } | |
| 441 | |
| 442 type Impossible struct { | |
| 443 Nested []ImpossibleInner | |
| 444 } | |
| 445 | |
| 446 type ImpossibleInner struct { | |
| 447 Ints Convertable `gae:"wot"` | |
| 448 } | |
| 449 | |
| 450 type Convertable2 struct { | |
| 451 Data string | |
| 452 Exploded []string | |
| 453 } | |
| 454 | |
| 455 func (c *Convertable2) ToDSProperty() (ret gae.DSProperty, err error) { | |
| 456 err = ret.SetValue(c.Data, gae.ShouldIndex) | |
| 457 return | |
| 458 } | |
| 459 | |
| 460 func (c *Convertable2) FromDSProperty(pv gae.DSProperty) error { | |
| 461 if sval, ok := pv.Value().(string); ok { | |
| 462 c.Data = sval | |
| 463 c.Exploded = []string{"turn", "down", "for", "what"} | |
| 464 return nil | |
| 465 } | |
| 466 return fmt.Errorf("nope") | |
| 467 } | |
| 468 | |
| 469 type Impossible2 struct { | |
| 470 Nested []ImpossibleInner2 | |
| 471 } | |
| 472 | |
| 473 type ImpossibleInner2 struct { | |
| 474 Thingy Convertable2 `gae:"nerb"` | |
| 475 } | |
| 476 | |
| 477 type JSONKVProp map[string]interface{} | |
| 478 | |
| 479 var _ gae.DSPropertyConverter = (*JSONKVProp)(nil) | |
| 480 | |
| 481 func (j *JSONKVProp) ToDSProperty() (ret gae.DSProperty, err error) { | |
| 482 data, err := json.Marshal(map[string]interface{}(*j)) | |
| 483 if err != nil { | |
| 484 return | |
| 485 } | |
| 486 err = ret.SetValue(data, gae.NoIndex) | |
| 487 return | |
| 488 } | |
| 489 | |
| 490 func (j *JSONKVProp) FromDSProperty(pv gae.DSProperty) error { | |
| 491 if bval, ok := pv.Value().([]byte); ok { | |
| 492 dec := json.NewDecoder(bytes.NewBuffer(bval)) | |
| 493 dec.UseNumber() | |
| 494 return dec.Decode((*map[string]interface{})(j)) | |
| 495 } | |
| 496 return fmt.Errorf("nope") | |
| 497 } | |
| 498 | |
| 499 type Impossible3 struct { | |
| 500 KMap JSONKVProp `gae:"kewelmap"` | |
| 501 } | |
| 502 | |
| 503 type Complex complex128 | |
| 504 | |
| 505 var _ gae.DSPropertyConverter = (*Complex)(nil) | |
| 506 | |
| 507 func (c *Complex) ToDSProperty() (ret gae.DSProperty, err error) { | |
| 508 // cheat hardkore and usurp GeoPoint so datastore will index these sucke
rs | |
| 509 // (note that this won't REALLY work, since GeoPoints are limited to a v
ery | |
| 510 // limited range of values, but it's nice to pretend ;)). You'd probably | |
| 511 // really end up with a packed binary representation. | |
| 512 err = ret.SetValue(gae.DSGeoPoint{Lat: real(*c), Lng: imag(*c)}, gae.Sho
uldIndex) | |
| 513 return | |
| 514 } | |
| 515 | |
| 516 func (c *Complex) FromDSProperty(p gae.DSProperty) error { | |
| 517 if gval, ok := p.Value().(gae.DSGeoPoint); ok { | |
| 518 *c = Complex(complex(gval.Lat, gval.Lng)) | |
| 519 return nil | |
| 520 } | |
| 521 return fmt.Errorf("nope") | |
| 522 } | |
| 523 | |
| 524 type Impossible4 struct { | |
| 525 Values []Complex | |
| 526 } | |
| 527 | |
| 528 type DerivedKey struct { | |
| 529 K *GenericDSKey | |
| 530 } | |
| 531 | |
| 532 type IfaceKey struct { | |
| 533 K gae.DSKey | |
| 534 } | |
| 535 | |
| 536 type testCase struct { | |
| 537 desc string | |
| 538 src interface{} | |
| 539 want interface{} | |
| 540 plsErr string | |
| 541 saveErr string | |
| 542 actualNoIndex bool | |
| 543 plsLoadErr string | |
| 544 loadErr string | |
| 545 } | |
| 546 | |
| 547 var testCases = []testCase{ | |
| 548 { | |
| 549 desc: "chan save fails", | |
| 550 src: &C0{I: -1}, | |
| 551 plsErr: `field "C" has invalid type: chan int`, | |
| 552 }, | |
| 553 { | |
| 554 desc: "*chan save fails", | |
| 555 src: &C1{I: -1}, | |
| 556 plsErr: `field "C" has invalid type: *chan int`, | |
| 557 }, | |
| 558 { | |
| 559 desc: "[]chan save fails", | |
| 560 src: &C2{I: -1, C: make([]chan int, 8)}, | |
| 561 plsErr: `field "C" has invalid type: []chan int`, | |
| 562 }, | |
| 563 { | |
| 564 desc: "chan load fails", | |
| 565 src: &C3{C: "not a chan"}, | |
| 566 want: &C0{}, | |
| 567 plsLoadErr: `field "C" has invalid type: chan int`, | |
| 568 }, | |
| 569 { | |
| 570 desc: "*chan load fails", | |
| 571 src: &C3{C: "not a *chan"}, | |
| 572 want: &C1{}, | |
| 573 plsLoadErr: `field "C" has invalid type: *chan int`, | |
| 574 }, | |
| 575 { | |
| 576 desc: "[]chan load fails", | |
| 577 src: &C3{C: "not a []chan"}, | |
| 578 want: &C2{}, | |
| 579 plsLoadErr: `field "C" has invalid type: []chan int`, | |
| 580 }, | |
| 581 { | |
| 582 desc: "empty struct", | |
| 583 src: &E{}, | |
| 584 want: &E{}, | |
| 585 }, | |
| 586 { | |
| 587 desc: "geopoint", | |
| 588 src: &G0{G: testGeoPt0}, | |
| 589 want: &G0{G: testGeoPt0}, | |
| 590 }, | |
| 591 { | |
| 592 desc: "geopoint invalid", | |
| 593 src: &G0{G: testBadGeoPt}, | |
| 594 saveErr: "invalid GeoPoint value", | |
| 595 }, | |
| 596 { | |
| 597 desc: "geopoint as props", | |
| 598 src: &G0{G: testGeoPt0}, | |
| 599 want: gae.DSPropertyMap{ | |
| 600 "G": {mp(testGeoPt0)}, | |
| 601 }, | |
| 602 }, | |
| 603 { | |
| 604 desc: "geopoint slice", | |
| 605 src: &G1{G: []gae.DSGeoPoint{testGeoPt0, testGeoPt1}}, | |
| 606 want: &G1{G: []gae.DSGeoPoint{testGeoPt0, testGeoPt1}}, | |
| 607 }, | |
| 608 { | |
| 609 desc: "key", | |
| 610 src: &K0{K: testKey1a}, | |
| 611 want: &K0{K: testKey1b}, | |
| 612 }, | |
| 613 { | |
| 614 desc: "key with parent", | |
| 615 src: &K0{K: testKey2a}, | |
| 616 want: &K0{K: testKey2b}, | |
| 617 }, | |
| 618 { | |
| 619 desc: "nil key", | |
| 620 src: &K0{}, | |
| 621 want: &K0{}, | |
| 622 }, | |
| 623 { | |
| 624 desc: "all nil keys in slice", | |
| 625 src: &K1{[]gae.DSKey{nil, nil}}, | |
| 626 want: &K1{[]gae.DSKey{nil, nil}}, | |
| 627 }, | |
| 628 { | |
| 629 desc: "some nil keys in slice", | |
| 630 src: &K1{[]gae.DSKey{testKey1a, nil, testKey2a}}, | |
| 631 want: &K1{[]gae.DSKey{testKey1b, nil, testKey2b}}, | |
| 632 }, | |
| 633 { | |
| 634 desc: "overflow", | |
| 635 src: &O0{I: 1 << 48}, | |
| 636 want: &O1{}, | |
| 637 loadErr: "overflow", | |
| 638 }, | |
| 639 { | |
| 640 desc: "time", | |
| 641 src: &T{T: time.Unix(1e9, 0).UTC()}, | |
| 642 want: &T{T: time.Unix(1e9, 0).UTC()}, | |
| 643 }, | |
| 644 { | |
| 645 desc: "time as props", | |
| 646 src: &T{T: time.Unix(1e9, 0).UTC()}, | |
| 647 want: gae.DSPropertyMap{ | |
| 648 "T": {mp(time.Unix(1e9, 0).UTC())}, | |
| 649 }, | |
| 650 }, | |
| 651 { | |
| 652 desc: "uint save", | |
| 653 src: &U0{U: 1}, | |
| 654 plsErr: `field "U" has invalid type: uint`, | |
| 655 }, | |
| 656 { | |
| 657 desc: "uint load", | |
| 658 src: &U1{U: "not a uint"}, | |
| 659 want: &U0{}, | |
| 660 plsLoadErr: `field "U" has invalid type: uint`, | |
| 661 }, | |
| 662 { | |
| 663 desc: "zero", | |
| 664 src: &X0{}, | |
| 665 want: &X0{}, | |
| 666 }, | |
| 667 { | |
| 668 desc: "basic", | |
| 669 src: &X0{S: "one", I: 2, i: 3}, | |
| 670 want: &X0{S: "one", I: 2}, | |
| 671 }, | |
| 672 { | |
| 673 desc: "save string/int load myString/int32", | |
| 674 src: &X0{S: "one", I: 2, i: 3}, | |
| 675 want: &X1{S: "one", I: 2}, | |
| 676 }, | |
| 677 { | |
| 678 desc: "missing fields", | |
| 679 src: &X0{S: "one", I: 2, i: 3}, | |
| 680 want: &X2{}, | |
| 681 loadErr: "no such struct field", | |
| 682 }, | |
| 683 { | |
| 684 desc: "save string load bool", | |
| 685 src: &X0{S: "one", I: 2, i: 3}, | |
| 686 want: &X3{I: 2}, | |
| 687 loadErr: "type mismatch", | |
| 688 }, | |
| 689 { | |
| 690 desc: "basic slice", | |
| 691 src: &Y0{B: true, F: []float64{7, 8, 9}}, | |
| 692 want: &Y0{B: true, F: []float64{7, 8, 9}}, | |
| 693 }, | |
| 694 { | |
| 695 desc: "save []float64 load float64", | |
| 696 src: &Y0{B: true, F: []float64{7, 8, 9}}, | |
| 697 want: &Y1{B: true}, | |
| 698 loadErr: "requires a slice", | |
| 699 }, | |
| 700 { | |
| 701 desc: "save single []int64 load int64", | |
| 702 src: &Y2{B: true, F: []int64{7}}, | |
| 703 want: &Y3{B: true, F: 7}, | |
| 704 }, | |
| 705 { | |
| 706 desc: "save int64 load single []int64", | |
| 707 src: &Y3{B: true, F: 7}, | |
| 708 want: &Y2{B: true, F: []int64{7}}, | |
| 709 }, | |
| 710 { | |
| 711 desc: "use convertable slice", | |
| 712 src: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con
vertable{2, 4, 6}}}}, | |
| 713 want: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con
vertable{2, 4, 6}}}}, | |
| 714 }, | |
| 715 { | |
| 716 desc: "use convertable slice (to map)", | |
| 717 src: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con
vertable{2, 4, 6}}}}, | |
| 718 want: gae.DSPropertyMap{ | |
| 719 "Nested.wot": {mpNI("1,5,9"), mpNI("2,4,6")}, | |
| 720 }, | |
| 721 }, | |
| 722 { | |
| 723 desc: "convertable slice (bad load)", | |
| 724 src: gae.DSPropertyMap{"Nested.wot": {mpNI([]byte("ohai"))}}
, | |
| 725 want: &Impossible{[]ImpossibleInner{{}}}, | |
| 726 loadErr: "nope", | |
| 727 }, | |
| 728 { | |
| 729 desc: "use convertable struct", | |
| 730 src: &Impossible2{ | |
| 731 []ImpossibleInner2{ | |
| 732 {Convertable2{"nerb", nil}}, | |
| 733 }, | |
| 734 }, | |
| 735 want: &Impossible2{ | |
| 736 []ImpossibleInner2{ | |
| 737 {Convertable2{"nerb", []string{"turn", "down", "
for", "what"}}}, | |
| 738 }, | |
| 739 }, | |
| 740 }, | |
| 741 { | |
| 742 desc: "convertable json KVMap", | |
| 743 src: &Impossible3{ | |
| 744 JSONKVProp{ | |
| 745 "epic": "success", | |
| 746 "no_way!": []interface{}{true, "story"}, | |
| 747 "what": []interface{}{"is", "really", 100}, | |
| 748 }, | |
| 749 }, | |
| 750 want: &Impossible3{ | |
| 751 JSONKVProp{ | |
| 752 "epic": "success", | |
| 753 "no_way!": []interface{}{true, "story"}, | |
| 754 "what": []interface{}{"is", "really", json.Nu
mber("100")}, | |
| 755 }, | |
| 756 }, | |
| 757 }, | |
| 758 { | |
| 759 desc: "convertable json KVMap (to map)", | |
| 760 src: &Impossible3{ | |
| 761 JSONKVProp{ | |
| 762 "epic": "success", | |
| 763 "no_way!": []interface{}{true, "story"}, | |
| 764 "what": []interface{}{"is", "really", 100}, | |
| 765 }, | |
| 766 }, | |
| 767 want: gae.DSPropertyMap{ | |
| 768 "kewelmap": { | |
| 769 mpNI([]byte( | |
| 770 `{"epic":"success","no_way!":[true,"stor
y"],"what":["is","really",100]}`))}, | |
| 771 }, | |
| 772 }, | |
| 773 { | |
| 774 desc: "convertable complex slice", | |
| 775 src: &Impossible4{ | |
| 776 []Complex{complex(1, 2), complex(3, 4)}, | |
| 777 }, | |
| 778 want: &Impossible4{ | |
| 779 []Complex{complex(1, 2), complex(3, 4)}, | |
| 780 }, | |
| 781 }, | |
| 782 { | |
| 783 desc: "convertable complex slice (to map)", | |
| 784 src: &Impossible4{ | |
| 785 []Complex{complex(1, 2), complex(3, 4)}, | |
| 786 }, | |
| 787 want: gae.DSPropertyMap{ | |
| 788 "Values": { | |
| 789 mp(gae.DSGeoPoint{Lat: 1, Lng: 2}), mp(gae.DSGeo
Point{Lat: 3, Lng: 4})}, | |
| 790 }, | |
| 791 }, | |
| 792 { | |
| 793 desc: "convertable complex slice (bad load)", | |
| 794 src: gae.DSPropertyMap{"Values": {mp("hello")}}, | |
| 795 want: &Impossible4{[]Complex(nil)}, | |
| 796 loadErr: "nope", | |
| 797 }, | |
| 798 { | |
| 799 desc: "allow concrete gae.DSKey implementors (save)", | |
| 800 src: &DerivedKey{testKey2a.(*GenericDSKey)}, | |
| 801 want: &IfaceKey{testKey2b}, | |
| 802 }, | |
| 803 { | |
| 804 desc: "allow concrete gae.DSKey implementors (load)", | |
| 805 src: &IfaceKey{testKey2b}, | |
| 806 want: &DerivedKey{testKey2a.(*GenericDSKey)}, | |
| 807 }, | |
| 808 { | |
| 809 desc: "save []float64 load []int64", | |
| 810 src: &Y0{B: true, F: []float64{7, 8, 9}}, | |
| 811 want: &Y2{B: true}, | |
| 812 loadErr: "type mismatch", | |
| 813 }, | |
| 814 { | |
| 815 desc: "single slice is too long", | |
| 816 src: &Y0{F: make([]float64, maxIndexedProperties+1)}, | |
| 817 want: &Y0{}, | |
| 818 saveErr: "gae: too many indexed properties", | |
| 819 }, | |
| 820 { | |
| 821 desc: "two slices are too long", | |
| 822 src: &Y0{F: make([]float64, maxIndexedProperties), G: make([
]float64, maxIndexedProperties)}, | |
| 823 want: &Y0{}, | |
| 824 saveErr: "gae: too many indexed properties", | |
| 825 }, | |
| 826 { | |
| 827 desc: "one slice and one scalar are too long", | |
| 828 src: &Y0{F: make([]float64, maxIndexedProperties), B: true}, | |
| 829 want: &Y0{}, | |
| 830 saveErr: "gae: too many indexed properties", | |
| 831 }, | |
| 832 { | |
| 833 desc: "long blob", | |
| 834 src: &B0{B: makeUint8Slice(maxIndexedProperties + 1)}, | |
| 835 want: &B0{B: makeUint8Slice(maxIndexedProperties + 1)}, | |
| 836 }, | |
| 837 { | |
| 838 desc: "long []int8 is too long", | |
| 839 src: &B1{B: makeInt8Slice(maxIndexedProperties + 1)}, | |
| 840 want: &B1{}, | |
| 841 saveErr: "gae: too many indexed properties", | |
| 842 }, | |
| 843 { | |
| 844 desc: "short []int8", | |
| 845 src: &B1{B: makeInt8Slice(3)}, | |
| 846 want: &B1{B: makeInt8Slice(3)}, | |
| 847 }, | |
| 848 { | |
| 849 desc: "long myBlob", | |
| 850 src: &B2{B: makeUint8Slice(maxIndexedProperties + 1)}, | |
| 851 want: &B2{B: makeUint8Slice(maxIndexedProperties + 1)}, | |
| 852 }, | |
| 853 { | |
| 854 desc: "short myBlob", | |
| 855 src: &B2{B: makeUint8Slice(3)}, | |
| 856 want: &B2{B: makeUint8Slice(3)}, | |
| 857 }, | |
| 858 { | |
| 859 desc: "long []myByte", | |
| 860 src: &B3{B: makeMyByteSlice(maxIndexedProperties + 1)}, | |
| 861 want: &B3{B: makeMyByteSlice(maxIndexedProperties + 1)}, | |
| 862 }, | |
| 863 { | |
| 864 desc: "short []myByte", | |
| 865 src: &B3{B: makeMyByteSlice(3)}, | |
| 866 want: &B3{B: makeMyByteSlice(3)}, | |
| 867 }, | |
| 868 { | |
| 869 desc: "slice of blobs", | |
| 870 src: &B4{B: [][]byte{ | |
| 871 makeUint8Slice(3), | |
| 872 makeUint8Slice(4), | |
| 873 makeUint8Slice(5), | |
| 874 }}, | |
| 875 want: &B4{B: [][]byte{ | |
| 876 makeUint8Slice(3), | |
| 877 makeUint8Slice(4), | |
| 878 makeUint8Slice(5), | |
| 879 }}, | |
| 880 }, | |
| 881 { | |
| 882 desc: "short gae.DSByteString", | |
| 883 src: &B5{B: gae.DSByteString(makeUint8Slice(3))}, | |
| 884 want: &B5{B: gae.DSByteString(makeUint8Slice(3))}, | |
| 885 }, | |
| 886 { | |
| 887 desc: "short gae.DSByteString as props", | |
| 888 src: &B5{B: gae.DSByteString(makeUint8Slice(3))}, | |
| 889 want: gae.DSPropertyMap{ | |
| 890 "B": {mp(gae.DSByteString(makeUint8Slice(3)))}, | |
| 891 }, | |
| 892 }, | |
| 893 { | |
| 894 desc: "[]byte must be noindex", | |
| 895 src: gae.DSPropertyMap{ | |
| 896 "B": {mp(makeUint8Slice(3))}, | |
| 897 }, | |
| 898 actualNoIndex: true, | |
| 899 }, | |
| 900 { | |
| 901 desc: "save tagged load props", | |
| 902 src: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6
, J: 7}, | |
| 903 want: gae.DSPropertyMap{ | |
| 904 // A and B are renamed to a and b; A and C are noindex,
I is ignored. | |
| 905 // Indexed properties are loaded before raw properties.
Thus, the | |
| 906 // result is: b, b, b, D, E, a, c. | |
| 907 "b1": { | |
| 908 mp(21), | |
| 909 mp(22), | |
| 910 mp(23), | |
| 911 }, | |
| 912 "D": {mp(4)}, | |
| 913 "E": {mp(5)}, | |
| 914 "a": {mpNI(1)}, | |
| 915 "C": {mpNI(3)}, | |
| 916 "J": {mpNI(7)}, | |
| 917 }, | |
| 918 }, | |
| 919 { | |
| 920 desc: "save tagged load tagged", | |
| 921 src: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6
, J: 7}, | |
| 922 want: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, J: 7
}, | |
| 923 }, | |
| 924 { | |
| 925 desc: "save props load tagged", | |
| 926 src: gae.DSPropertyMap{ | |
| 927 "A": {mpNI(11)}, | |
| 928 "a": {mpNI(12)}, | |
| 929 }, | |
| 930 want: &Tagged{A: 12}, | |
| 931 loadErr: `cannot load field "A"`, | |
| 932 }, | |
| 933 { | |
| 934 desc: "invalid tagged1", | |
| 935 src: &InvalidTagged1{I: 1}, | |
| 936 plsErr: `struct tag has invalid property name: "\t"`, | |
| 937 }, | |
| 938 { | |
| 939 desc: "invalid tagged2", | |
| 940 src: &InvalidTagged2{I: 1, J: 2}, | |
| 941 want: &InvalidTagged2{}, | |
| 942 plsErr: `struct tag has repeated property name: "I"`, | |
| 943 }, | |
| 944 { | |
| 945 desc: "invalid tagged3", | |
| 946 src: &InvalidTagged3{I: 1}, | |
| 947 plsErr: `struct tag has invalid property name: "a\t"`, | |
| 948 }, | |
| 949 { | |
| 950 desc: "invalid tagged4", | |
| 951 src: &InvalidTagged4{I: 1}, | |
| 952 plsErr: `struct tag has invalid property name: "a."`, | |
| 953 }, | |
| 954 { | |
| 955 desc: "invalid tagged5", | |
| 956 src: &InvalidTagged5{I: 19, V: []InvalidTaggedSub{{1}}}, | |
| 957 plsErr: `struct tag has repeated property name: "V.I"`, | |
| 958 }, | |
| 959 { | |
| 960 desc: "doubler", | |
| 961 src: &Doubler{S: "s", I: 1, B: true}, | |
| 962 want: &Doubler{S: "ss", I: 2, B: true}, | |
| 963 }, | |
| 964 { | |
| 965 desc: "save struct load props", | |
| 966 src: &X0{S: "s", I: 1}, | |
| 967 want: gae.DSPropertyMap{ | |
| 968 "S": {mp("s")}, | |
| 969 "I": {mp(1)}, | |
| 970 }, | |
| 971 }, | |
| 972 { | |
| 973 desc: "save props load struct", | |
| 974 src: gae.DSPropertyMap{ | |
| 975 "S": {mp("s")}, | |
| 976 "I": {mp(1)}, | |
| 977 }, | |
| 978 want: &X0{S: "s", I: 1}, | |
| 979 }, | |
| 980 { | |
| 981 desc: "nil-value props", | |
| 982 src: gae.DSPropertyMap{ | |
| 983 "I": {mp(nil)}, | |
| 984 "B": {mp(nil)}, | |
| 985 "S": {mp(nil)}, | |
| 986 "F": {mp(nil)}, | |
| 987 "K": {mp(nil)}, | |
| 988 "T": {mp(nil)}, | |
| 989 "J": { | |
| 990 mp(nil), | |
| 991 mp(7), | |
| 992 mp(nil), | |
| 993 }, | |
| 994 }, | |
| 995 want: &struct { | |
| 996 I int64 | |
| 997 B bool | |
| 998 S string | |
| 999 F float64 | |
| 1000 K gae.DSKey | |
| 1001 T time.Time | |
| 1002 J []int64 | |
| 1003 }{ | |
| 1004 J: []int64{0, 7, 0}, | |
| 1005 }, | |
| 1006 }, | |
| 1007 { | |
| 1008 desc: "save outer load props", | |
| 1009 src: &Outer{ | |
| 1010 A: 1, | |
| 1011 I: []Inner1{ | |
| 1012 {10, "ten"}, | |
| 1013 {20, "twenty"}, | |
| 1014 {30, "thirty"}, | |
| 1015 }, | |
| 1016 J: Inner2{ | |
| 1017 Y: 3.14, | |
| 1018 }, | |
| 1019 Inner3: Inner3{ | |
| 1020 Z: true, | |
| 1021 }, | |
| 1022 }, | |
| 1023 want: gae.DSPropertyMap{ | |
| 1024 "A": {mp(1)}, | |
| 1025 "I.W": { | |
| 1026 mp(10), | |
| 1027 mp(20), | |
| 1028 mp(30), | |
| 1029 }, | |
| 1030 "I.X": { | |
| 1031 mp("ten"), | |
| 1032 mp("twenty"), | |
| 1033 mp("thirty"), | |
| 1034 }, | |
| 1035 "J.Y": {mp(3.14)}, | |
| 1036 "Z": {mp(true)}, | |
| 1037 }, | |
| 1038 }, | |
| 1039 { | |
| 1040 desc: "save props load outer-equivalent", | |
| 1041 src: gae.DSPropertyMap{ | |
| 1042 "A": {mp(1)}, | |
| 1043 "I.W": { | |
| 1044 mp(10), | |
| 1045 mp(20), | |
| 1046 mp(30), | |
| 1047 }, | |
| 1048 "I.X": { | |
| 1049 mp("ten"), | |
| 1050 mp("twenty"), | |
| 1051 mp("thirty"), | |
| 1052 }, | |
| 1053 "J.Y": {mp(3.14)}, | |
| 1054 "Z": {mp(true)}, | |
| 1055 }, | |
| 1056 want: &OuterEquivalent{ | |
| 1057 A: 1, | |
| 1058 IDotW: []int32{10, 20, 30}, | |
| 1059 IDotX: []string{"ten", "twenty", "thirty"}, | |
| 1060 JDotY: 3.14, | |
| 1061 Z: true, | |
| 1062 }, | |
| 1063 }, | |
| 1064 { | |
| 1065 desc: "save outer-equivalent load outer", | |
| 1066 src: &OuterEquivalent{ | |
| 1067 A: 1, | |
| 1068 IDotW: []int32{10, 20, 30}, | |
| 1069 IDotX: []string{"ten", "twenty", "thirty"}, | |
| 1070 JDotY: 3.14, | |
| 1071 Z: true, | |
| 1072 }, | |
| 1073 want: &Outer{ | |
| 1074 A: 1, | |
| 1075 I: []Inner1{ | |
| 1076 {10, "ten"}, | |
| 1077 {20, "twenty"}, | |
| 1078 {30, "thirty"}, | |
| 1079 }, | |
| 1080 J: Inner2{ | |
| 1081 Y: 3.14, | |
| 1082 }, | |
| 1083 Inner3: Inner3{ | |
| 1084 Z: true, | |
| 1085 }, | |
| 1086 }, | |
| 1087 }, | |
| 1088 { | |
| 1089 desc: "dotted names save", | |
| 1090 src: &Dotted{A: DottedA{B: DottedB{C: 88}}}, | |
| 1091 want: gae.DSPropertyMap{ | |
| 1092 "A0.A1.A2.B3.C4.C5": {mp(88)}, | |
| 1093 }, | |
| 1094 }, | |
| 1095 { | |
| 1096 desc: "dotted names load", | |
| 1097 src: gae.DSPropertyMap{ | |
| 1098 "A0.A1.A2.B3.C4.C5": {mp(99)}, | |
| 1099 }, | |
| 1100 want: &Dotted{A: DottedA{B: DottedB{C: 99}}}, | |
| 1101 }, | |
| 1102 { | |
| 1103 desc: "save struct load deriver", | |
| 1104 src: &X0{S: "s", I: 1}, | |
| 1105 want: &Deriver{S: "s", Derived: "derived+s"}, | |
| 1106 }, | |
| 1107 { | |
| 1108 desc: "save deriver load struct", | |
| 1109 src: &Deriver{S: "s", Derived: "derived+s", Ignored: "ignored"}
, | |
| 1110 want: &X0{S: "s"}, | |
| 1111 }, | |
| 1112 // Regression: CL 25062824 broke handling of appengine.BlobKey fields. | |
| 1113 { | |
| 1114 desc: "appengine.BlobKey", | |
| 1115 src: &BK{Key: "blah"}, | |
| 1116 want: &BK{Key: "blah"}, | |
| 1117 }, | |
| 1118 { | |
| 1119 desc: "zero time.Time", | |
| 1120 src: &T{T: time.Time{}}, | |
| 1121 want: &T{T: time.Time{}}, | |
| 1122 }, | |
| 1123 { | |
| 1124 desc: "time.Time near Unix zero time", | |
| 1125 src: &T{T: time.Unix(0, 4e3).UTC()}, | |
| 1126 want: &T{T: time.Unix(0, 4e3).UTC()}, | |
| 1127 }, | |
| 1128 { | |
| 1129 desc: "time.Time, far in the future", | |
| 1130 src: &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)}, | |
| 1131 want: &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)}, | |
| 1132 }, | |
| 1133 { | |
| 1134 desc: "time.Time, very far in the past", | |
| 1135 src: &T{T: time.Date(-300000, 1, 1, 0, 0, 0, 0, time.UTC)}, | |
| 1136 want: &T{}, | |
| 1137 saveErr: "time value out of range", | |
| 1138 }, | |
| 1139 { | |
| 1140 desc: "time.Time, very far in the future", | |
| 1141 src: &T{T: time.Date(294248, 1, 1, 0, 0, 0, 0, time.UTC)}, | |
| 1142 want: &T{}, | |
| 1143 saveErr: "time value out of range", | |
| 1144 }, | |
| 1145 { | |
| 1146 desc: "structs", | |
| 1147 src: &N0{ | |
| 1148 X0: X0{S: "one", I: 2, i: 3}, | |
| 1149 Nonymous: X0{S: "four", I: 5, i: 6}, | |
| 1150 Ignore: "ignore", | |
| 1151 Other: "other", | |
| 1152 }, | |
| 1153 want: &N0{ | |
| 1154 X0: X0{S: "one", I: 2}, | |
| 1155 Nonymous: X0{S: "four", I: 5}, | |
| 1156 Other: "other", | |
| 1157 }, | |
| 1158 }, | |
| 1159 { | |
| 1160 desc: "exotic types", | |
| 1161 src: &ExoticTypes{ | |
| 1162 BS: "sup", | |
| 1163 DSBS: gae.DSByteString("nerds"), | |
| 1164 }, | |
| 1165 want: &ExoticTypes{ | |
| 1166 BS: "sup", | |
| 1167 DSBS: gae.DSByteString("nerds"), | |
| 1168 }, | |
| 1169 }, | |
| 1170 { | |
| 1171 desc: "underspecified types", | |
| 1172 src: &Underspecified{}, | |
| 1173 plsErr: "non-concrete interface", | |
| 1174 }, | |
| 1175 { | |
| 1176 desc: "mismatch (string)", | |
| 1177 src: gae.DSPropertyMap{ | |
| 1178 "K": {mp(199)}, | |
| 1179 "S": {mp([]byte("cats"))}, | |
| 1180 "F": {mp(gae.DSByteString("nurbs"))}, | |
| 1181 }, | |
| 1182 want: &MismatchTypes{}, | |
| 1183 loadErr: "type mismatch", | |
| 1184 }, | |
| 1185 { | |
| 1186 desc: "mismatch (float)", | |
| 1187 src: gae.DSPropertyMap{"F": {mp(gae.BSKey("wot"))}}, | |
| 1188 want: &MismatchTypes{}, | |
| 1189 loadErr: "type mismatch", | |
| 1190 }, | |
| 1191 { | |
| 1192 desc: "mismatch (float/overflow)", | |
| 1193 src: gae.DSPropertyMap{"F": {mp(math.MaxFloat64)}}, | |
| 1194 want: &MismatchTypes{}, | |
| 1195 loadErr: "overflows", | |
| 1196 }, | |
| 1197 { | |
| 1198 desc: "mismatch (key)", | |
| 1199 src: gae.DSPropertyMap{"K": {mp(false)}}, | |
| 1200 want: &MismatchTypes{}, | |
| 1201 loadErr: "type mismatch", | |
| 1202 }, | |
| 1203 { | |
| 1204 desc: "mismatch (bool)", | |
| 1205 src: gae.DSPropertyMap{"B": {mp(testKey0)}}, | |
| 1206 want: &MismatchTypes{}, | |
| 1207 loadErr: "type mismatch", | |
| 1208 }, | |
| 1209 { | |
| 1210 desc: "mismatch (time)", | |
| 1211 src: gae.DSPropertyMap{"T": {mp(gae.DSGeoPoint{})}}, | |
| 1212 want: &MismatchTypes{}, | |
| 1213 loadErr: "type mismatch", | |
| 1214 }, | |
| 1215 { | |
| 1216 desc: "mismatch (geopoint)", | |
| 1217 src: gae.DSPropertyMap{"G": {mp(time.Now().UTC())}}, | |
| 1218 want: &MismatchTypes{}, | |
| 1219 loadErr: "type mismatch", | |
| 1220 }, | |
| 1221 { | |
| 1222 desc: "slice of structs", | |
| 1223 src: &N1{ | |
| 1224 X0: X0{S: "one", I: 2, i: 3}, | |
| 1225 Nonymous: []X0{ | |
| 1226 {S: "four", I: 5, i: 6}, | |
| 1227 {S: "seven", I: 8, i: 9}, | |
| 1228 {S: "ten", I: 11, i: 12}, | |
| 1229 {S: "thirteen", I: 14, i: 15}, | |
| 1230 }, | |
| 1231 Ignore: "ignore", | |
| 1232 Other: "other", | |
| 1233 }, | |
| 1234 want: &N1{ | |
| 1235 X0: X0{S: "one", I: 2}, | |
| 1236 Nonymous: []X0{ | |
| 1237 {S: "four", I: 5}, | |
| 1238 {S: "seven", I: 8}, | |
| 1239 {S: "ten", I: 11}, | |
| 1240 {S: "thirteen", I: 14}, | |
| 1241 }, | |
| 1242 Other: "other", | |
| 1243 }, | |
| 1244 }, | |
| 1245 { | |
| 1246 desc: "structs with slices of structs", | |
| 1247 src: &N2{ | |
| 1248 N1: N1{ | |
| 1249 X0: X0{S: "rouge"}, | |
| 1250 Nonymous: []X0{ | |
| 1251 {S: "rosso0"}, | |
| 1252 {S: "rosso1"}, | |
| 1253 }, | |
| 1254 }, | |
| 1255 Green: N1{ | |
| 1256 X0: X0{S: "vert"}, | |
| 1257 Nonymous: []X0{ | |
| 1258 {S: "verde0"}, | |
| 1259 {S: "verde1"}, | |
| 1260 {S: "verde2"}, | |
| 1261 }, | |
| 1262 }, | |
| 1263 Blue: N1{ | |
| 1264 X0: X0{S: "bleu"}, | |
| 1265 Nonymous: []X0{ | |
| 1266 {S: "blu0"}, | |
| 1267 {S: "blu1"}, | |
| 1268 {S: "blu2"}, | |
| 1269 {S: "blu3"}, | |
| 1270 }, | |
| 1271 }, | |
| 1272 }, | |
| 1273 want: &N2{ | |
| 1274 N1: N1{ | |
| 1275 X0: X0{S: "rouge"}, | |
| 1276 Nonymous: []X0{ | |
| 1277 {S: "rosso0"}, | |
| 1278 {S: "rosso1"}, | |
| 1279 }, | |
| 1280 }, | |
| 1281 Green: N1{ | |
| 1282 X0: X0{S: "vert"}, | |
| 1283 Nonymous: []X0{ | |
| 1284 {S: "verde0"}, | |
| 1285 {S: "verde1"}, | |
| 1286 {S: "verde2"}, | |
| 1287 }, | |
| 1288 }, | |
| 1289 Blue: N1{ | |
| 1290 X0: X0{S: "bleu"}, | |
| 1291 Nonymous: []X0{ | |
| 1292 {S: "blu0"}, | |
| 1293 {S: "blu1"}, | |
| 1294 {S: "blu2"}, | |
| 1295 {S: "blu3"}, | |
| 1296 }, | |
| 1297 }, | |
| 1298 }, | |
| 1299 }, | |
| 1300 { | |
| 1301 desc: "save structs load props", | |
| 1302 src: &N2{ | |
| 1303 N1: N1{ | |
| 1304 X0: X0{S: "rouge"}, | |
| 1305 Nonymous: []X0{ | |
| 1306 {S: "rosso0"}, | |
| 1307 {S: "rosso1"}, | |
| 1308 }, | |
| 1309 }, | |
| 1310 Green: N1{ | |
| 1311 X0: X0{S: "vert"}, | |
| 1312 Nonymous: []X0{ | |
| 1313 {S: "verde0"}, | |
| 1314 {S: "verde1"}, | |
| 1315 {S: "verde2"}, | |
| 1316 }, | |
| 1317 }, | |
| 1318 Blue: N1{ | |
| 1319 X0: X0{S: "bleu"}, | |
| 1320 Nonymous: []X0{ | |
| 1321 {S: "blu0"}, | |
| 1322 {S: "blu1"}, | |
| 1323 {S: "blu2"}, | |
| 1324 {S: "blu3"}, | |
| 1325 }, | |
| 1326 }, | |
| 1327 }, | |
| 1328 want: gae.DSPropertyMap{ | |
| 1329 "red.S": {mp("rouge")}, | |
| 1330 "red.I": {mp(0)}, | |
| 1331 "red.Nonymous.S": {mp("rosso0"), mp("rosso1")}, | |
| 1332 "red.Nonymous.I": {mp(0), mp(0)}, | |
| 1333 "red.Other": {mp("")}, | |
| 1334 "green.S": {mp("vert")}, | |
| 1335 "green.I": {mp(0)}, | |
| 1336 "green.Nonymous.S": {mp("verde0"), mp("verde1"), mp("ver
de2")}, | |
| 1337 "green.Nonymous.I": {mp(0), mp(0), mp(0)}, | |
| 1338 "green.Other": {mp("")}, | |
| 1339 "Blue.S": {mp("bleu")}, | |
| 1340 "Blue.I": {mp(0)}, | |
| 1341 "Blue.Nonymous.S": {mp("blu0"), mp("blu1"), mp("blu2"),
mp("blu3")}, | |
| 1342 "Blue.Nonymous.I": {mp(0), mp(0), mp(0), mp(0)}, | |
| 1343 "Blue.Other": {mp("")}, | |
| 1344 }, | |
| 1345 }, | |
| 1346 { | |
| 1347 desc: "save props load structs with ragged fields", | |
| 1348 src: gae.DSPropertyMap{ | |
| 1349 "red.S": {mp("rot")}, | |
| 1350 "green.Nonymous.I": {mp(10), mp(11), mp(12), mp(13)}, | |
| 1351 "Blue.Nonymous.S": {mp("blau0"), mp("blau1"), mp("blau2
")}, | |
| 1352 "Blue.Nonymous.I": {mp(20), mp(21)}, | |
| 1353 }, | |
| 1354 want: &N2{ | |
| 1355 N1: N1{ | |
| 1356 X0: X0{S: "rot"}, | |
| 1357 }, | |
| 1358 Green: N1{ | |
| 1359 Nonymous: []X0{ | |
| 1360 {I: 10}, | |
| 1361 {I: 11}, | |
| 1362 {I: 12}, | |
| 1363 {I: 13}, | |
| 1364 }, | |
| 1365 }, | |
| 1366 Blue: N1{ | |
| 1367 Nonymous: []X0{ | |
| 1368 {S: "blau0", I: 20}, | |
| 1369 {S: "blau1", I: 21}, | |
| 1370 {S: "blau2"}, | |
| 1371 }, | |
| 1372 }, | |
| 1373 }, | |
| 1374 }, | |
| 1375 { | |
| 1376 desc: "save structs with noindex tags", | |
| 1377 src: &struct { | |
| 1378 A struct { | |
| 1379 X string `gae:",noindex"` | |
| 1380 Y string | |
| 1381 } `gae:",noindex"` | |
| 1382 B struct { | |
| 1383 X string `gae:",noindex"` | |
| 1384 Y string | |
| 1385 } | |
| 1386 }{}, | |
| 1387 want: gae.DSPropertyMap{ | |
| 1388 "B.Y": {mp("")}, | |
| 1389 "A.X": {mpNI("")}, | |
| 1390 "A.Y": {mpNI("")}, | |
| 1391 "B.X": {mpNI("")}, | |
| 1392 }, | |
| 1393 }, | |
| 1394 { | |
| 1395 desc: "embedded struct with name override", | |
| 1396 src: &struct { | |
| 1397 Inner1 `gae:"foo"` | |
| 1398 }{}, | |
| 1399 want: gae.DSPropertyMap{ | |
| 1400 "foo.W": {mp(0)}, | |
| 1401 "foo.X": {mp("")}, | |
| 1402 }, | |
| 1403 }, | |
| 1404 { | |
| 1405 desc: "slice of slices", | |
| 1406 src: &SliceOfSlices{}, | |
| 1407 plsErr: `flattening nested structs leads to a slice of slices: f
ield "S"`, | |
| 1408 }, | |
| 1409 { | |
| 1410 desc: "recursive struct", | |
| 1411 src: &Recursive{}, | |
| 1412 plsErr: `field "R" is recursively defined`, | |
| 1413 }, | |
| 1414 { | |
| 1415 desc: "mutually recursive struct", | |
| 1416 src: &MutuallyRecursive0{}, | |
| 1417 plsErr: `field "R" has problem: field "R" is recursively defined
`, | |
| 1418 }, | |
| 1419 { | |
| 1420 desc: "non-exported struct fields", | |
| 1421 src: &struct { | |
| 1422 i, J int64 | |
| 1423 }{i: 1, J: 2}, | |
| 1424 want: gae.DSPropertyMap{ | |
| 1425 "J": {mp(2)}, | |
| 1426 }, | |
| 1427 }, | |
| 1428 { | |
| 1429 desc: "json.RawMessage", | |
| 1430 src: &struct { | |
| 1431 J json.RawMessage | |
| 1432 }{ | |
| 1433 J: json.RawMessage("rawr"), | |
| 1434 }, | |
| 1435 want: gae.DSPropertyMap{ | |
| 1436 "J": {mp([]byte("rawr"))}, | |
| 1437 }, | |
| 1438 }, | |
| 1439 { | |
| 1440 desc: "json.RawMessage to myBlob", | |
| 1441 src: &struct { | |
| 1442 B json.RawMessage | |
| 1443 }{ | |
| 1444 B: json.RawMessage("rawr"), | |
| 1445 }, | |
| 1446 want: &B2{B: myBlob("rawr")}, | |
| 1447 }, | |
| 1448 } | |
| 1449 | |
| 1450 // checkErr returns the empty string if either both want and err are zero, | |
| 1451 // or if want is a non-empty substring of err's string representation. | |
| 1452 func checkErr(want string, err error) string { | |
| 1453 if err != nil { | |
| 1454 got := err.Error() | |
| 1455 if want == "" || strings.Index(got, want) == -1 { | |
| 1456 return got | |
| 1457 } | |
| 1458 } else if want != "" { | |
| 1459 return fmt.Sprintf("want error %q", want) | |
| 1460 } | |
| 1461 return "" | |
| 1462 } | |
| 1463 | |
| 1464 func ShouldErrLike(actual interface{}, expected ...interface{}) string { | |
| 1465 e2s := func(o interface{}) (string, bool) { | |
| 1466 switch x := o.(type) { | |
| 1467 case nil: | |
| 1468 return "", true | |
| 1469 case string: | |
| 1470 return x, true | |
| 1471 case error: | |
| 1472 if x != nil { | |
| 1473 return x.Error(), true | |
| 1474 } | |
| 1475 return "", true | |
| 1476 } | |
| 1477 return fmt.Sprintf("unknown argument type %T, expected string, e
rror or nil", actual), false | |
| 1478 } | |
| 1479 | |
| 1480 as, ok := e2s(actual) | |
| 1481 if !ok { | |
| 1482 return as | |
| 1483 } | |
| 1484 | |
| 1485 if len(expected) != 1 { | |
| 1486 return fmt.Sprintf("Assertion requires 1 expected value, got %d"
, len(expected)) | |
| 1487 } | |
| 1488 | |
| 1489 err, ok := e2s(expected[0]) | |
| 1490 if !ok { | |
| 1491 return err | |
| 1492 } | |
| 1493 return ShouldContainSubstring(as, err) | |
| 1494 } | |
| 1495 | |
| 1496 func TestRoundTrip(t *testing.T) { | |
| 1497 t.Parallel() | |
| 1498 | |
| 1499 checkErr := func(actual interface{}, expected string) bool { | |
| 1500 So(actual, ShouldErrLike, expected) | |
| 1501 return expected != "" | |
| 1502 } | |
| 1503 | |
| 1504 Convey("Test round-trip", t, func() { | |
| 1505 for _, tc := range testCases { | |
| 1506 tc := tc | |
| 1507 Convey(tc.desc, func() { | |
| 1508 pls, ok := tc.src.(gae.DSPropertyLoadSaver) | |
| 1509 if !ok { | |
| 1510 pls = GetPLS(tc.src) | |
| 1511 } | |
| 1512 if checkErr(pls.Problem(), tc.plsErr) { | |
| 1513 return | |
| 1514 } | |
| 1515 So(pls, ShouldNotBeNil) | |
| 1516 | |
| 1517 savedProps, err := pls.Save(false) | |
| 1518 if checkErr(err, tc.saveErr) { | |
| 1519 return | |
| 1520 } | |
| 1521 So(savedProps, ShouldNotBeNil) | |
| 1522 | |
| 1523 if tc.actualNoIndex { | |
| 1524 for _, props := range savedProps { | |
| 1525 So(props[0].IndexSetting(), Shou
ldEqual, gae.NoIndex) | |
| 1526 return | |
| 1527 } | |
| 1528 So(true, ShouldBeFalse) // shouldn't get
here | |
| 1529 } | |
| 1530 | |
| 1531 var got interface{} | |
| 1532 if _, ok := tc.want.(gae.DSPropertyMap); ok { | |
| 1533 pls = gae.DSPropertyMap{} | |
| 1534 got = pls | |
| 1535 } else { | |
| 1536 got = reflect.New(reflect.TypeOf(tc.want
).Elem()).Interface() | |
| 1537 if pls, ok = got.(gae.DSPropertyLoadSave
r); !ok { | |
| 1538 pls = GetPLS(got) | |
| 1539 } | |
| 1540 } | |
| 1541 | |
| 1542 if checkErr(pls.Problem(), tc.plsLoadErr) { | |
| 1543 return | |
| 1544 } | |
| 1545 So(pls, ShouldNotBeNil) | |
| 1546 | |
| 1547 err = pls.Load(savedProps) | |
| 1548 if checkErr(err, tc.loadErr) { | |
| 1549 return | |
| 1550 } | |
| 1551 if tc.want == nil { | |
| 1552 return | |
| 1553 } | |
| 1554 | |
| 1555 if gotT, ok := got.(*T); ok { | |
| 1556 // Round tripping a time.Time can result
in a different time.Location: Local instead of UTC. | |
| 1557 // We therefore test equality explicitly
, instead of relying on reflect.DeepEqual. | |
| 1558 So(gotT.T.Equal(tc.want.(*T).T), ShouldB
eTrue) | |
| 1559 } else { | |
| 1560 So(got, ShouldResemble, tc.want) | |
| 1561 } | |
| 1562 }) | |
| 1563 } | |
| 1564 }) | |
| 1565 } | |
| 1566 | |
| 1567 func TestSpecial(t *testing.T) { | |
| 1568 t.Parallel() | |
| 1569 | |
| 1570 Convey("Test special fields", t, func() { | |
| 1571 Convey("Can retrieve from struct", func() { | |
| 1572 o := &N0{ID: 100} | |
| 1573 pls := GetPLS(o) | |
| 1574 val, err := pls.GetMeta("id") | |
| 1575 So(err, ShouldBeNil) | |
| 1576 So(val, ShouldEqual, 100) | |
| 1577 | |
| 1578 val, err = pls.GetMeta("kind") | |
| 1579 So(err, ShouldBeNil) | |
| 1580 So(val, ShouldEqual, "whatnow") | |
| 1581 }) | |
| 1582 | |
| 1583 Convey("Getting something not there is an error", func() { | |
| 1584 o := &N0{ID: 100} | |
| 1585 pls := GetPLS(o) | |
| 1586 _, err := pls.GetMeta("wat") | |
| 1587 So(err, ShouldEqual, gae.ErrDSMetaFieldUnset) | |
| 1588 }) | |
| 1589 | |
| 1590 Convey("getting/setting from a bad struct is an error", func() { | |
| 1591 o := &Recursive{} | |
| 1592 pls := GetPLS(o) | |
| 1593 _, err := pls.GetMeta("wat") | |
| 1594 So(err, ShouldNotBeNil) | |
| 1595 | |
| 1596 err = pls.SetMeta("wat", 100) | |
| 1597 So(err, ShouldNotBeNil) | |
| 1598 }) | |
| 1599 | |
| 1600 Convey("can assign values to exported special fields", func() { | |
| 1601 o := &N0{ID: 100} | |
| 1602 pls := GetPLS(o) | |
| 1603 err := pls.SetMeta("id", int64(200)) | |
| 1604 So(err, ShouldBeNil) | |
| 1605 So(o.ID, ShouldEqual, 200) | |
| 1606 | |
| 1607 }) | |
| 1608 | |
| 1609 Convey("assigning to unsassiagnable fields is a simple error", f
unc() { | |
| 1610 o := &N0{ID: 100} | |
| 1611 pls := GetPLS(o) | |
| 1612 err := pls.SetMeta("kind", "hi") | |
| 1613 So(err.Error(), ShouldContainSubstring, "unexported fiel
d") | |
| 1614 | |
| 1615 err = pls.SetMeta("noob", "hi") | |
| 1616 So(err, ShouldEqual, gae.ErrDSMetaFieldUnset) | |
| 1617 }) | |
| 1618 }) | |
| 1619 | |
| 1620 Convey("StructPLS Miscellaneous", t, func() { | |
| 1621 Convey("multiple overlapping fields is an error", func() { | |
| 1622 o := &BadSpecial{} | |
| 1623 pls := GetPLS(o) | |
| 1624 err := pls.Load(nil) | |
| 1625 So(err, ShouldErrLike, "multiple times") | |
| 1626 e := pls.Problem() | |
| 1627 _, err = pls.Save(true) | |
| 1628 So(err, ShouldEqual, e) | |
| 1629 err = pls.Load(nil) | |
| 1630 So(err, ShouldEqual, e) | |
| 1631 }) | |
| 1632 | |
| 1633 Convey("empty property names are invalid", func() { | |
| 1634 So(validPropertyName(""), ShouldBeFalse) | |
| 1635 }) | |
| 1636 | |
| 1637 Convey("attempting to get a PLS for a non *struct is an error",
func() { | |
| 1638 pls := GetPLS((*[]string)(nil)) | |
| 1639 So(pls.Problem(), ShouldEqual, gae.ErrDSInvalidEntityTyp
e) | |
| 1640 }) | |
| 1641 | |
| 1642 Convey("convertible meta default types", func() { | |
| 1643 type OKDefaults struct { | |
| 1644 When string `gae:"$when,tomorrow"` | |
| 1645 Amount int64 `gae:"$amt,100"` | |
| 1646 } | |
| 1647 pls := GetPLS(&OKDefaults{}) | |
| 1648 So(pls.Problem(), ShouldBeNil) | |
| 1649 | |
| 1650 v, err := pls.GetMeta("when") | |
| 1651 So(err, ShouldBeNil) | |
| 1652 So(v, ShouldEqual, "tomorrow") | |
| 1653 | |
| 1654 v, err = pls.GetMeta("amt") | |
| 1655 So(err, ShouldBeNil) | |
| 1656 So(v, ShouldEqual, int64(100)) | |
| 1657 }) | |
| 1658 | |
| 1659 Convey("meta fields can be saved", func() { | |
| 1660 type OKDefaults struct { | |
| 1661 When string `gae:"$when,tomorrow"` | |
| 1662 Amount int64 `gae:"$amt,100"` | |
| 1663 } | |
| 1664 pls := GetPLS(&OKDefaults{}) | |
| 1665 pm, err := pls.Save(true) | |
| 1666 So(err, ShouldBeNil) | |
| 1667 So(pm, ShouldResemble, gae.DSPropertyMap{ | |
| 1668 "$when": {gae.MkDSPropertyNI("tomorrow")}, | |
| 1669 "$amt": {gae.MkDSPropertyNI(100)}, | |
| 1670 }) | |
| 1671 | |
| 1672 v, err := pm.GetMeta("when") | |
| 1673 So(err, ShouldBeNil) | |
| 1674 So(v, ShouldEqual, "tomorrow") | |
| 1675 | |
| 1676 v, err = pm.GetMeta("amt") | |
| 1677 So(err, ShouldBeNil) | |
| 1678 So(v, ShouldEqual, int64(100)) | |
| 1679 }) | |
| 1680 | |
| 1681 Convey("default are optional", func() { | |
| 1682 type OverrideDefault struct { | |
| 1683 Val int64 `gae:"$val"` | |
| 1684 } | |
| 1685 o := &OverrideDefault{} | |
| 1686 pls := GetPLS(o) | |
| 1687 | |
| 1688 v, err := pls.GetMeta("val") | |
| 1689 So(err, ShouldBeNil) | |
| 1690 So(v, ShouldEqual, int64(0)) | |
| 1691 }) | |
| 1692 | |
| 1693 Convey("overridable defaults", func() { | |
| 1694 type OverrideDefault struct { | |
| 1695 Val int64 `gae:"$val,100"` | |
| 1696 } | |
| 1697 o := &OverrideDefault{} | |
| 1698 pls := GetPLS(o) | |
| 1699 | |
| 1700 v, err := pls.GetMeta("val") | |
| 1701 So(err, ShouldBeNil) | |
| 1702 So(v, ShouldEqual, int64(100)) | |
| 1703 | |
| 1704 o.Val = 10 | |
| 1705 v, err = pls.GetMeta("val") | |
| 1706 So(err, ShouldBeNil) | |
| 1707 So(v, ShouldEqual, int64(10)) | |
| 1708 }) | |
| 1709 | |
| 1710 Convey("Bad default meta type", func() { | |
| 1711 type BadDefault struct { | |
| 1712 Val time.Time `gae:"$meta,tomorrow"` | |
| 1713 } | |
| 1714 pls := GetPLS(&BadDefault{}) | |
| 1715 So(pls.Problem().Error(), ShouldContainSubstring, "bad t
ype") | |
| 1716 }) | |
| 1717 }) | |
| 1718 } | |
| OLD | NEW |