github.com/thiagoyeds/go-cloud@v0.26.0/docstore/drivertest/drivertest.go (about) 1 // Copyright 2019 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package drivertest provides a conformance test for implementations of 16 // driver. 17 package drivertest // import "gocloud.dev/docstore/drivertest" 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "math" 25 "reflect" 26 "testing" 27 "time" 28 29 "github.com/google/go-cmp/cmp" 30 "github.com/google/go-cmp/cmp/cmpopts" 31 "github.com/google/uuid" 32 "gocloud.dev/docstore" 33 ds "gocloud.dev/docstore" 34 "gocloud.dev/docstore/driver" 35 "gocloud.dev/gcerrors" 36 "google.golang.org/protobuf/proto" 37 tspb "google.golang.org/protobuf/types/known/timestamppb" 38 ) 39 40 // ByteArray is an array of 2 bytes. 41 type ByteArray [2]byte 42 43 // CollectionKind describes the kind of testing collection to create. 44 type CollectionKind int 45 46 const ( 47 // A collection with a single primary key field of type string named 48 // drivertest.KeyField. 49 SingleKey CollectionKind = iota 50 51 // A collection that will consist entirely of HighScore structs (see below), 52 // whose two primary key fields are "Game" and "Player", both strings. Use 53 // drivertest.HighScoreKey as the key function. 54 TwoKey 55 56 // The collection should behave like a SingleKey collection, except 57 // that the revision field should be drivertest.AlternateRevisionField. 58 AltRev 59 60 // The collection's documents will not have a revision field. 61 NoRev 62 ) 63 64 // Harness descibes the functionality test harnesses must provide to run 65 // conformance tests. 66 type Harness interface { 67 // MakeCollection makes a driver.Collection for testing. 68 MakeCollection(context.Context, CollectionKind) (driver.Collection, error) 69 70 // BeforeDoTypes should return a list of values whose types are valid for the as 71 // function given to BeforeDo. For example, if the driver converts Get actions 72 // to *GetRequests and write actions to *WriteRequests, then BeforeDoTypes should 73 // return []interface{}{&GetRequest{}, &WriteRequest{}}. 74 // TODO(jba): consider splitting these by action kind. 75 BeforeDoTypes() []interface{} 76 77 // BeforeQueryTypes should return a list of values whose types are valid for the as 78 // function given to BeforeQuery. 79 BeforeQueryTypes() []interface{} 80 81 // RevisionsEqual reports whether two revisions are equal. 82 RevisionsEqual(rev1, rev2 interface{}) bool 83 84 // Close closes resources used by the harness. 85 Close() 86 } 87 88 // HarnessMaker describes functions that construct a harness for running tests. 89 // It is called exactly once per test; Harness.Close() will be called when the test is complete. 90 type HarnessMaker func(ctx context.Context, t *testing.T) (Harness, error) 91 92 // UnsupportedType is an enum for types not supported by native codecs. We chose 93 // to describe this negatively (types that aren't supported rather than types 94 // that are) to make the more inclusive cases easier to write. A driver can 95 // return nil for CodecTester.UnsupportedTypes, then add values from this enum 96 // one by one until all tests pass. 97 type UnsupportedType int 98 99 // These are known unsupported types by one or more driver. Each of them 100 // corresponses to an unsupported type specific test which if the driver 101 // actually supports. 102 const ( 103 // Native codec doesn't support any unsigned integer type 104 Uint UnsupportedType = iota 105 // Native codec doesn't support arrays 106 Arrays 107 // Native codec doesn't support full time precision 108 NanosecondTimes 109 // Native codec doesn't support [][]byte 110 BinarySet 111 ) 112 113 // CodecTester describes functions that encode and decode values using both the 114 // docstore codec for a driver, and that driver's own "native" codec. 115 type CodecTester interface { 116 UnsupportedTypes() []UnsupportedType 117 NativeEncode(interface{}) (interface{}, error) 118 NativeDecode(value, dest interface{}) error 119 DocstoreEncode(interface{}) (interface{}, error) 120 DocstoreDecode(value, dest interface{}) error 121 } 122 123 // AsTest represents a test of As functionality. 124 type AsTest interface { 125 // Name should return a descriptive name for the test. 126 Name() string 127 // CollectionCheck will be called to allow verification of Collection.As. 128 CollectionCheck(coll *docstore.Collection) error 129 // QueryCheck will be called after calling Query. It should call it.As and 130 // verify the results. 131 QueryCheck(it *docstore.DocumentIterator) error 132 // ErrorCheck is called to allow verification of Collection.ErrorAs. 133 ErrorCheck(c *docstore.Collection, err error) error 134 } 135 136 type verifyAsFailsOnNil struct{} 137 138 func (verifyAsFailsOnNil) Name() string { 139 return "verify As returns false when passed nil" 140 } 141 142 func (verifyAsFailsOnNil) CollectionCheck(coll *docstore.Collection) error { 143 if coll.As(nil) { 144 return errors.New("want Collection.As to return false when passed nil") 145 } 146 return nil 147 } 148 149 func (verifyAsFailsOnNil) QueryCheck(it *docstore.DocumentIterator) error { 150 if it.As(nil) { 151 return errors.New("want DocumentIterator.As to return false when passed nil") 152 } 153 return nil 154 } 155 156 func (v verifyAsFailsOnNil) ErrorCheck(c *docstore.Collection, err error) (ret error) { 157 defer func() { 158 if recover() == nil { 159 ret = errors.New("want ErrorAs to panic when passed nil") 160 } 161 }() 162 c.ErrorAs(err, nil) 163 return nil 164 } 165 166 // RunConformanceTests runs conformance tests for driver implementations of docstore. 167 func RunConformanceTests(t *testing.T, newHarness HarnessMaker, ct CodecTester, asTests []AsTest) { 168 t.Run("TypeDrivenCodec", func(t *testing.T) { testTypeDrivenDecode(t, ct) }) 169 t.Run("BlindCodec", func(t *testing.T) { testBlindDecode(t, ct) }) 170 171 t.Run("Create", func(t *testing.T) { withRevCollections(t, newHarness, testCreate) }) 172 t.Run("Put", func(t *testing.T) { withRevCollections(t, newHarness, testPut) }) 173 t.Run("Replace", func(t *testing.T) { withRevCollections(t, newHarness, testReplace) }) 174 t.Run("Get", func(t *testing.T) { withRevCollections(t, newHarness, testGet) }) 175 t.Run("Delete", func(t *testing.T) { withRevCollections(t, newHarness, testDelete) }) 176 t.Run("Update", func(t *testing.T) { withRevCollections(t, newHarness, testUpdate) }) 177 t.Run("Data", func(t *testing.T) { withCollection(t, newHarness, SingleKey, testData) }) 178 t.Run("Proto", func(t *testing.T) { withCollection(t, newHarness, SingleKey, testProto) }) 179 t.Run("MultipleActions", func(t *testing.T) { withRevCollections(t, newHarness, testMultipleActions) }) 180 t.Run("GetQueryKeyField", func(t *testing.T) { withRevCollections(t, newHarness, testGetQueryKeyField) }) 181 t.Run("SerializeRevision", func(t *testing.T) { withCollection(t, newHarness, SingleKey, testSerializeRevision) }) 182 t.Run("ActionsOnStructNoRev", func(t *testing.T) { 183 withCollection(t, newHarness, NoRev, testActionsOnStructNoRev) 184 }) 185 t.Run("ActionsWithCompositeID", func(t *testing.T) { withCollection(t, newHarness, TwoKey, testActionsWithCompositeID) }) 186 t.Run("GetQuery", func(t *testing.T) { withCollection(t, newHarness, TwoKey, testGetQuery) }) 187 188 t.Run("ExampleInDoc", func(t *testing.T) { withCollection(t, newHarness, NoRev, testExampleInDoc) }) 189 190 t.Run("BeforeDo", func(t *testing.T) { testBeforeDo(t, newHarness) }) 191 t.Run("BeforeQuery", func(t *testing.T) { testBeforeQuery(t, newHarness) }) 192 193 asTests = append(asTests, verifyAsFailsOnNil{}) 194 t.Run("As", func(t *testing.T) { 195 for _, st := range asTests { 196 if st.Name() == "" { 197 t.Fatalf("AsTest.Name is required") 198 } 199 t.Run(st.Name(), func(t *testing.T) { 200 withCollection(t, newHarness, TwoKey, func(t *testing.T, _ Harness, coll *docstore.Collection) { 201 testAs(t, coll, st) 202 }) 203 }) 204 } 205 }) 206 } 207 208 // withCollection calls f with a fresh harness and an empty collection of the given kind. 209 func withCollection(t *testing.T, newHarness HarnessMaker, kind CollectionKind, f func(*testing.T, Harness, *ds.Collection)) { 210 ctx := context.Background() 211 h, err := newHarness(ctx, t) 212 if err != nil { 213 t.Fatal(err) 214 } 215 defer h.Close() 216 217 withColl(t, h, kind, f) 218 } 219 220 // withRevCollections calls f twice: once with the SingleKey collection, using documents and code that expect 221 // the standard revision field; and once with the AltRev collection, that uses an alternative revisionf field 222 // name. 223 func withRevCollections(t *testing.T, newHarness HarnessMaker, f func(*testing.T, *ds.Collection, string)) { 224 ctx := context.Background() 225 h, err := newHarness(ctx, t) 226 if err != nil { 227 t.Fatal(err) 228 } 229 defer h.Close() 230 231 t.Run("StdRev", func(t *testing.T) { 232 withColl(t, h, SingleKey, func(t *testing.T, _ Harness, coll *ds.Collection) { 233 f(t, coll, ds.DefaultRevisionField) 234 }) 235 }) 236 t.Run("AltRev", func(t *testing.T) { 237 withColl(t, h, AltRev, func(t *testing.T, _ Harness, coll *ds.Collection) { 238 f(t, coll, AlternateRevisionField) 239 }) 240 }) 241 } 242 243 // withColl calls f with h and an empty collection of the given kind. It takes care of closing 244 // the collection after f returns. 245 func withColl(t *testing.T, h Harness, kind CollectionKind, f func(*testing.T, Harness, *ds.Collection)) { 246 ctx := context.Background() 247 dc, err := h.MakeCollection(ctx, kind) 248 if err != nil { 249 t.Fatal(err) 250 } 251 coll := ds.NewCollection(dc) 252 defer coll.Close() 253 clearCollection(t, coll) 254 f(t, h, coll) 255 } 256 257 // KeyField is the primary key field for the main test collection. 258 const KeyField = "name" 259 260 // AlternateRevisionField is used for testing the option to provide a different 261 // name for the revision field. 262 const AlternateRevisionField = "Etag" 263 264 type docmap = map[string]interface{} 265 266 func newDoc(doc interface{}) interface{} { 267 switch v := doc.(type) { 268 case docmap: 269 return docmap{KeyField: v[KeyField]} 270 case *docstruct: 271 return &docstruct{Name: v.Name} 272 } 273 return nil 274 } 275 276 func key(doc interface{}) interface{} { 277 switch d := doc.(type) { 278 case docmap: 279 return d[KeyField] 280 case *docstruct: 281 return d.Name 282 } 283 return nil 284 } 285 286 func setKey(doc, key interface{}) { 287 switch d := doc.(type) { 288 case docmap: 289 d[KeyField] = key 290 case *docstruct: 291 d.Name = key 292 } 293 } 294 295 func revision(doc interface{}, revField string) interface{} { 296 switch d := doc.(type) { 297 case docmap: 298 return d[revField] 299 case *docstruct: 300 if revField == docstore.DefaultRevisionField { 301 return d.DocstoreRevision 302 } 303 return d.Etag 304 } 305 return nil 306 } 307 308 func setRevision(doc, rev interface{}, revField string) { 309 switch d := doc.(type) { 310 case docmap: 311 d[revField] = rev 312 case *docstruct: 313 if revField == docstore.DefaultRevisionField { 314 d.DocstoreRevision = rev 315 } else { 316 d.Etag = rev 317 } 318 } 319 } 320 321 type docstruct struct { 322 Name interface{} `docstore:"name"` 323 DocstoreRevision interface{} 324 Etag interface{} 325 326 I int 327 U uint 328 F float64 329 St string 330 B bool 331 M map[string]interface{} 332 } 333 334 func nonexistentDoc() docmap { return docmap{KeyField: "doesNotExist"} } 335 336 func testCreate(t *testing.T, coll *ds.Collection, revField string) { 337 ctx := context.Background() 338 for _, tc := range []struct { 339 name string 340 doc interface{} 341 wantErr gcerrors.ErrorCode 342 }{ 343 { 344 name: "named map", 345 doc: docmap{KeyField: "testCreateMap", "b": true, revField: nil}, 346 }, 347 { 348 name: "existing", 349 doc: docmap{KeyField: "testCreateMap", revField: nil}, 350 wantErr: gcerrors.AlreadyExists, 351 }, 352 { 353 name: "unnamed map", 354 doc: docmap{"b": true, revField: nil}, 355 }, 356 { 357 name: "named struct", 358 doc: &docstruct{Name: "testCreateStruct", B: true}, 359 }, 360 { 361 name: "unnamed struct", 362 doc: &docstruct{B: true}, 363 }, 364 { 365 name: "empty named struct", 366 doc: &docstruct{Name: "", B: true}, 367 }, 368 { 369 name: "with non-nil revision", 370 doc: docmap{KeyField: "testCreate2", revField: 0}, 371 wantErr: gcerrors.InvalidArgument, 372 }, 373 } { 374 t.Run(tc.name, func(t *testing.T) { 375 if tc.wantErr == gcerrors.OK { 376 checkNoRevisionField(t, tc.doc, revField) 377 if err := coll.Create(ctx, tc.doc); err != nil { 378 t.Fatalf("Create: %v", err) 379 } 380 checkHasRevisionField(t, tc.doc, revField) 381 382 got := newDoc(tc.doc) 383 if err := coll.Get(ctx, got); err != nil { 384 t.Fatalf("Get: %v", err) 385 } 386 if diff := cmpDiff(got, tc.doc); diff != "" { 387 t.Fatal(diff) 388 } 389 } else { 390 err := coll.Create(ctx, tc.doc) 391 checkCode(t, err, tc.wantErr) 392 } 393 }) 394 } 395 } 396 397 func testPut(t *testing.T, coll *ds.Collection, revField string) { 398 ctx := context.Background() 399 must := func(err error) { 400 t.Helper() 401 if err != nil { 402 t.Fatal(err) 403 } 404 } 405 var maprev, strmap interface{} 406 407 for _, tc := range []struct { 408 name string 409 doc interface{} 410 rev bool 411 }{ 412 { 413 name: "create map", 414 doc: docmap{KeyField: "testPutMap", "b": true, revField: nil}, 415 }, 416 { 417 name: "create struct", 418 doc: &docstruct{Name: "testPutStruct", B: true}, 419 }, 420 { 421 name: "replace map", 422 doc: docmap{KeyField: "testPutMap", "b": false, revField: nil}, 423 rev: true, 424 }, 425 { 426 name: "replace struct", 427 doc: &docstruct{Name: "testPutStruct", B: false}, 428 rev: true, 429 }, 430 } { 431 t.Run(tc.name, func(t *testing.T) { 432 checkNoRevisionField(t, tc.doc, revField) 433 must(coll.Put(ctx, tc.doc)) 434 checkHasRevisionField(t, tc.doc, revField) 435 got := newDoc(tc.doc) 436 must(coll.Get(ctx, got)) 437 if diff := cmpDiff(got, tc.doc); diff != "" { 438 t.Fatalf(diff) 439 } 440 if tc.rev { 441 switch v := tc.doc.(type) { 442 case docmap: 443 maprev = v[revField] 444 case *docstruct: 445 if revField == docstore.DefaultRevisionField { 446 strmap = v.DocstoreRevision 447 } else { 448 strmap = v.Etag 449 } 450 } 451 } 452 }) 453 } 454 455 // Putting a doc with a revision field is the same as replace, meaning 456 // it will fail if the document doesn't exist. 457 for _, tc := range []struct { 458 name string 459 doc interface{} 460 }{ 461 { 462 name: "replace map wrong key", 463 doc: docmap{KeyField: "testPutMap2", revField: maprev}, 464 }, 465 { 466 name: "replace struct wrong key", 467 doc: &docstruct{Name: "testPutStruct2", DocstoreRevision: strmap, Etag: strmap}, 468 }, 469 } { 470 t.Run(tc.name, func(t *testing.T) { 471 err := coll.Put(ctx, tc.doc) 472 if c := gcerrors.Code(err); c != gcerrors.NotFound && c != gcerrors.FailedPrecondition { 473 t.Errorf("got %v, want NotFound or FailedPrecondition", err) 474 } 475 }) 476 } 477 478 t.Run("revision", func(t *testing.T) { 479 testRevisionField(t, coll, revField, func(doc interface{}) error { 480 return coll.Put(ctx, doc) 481 }) 482 }) 483 484 err := coll.Put(ctx, &docstruct{Name: ""}) 485 checkCode(t, err, gcerrors.InvalidArgument) 486 } 487 488 func testReplace(t *testing.T, coll *ds.Collection, revField string) { 489 ctx := context.Background() 490 must := func(err error) { 491 t.Helper() 492 if err != nil { 493 t.Fatal(err) 494 } 495 } 496 497 for _, tc := range []struct { 498 name string 499 doc1, doc2 interface{} 500 }{ 501 { 502 name: "replace map", 503 doc1: docmap{KeyField: "testReplaceMap", "s": "a", revField: nil}, 504 doc2: docmap{KeyField: "testReplaceMap", "s": "b", revField: nil}, 505 }, 506 { 507 name: "replace struct", 508 doc1: &docstruct{Name: "testReplaceStruct", St: "a"}, 509 doc2: &docstruct{Name: "testReplaceStruct", St: "b"}, 510 }, 511 } { 512 t.Run(tc.name, func(t *testing.T) { 513 must(coll.Put(ctx, tc.doc1)) 514 checkNoRevisionField(t, tc.doc2, revField) 515 must(coll.Replace(ctx, tc.doc2)) 516 checkHasRevisionField(t, tc.doc2, revField) 517 got := newDoc(tc.doc2) 518 must(coll.Get(ctx, got)) 519 if diff := cmpDiff(got, tc.doc2); diff != "" { 520 t.Fatalf(diff) 521 } 522 }) 523 } 524 525 // Can't replace a nonexistent doc. 526 checkCode(t, coll.Replace(ctx, nonexistentDoc()), gcerrors.NotFound) 527 528 t.Run("revision", func(t *testing.T) { 529 testRevisionField(t, coll, revField, func(doc interface{}) error { 530 return coll.Replace(ctx, doc) 531 }) 532 }) 533 } 534 535 // Check that doc does not have a revision field (or has a nil one). 536 func checkNoRevisionField(t *testing.T, doc interface{}, revField string) { 537 t.Helper() 538 ddoc, err := driver.NewDocument(doc) 539 if err != nil { 540 t.Fatal(err) 541 } 542 if rev, _ := ddoc.GetField(revField); rev != nil { 543 t.Fatal("doc has revision field") 544 } 545 } 546 547 // Check that doc has a non-nil revision field. 548 func checkHasRevisionField(t *testing.T, doc interface{}, revField string) { 549 t.Helper() 550 ddoc, err := driver.NewDocument(doc) 551 if err != nil { 552 t.Fatal(err) 553 } 554 if rev, err := ddoc.GetField(revField); err != nil || rev == nil { 555 t.Fatalf("doc missing revision field (error = %v)", err) 556 } 557 } 558 559 func testGet(t *testing.T, coll *ds.Collection, revField string) { 560 ctx := context.Background() 561 must := func(err error) { 562 t.Helper() 563 if err != nil { 564 t.Fatal(err) 565 } 566 } 567 568 for _, tc := range []struct { 569 name string 570 doc interface{} 571 fps []docstore.FieldPath 572 want interface{} 573 }{ 574 // If Get is called with no field paths, the full document is populated. 575 { 576 name: "get map", 577 doc: docmap{ 578 KeyField: "testGetMap", 579 "s": "a string", 580 "i": int64(95), 581 "f": 32.3, 582 "m": map[string]interface{}{"a": "one", "b": "two"}, 583 revField: nil, 584 }, 585 }, 586 { 587 name: "get struct", 588 doc: &docstruct{ 589 Name: "testGetStruct", 590 St: "a string", 591 I: 95, 592 F: 32.3, 593 M: map[string]interface{}{"a": "one", "b": "two"}, 594 }, 595 }, 596 // If Get is called with field paths, the resulting document has only those fields. 597 { 598 name: "get map with field path", 599 doc: docmap{ 600 KeyField: "testGetMapFP", 601 "s": "a string", 602 "i": int64(95), 603 "f": 32.3, 604 "m": map[string]interface{}{"a": "one", "b": "two"}, 605 revField: nil, 606 }, 607 fps: []docstore.FieldPath{"f", "m.b", ds.FieldPath(revField)}, 608 want: docmap{ 609 KeyField: "testGetMapFP", 610 "f": 32.3, 611 "m": map[string]interface{}{"b": "two"}, 612 }, 613 }, 614 { 615 name: "get struct with field path", 616 doc: &docstruct{ 617 Name: "testGetStructFP", 618 St: "a string", 619 I: 95, 620 F: 32.3, 621 M: map[string]interface{}{"a": "one", "b": "two"}, 622 }, 623 fps: []docstore.FieldPath{"St", "M.a", ds.FieldPath(revField)}, 624 want: &docstruct{ 625 Name: "testGetStructFP", 626 St: "a string", 627 M: map[string]interface{}{"a": "one"}, 628 }, 629 }, 630 { 631 name: "get struct wrong case", 632 doc: &docstruct{ 633 Name: "testGetStructWC", 634 St: "a string", 635 I: 95, 636 F: 32.3, 637 M: map[string]interface{}{"a": "one", "b": "two"}, 638 }, 639 fps: []docstore.FieldPath{"st", "m.a"}, 640 want: &docstruct{ 641 Name: "testGetStructWC", 642 }, 643 }, 644 } { 645 t.Run(tc.name, func(t *testing.T) { 646 must(coll.Put(ctx, tc.doc)) 647 got := newDoc(tc.doc) 648 must(coll.Get(ctx, got, tc.fps...)) 649 if tc.want == nil { 650 tc.want = tc.doc 651 } 652 setRevision(tc.want, revision(got, revField), revField) 653 if diff := cmpDiff(got, tc.want); diff != "" { 654 t.Error("Get with field paths:\n", diff) 655 } 656 }) 657 } 658 659 err := coll.Get(ctx, nonexistentDoc()) 660 checkCode(t, err, gcerrors.NotFound) 661 662 err = coll.Get(ctx, &docstruct{Name: ""}) 663 checkCode(t, err, gcerrors.InvalidArgument) 664 } 665 666 func testDelete(t *testing.T, coll *ds.Collection, revField string) { 667 ctx := context.Background() 668 var rev interface{} 669 670 for _, tc := range []struct { 671 name string 672 doc interface{} 673 wantErr gcerrors.ErrorCode 674 }{ 675 { 676 name: "delete map", 677 doc: docmap{KeyField: "testDeleteMap", revField: nil}, 678 }, 679 { 680 name: "delete map wrong rev", 681 doc: docmap{KeyField: "testDeleteMap", "b": true, revField: nil}, 682 wantErr: gcerrors.FailedPrecondition, 683 }, 684 { 685 name: "delete struct", 686 doc: &docstruct{Name: "testDeleteStruct"}, 687 }, 688 { 689 name: "delete struct wrong rev", 690 doc: &docstruct{Name: "testDeleteStruct", B: true}, 691 wantErr: gcerrors.FailedPrecondition, 692 }, 693 } { 694 t.Run(tc.name, func(t *testing.T) { 695 if err := coll.Put(ctx, tc.doc); err != nil { 696 t.Fatal(err) 697 } 698 if tc.wantErr == gcerrors.OK { 699 rev = revision(tc.doc, revField) 700 if err := coll.Delete(ctx, tc.doc); err != nil { 701 t.Fatal(err) 702 } 703 // The document should no longer exist. 704 if err := coll.Get(ctx, tc.doc); err == nil { 705 t.Error("want error, got nil") 706 } 707 } else { 708 setRevision(tc.doc, rev, revField) 709 checkCode(t, coll.Delete(ctx, tc.doc), gcerrors.FailedPrecondition) 710 } 711 }) 712 } 713 // Delete doesn't fail if the doc doesn't exist. 714 if err := coll.Delete(ctx, nonexistentDoc()); err != nil { 715 t.Errorf("delete nonexistent doc: want nil, got %v", err) 716 } 717 718 err := coll.Delete(ctx, &docstruct{Name: ""}) 719 checkCode(t, err, gcerrors.InvalidArgument) 720 } 721 722 func testUpdate(t *testing.T, coll *ds.Collection, revField string) { 723 ctx := context.Background() 724 for _, tc := range []struct { 725 name string 726 doc interface{} 727 mods ds.Mods 728 want interface{} 729 }{ 730 { 731 name: "update map", 732 doc: docmap{KeyField: "testUpdateMap", "a": "A", "b": "B", "n": 3.5, "i": 1, revField: nil}, 733 mods: ds.Mods{ 734 "a": "X", 735 "b": nil, 736 "c": "C", 737 "n": docstore.Increment(-1), 738 "i": nil, 739 "m": 3, 740 }, 741 want: docmap{KeyField: "testUpdateMap", "a": "X", "c": "C", "n": 2.5, "m": int64(3)}, 742 }, 743 { 744 name: "update map overwrite only", 745 doc: docmap{KeyField: "testUpdateMapWrt", "a": "A", revField: nil}, 746 mods: ds.Mods{ 747 "a": "X", 748 "b": nil, 749 "m": 3, 750 }, 751 want: docmap{KeyField: "testUpdateMapWrt", "a": "X", "m": int64(3)}, 752 }, 753 { 754 name: "update map increment only", 755 doc: docmap{KeyField: "testUpdateMapInc", "a": "A", "n": 3.5, "i": 1, revField: nil}, 756 mods: ds.Mods{ 757 "n": docstore.Increment(-1), 758 "i": docstore.Increment(2.5), 759 "m": docstore.Increment(3), 760 }, 761 want: docmap{KeyField: "testUpdateMapInc", "a": "A", "n": 2.5, "i": 3.5, "m": int64(3)}, 762 }, 763 { 764 name: "update struct", 765 doc: &docstruct{Name: "testUpdateStruct", St: "st", I: 1, F: 3.5}, 766 mods: ds.Mods{ 767 "St": "str", 768 "I": nil, 769 "U": 4, 770 "F": docstore.Increment(-3), 771 }, 772 want: &docstruct{Name: "testUpdateStruct", St: "str", U: 4, F: 0.5}, 773 }, 774 { 775 name: "update struct overwrite only", 776 doc: &docstruct{Name: "testUpdateStructWrt", St: "st", I: 1}, 777 mods: ds.Mods{ 778 "St": "str", 779 "I": nil, 780 "U": 4, 781 }, 782 want: &docstruct{Name: "testUpdateStructWrt", St: "str", U: 4}, 783 }, 784 { 785 name: "update struct increment only", 786 doc: &docstruct{Name: "testUpdateStructInc", St: "st", I: 1, F: 3.5}, 787 mods: ds.Mods{ 788 "U": docstore.Increment(4), 789 "F": docstore.Increment(-3), 790 }, 791 want: &docstruct{Name: "testUpdateStructInc", St: "st", U: 4, I: 1, F: 0.5}, 792 }, 793 } { 794 t.Run(tc.name, func(t *testing.T) { 795 if err := coll.Put(ctx, tc.doc); err != nil { 796 t.Fatal(err) 797 } 798 setRevision(tc.doc, nil, revField) 799 got := newDoc(tc.doc) 800 checkNoRevisionField(t, tc.doc, revField) 801 errs := coll.Actions().Update(tc.doc, tc.mods).Get(got).Do(ctx) 802 if errs != nil { 803 t.Fatal(errs) 804 } 805 checkHasRevisionField(t, tc.doc, revField) 806 setRevision(tc.want, revision(got, revField), revField) 807 if diff := cmp.Diff(got, tc.want, cmpopts.IgnoreUnexported(tspb.Timestamp{})); diff != "" { 808 t.Error(diff) 809 } 810 }) 811 } 812 813 // Can't update a nonexistent doc. 814 if err := coll.Update(ctx, nonexistentDoc(), ds.Mods{"x": "y"}); err == nil { 815 t.Error("nonexistent document: got nil, want error") 816 } 817 818 // Bad increment value. 819 err := coll.Update(ctx, docmap{KeyField: "update invalid"}, ds.Mods{"x": ds.Increment("3")}) 820 checkCode(t, err, gcerrors.InvalidArgument) 821 822 t.Run("revision", func(t *testing.T) { 823 testRevisionField(t, coll, revField, func(doc interface{}) error { 824 return coll.Update(ctx, doc, ds.Mods{"s": "c"}) 825 }) 826 }) 827 } 828 829 // Test that: 830 // - Writing a document with a revision field succeeds if the document hasn't changed. 831 // - Writing a document with a revision field fails if the document has changed. 832 func testRevisionField(t *testing.T, coll *ds.Collection, revField string, write func(interface{}) error) { 833 ctx := context.Background() 834 must := func(err error) { 835 t.Helper() 836 if err != nil { 837 t.Fatal(err) 838 } 839 } 840 for _, tc := range []struct { 841 name string 842 doc interface{} 843 }{ 844 { 845 name: "map revision", 846 doc: docmap{KeyField: "testRevisionMap", "s": "a", revField: nil}, 847 }, 848 { 849 name: "struct revision", 850 doc: &docstruct{Name: "testRevisionStruct", St: "a"}, 851 }, 852 } { 853 t.Run(tc.name, func(t *testing.T) { 854 must(coll.Put(ctx, tc.doc)) 855 got := newDoc(tc.doc) 856 must(coll.Get(ctx, got)) 857 rev := revision(got, revField) 858 if rev == nil { 859 t.Fatal("missing revision field") 860 } 861 // A write should succeed, because the document hasn't changed since it was gotten. 862 if err := write(tc.doc); err != nil { 863 t.Fatalf("write with revision field got %v, want nil", err) 864 } 865 // This write should fail: got's revision field hasn't changed, but the stored document has. 866 err := write(got) 867 if c := gcerrors.Code(err); c != gcerrors.FailedPrecondition && c != gcerrors.NotFound { 868 t.Errorf("write with old revision field: got %v, wanted FailedPrecondition or NotFound", err) 869 } 870 }) 871 } 872 } 873 874 // Verify that the driver can serialize and deserialize revisions. 875 func testSerializeRevision(t *testing.T, h Harness, coll *ds.Collection) { 876 ctx := context.Background() 877 doc := docmap{KeyField: "testSerializeRevision", "x": 1, docstore.DefaultRevisionField: nil} 878 if err := coll.Create(ctx, doc); err != nil { 879 t.Fatal(err) 880 } 881 want := doc[docstore.DefaultRevisionField] 882 if want == nil { 883 t.Fatal("nil revision") 884 } 885 s, err := coll.RevisionToString(want) 886 if err != nil { 887 t.Fatal(err) 888 } 889 got, err := coll.StringToRevision(s) 890 if err != nil { 891 t.Fatal(err) 892 } 893 if !h.RevisionsEqual(got, want) { 894 t.Fatalf("got %v, want %v", got, want) 895 } 896 } 897 898 // Test all Go integer types are supported, and they all come back as int64. 899 func testData(t *testing.T, _ Harness, coll *ds.Collection) { 900 ctx := context.Background() 901 for _, test := range []struct { 902 in, want interface{} 903 }{ 904 {int(-1), int64(-1)}, 905 {int8(-8), int64(-8)}, 906 {int16(-16), int64(-16)}, 907 {int32(-32), int64(-32)}, 908 {int64(-64), int64(-64)}, 909 {uint(1), int64(1)}, 910 {uint8(8), int64(8)}, 911 {uint16(16), int64(16)}, 912 {uint32(32), int64(32)}, 913 {uint64(64), int64(64)}, 914 {float32(3.5), float64(3.5)}, 915 {[]byte{0, 1, 2}, []byte{0, 1, 2}}, 916 } { 917 doc := docmap{KeyField: "testData", "val": test.in} 918 got := docmap{KeyField: doc[KeyField]} 919 if errs := coll.Actions().Put(doc).Get(got).Do(ctx); errs != nil { 920 t.Fatal(errs) 921 } 922 want := docmap{ 923 "val": test.want, 924 KeyField: doc[KeyField], 925 } 926 if len(got) != len(want) { 927 t.Errorf("%v: got %v, want %v", test.in, got, want) 928 } else if g := got["val"]; !cmp.Equal(g, test.want) { 929 t.Errorf("%v: got %v (%T), want %v (%T)", test.in, g, g, test.want, test.want) 930 } 931 } 932 933 // TODO: strings: valid vs. invalid unicode 934 935 } 936 937 var ( 938 // A time with non-zero milliseconds, but zero nanoseconds. 939 milliTime = time.Date(2019, time.March, 27, 0, 0, 0, 5*1e6, time.UTC) 940 // A time with non-zero nanoseconds. 941 nanoTime = time.Date(2019, time.March, 27, 0, 0, 0, 5*1e6+7, time.UTC) 942 ) 943 944 // Test that encoding from a struct and then decoding into the same struct works properly. 945 // The decoding is "type-driven" because the decoder knows the expected type of the value 946 // it is decoding--it is the type of a struct field. 947 func testTypeDrivenDecode(t *testing.T, ct CodecTester) { 948 if ct == nil { 949 t.Skip("no CodecTester") 950 } 951 check := func(in, dec interface{}, encode func(interface{}) (interface{}, error), decode func(interface{}, interface{}) error) { 952 t.Helper() 953 enc, err := encode(in) 954 if err != nil { 955 t.Fatalf("%+v", err) 956 } 957 if err := decode(enc, dec); err != nil { 958 t.Fatalf("%+v", err) 959 } 960 if diff := cmp.Diff(in, dec); diff != "" { 961 t.Error(diff) 962 } 963 } 964 965 s := "bar" 966 dsrt := &docstoreRoundTrip{ 967 N: nil, 968 I: 1, 969 U: 2, 970 F: 2.5, 971 St: "foo", 972 B: true, 973 L: []int{3, 4, 5}, 974 A: [2]int{6, 7}, 975 A2: [2]int8{1, 2}, 976 At: ByteArray{1, 2}, 977 Uu: uuid.NameSpaceDNS, 978 M: map[string]bool{"a": true, "b": false}, 979 By: []byte{6, 7, 8}, 980 P: &s, 981 T: milliTime, 982 } 983 984 check(dsrt, &docstoreRoundTrip{}, ct.DocstoreEncode, ct.DocstoreDecode) 985 986 // Test native-to-docstore and docstore-to-native round trips with a smaller set 987 // of types. 988 nm := &nativeMinimal{ 989 N: nil, 990 I: 1, 991 F: 2.5, 992 St: "foo", 993 B: true, 994 L: []int{3, 4, 5}, 995 A: [2]int{6, 7}, 996 A2: [2]int8{6, 7}, 997 At: ByteArray{1, 2}, 998 M: map[string]bool{"a": true, "b": false}, 999 By: []byte{6, 7, 8}, 1000 P: &s, 1001 T: milliTime, 1002 LF: []float64{18.8, -19.9, 20}, 1003 LS: []string{"foo", "bar"}, 1004 } 1005 check(nm, &nativeMinimal{}, ct.DocstoreEncode, ct.NativeDecode) 1006 check(nm, &nativeMinimal{}, ct.NativeEncode, ct.DocstoreDecode) 1007 1008 // Test various other types, unless they are unsupported. 1009 unsupported := map[UnsupportedType]bool{} 1010 for _, u := range ct.UnsupportedTypes() { 1011 unsupported[u] = true 1012 } 1013 1014 // Unsigned integers. 1015 if !unsupported[Uint] { 1016 type Uint struct { 1017 U uint 1018 } 1019 u := &Uint{10} 1020 check(u, &Uint{}, ct.DocstoreEncode, ct.NativeDecode) 1021 check(u, &Uint{}, ct.NativeEncode, ct.DocstoreDecode) 1022 } 1023 1024 // Arrays. 1025 if !unsupported[Arrays] { 1026 type Arrays struct { 1027 A [2]int 1028 } 1029 a := &Arrays{[2]int{13, 14}} 1030 check(a, &Arrays{}, ct.DocstoreEncode, ct.NativeDecode) 1031 check(a, &Arrays{}, ct.NativeEncode, ct.DocstoreDecode) 1032 } 1033 // Nanosecond-precision time. 1034 type NT struct { 1035 T time.Time 1036 } 1037 1038 nt := &NT{nanoTime} 1039 if unsupported[NanosecondTimes] { 1040 // Expect rounding to the nearest millisecond. 1041 check := func(encode func(interface{}) (interface{}, error), decode func(interface{}, interface{}) error) { 1042 enc, err := encode(nt) 1043 if err != nil { 1044 t.Fatalf("%+v", err) 1045 } 1046 var got NT 1047 if err := decode(enc, &got); err != nil { 1048 t.Fatalf("%+v", err) 1049 } 1050 want := nt.T.Round(time.Millisecond) 1051 if !got.T.Equal(want) { 1052 t.Errorf("got %v, want %v", got.T, want) 1053 } 1054 } 1055 check(ct.DocstoreEncode, ct.NativeDecode) 1056 check(ct.NativeEncode, ct.DocstoreDecode) 1057 } else { 1058 // Expect perfect round-tripping of nanosecond times. 1059 check(nt, &NT{}, ct.DocstoreEncode, ct.NativeDecode) 1060 check(nt, &NT{}, ct.NativeEncode, ct.DocstoreDecode) 1061 } 1062 1063 // Binary sets. 1064 if !unsupported[BinarySet] { 1065 type BinarySet struct { 1066 B [][]byte 1067 } 1068 b := &BinarySet{[][]byte{{15}, {16}, {17}}} 1069 check(b, &BinarySet{}, ct.DocstoreEncode, ct.NativeDecode) 1070 check(b, &BinarySet{}, ct.NativeEncode, ct.DocstoreDecode) 1071 } 1072 } 1073 1074 // Test decoding into an interface{}, where the decoder doesn't know the type of the 1075 // result and must return some Go type that accurately represents the value. 1076 // This is implemented by the AsInterface method of driver.Decoder. 1077 // Since it's fine for different drivers to return different types in this case, 1078 // each test case compares against a list of possible values. 1079 func testBlindDecode(t *testing.T, ct CodecTester) { 1080 if ct == nil { 1081 t.Skip("no CodecTester") 1082 } 1083 t.Run("DocstoreEncode", func(t *testing.T) { testBlindDecode1(t, ct.DocstoreEncode, ct.DocstoreDecode) }) 1084 t.Run("NativeEncode", func(t *testing.T) { testBlindDecode1(t, ct.NativeEncode, ct.DocstoreDecode) }) 1085 } 1086 1087 func testBlindDecode1(t *testing.T, encode func(interface{}) (interface{}, error), decode func(_, _ interface{}) error) { 1088 // Encode and decode expect a document, so use this struct to hold the values. 1089 type S struct{ X interface{} } 1090 1091 for _, test := range []struct { 1092 in interface{} // the value to be encoded 1093 want interface{} // one possibility 1094 want2 interface{} // a second possibility 1095 }{ 1096 {in: nil, want: nil}, 1097 {in: true, want: true}, 1098 {in: "foo", want: "foo"}, 1099 {in: 'c', want: 'c', want2: int64('c')}, 1100 {in: int(3), want: int32(3), want2: int64(3)}, 1101 {in: int8(3), want: int32(3), want2: int64(3)}, 1102 {in: int(-3), want: int32(-3), want2: int64(-3)}, 1103 {in: int64(math.MaxInt32 + 1), want: int64(math.MaxInt32 + 1)}, 1104 {in: float32(1.5), want: float64(1.5)}, 1105 {in: float64(1.5), want: float64(1.5)}, 1106 {in: []byte{1, 2}, want: []byte{1, 2}}, 1107 {in: []int{1, 2}, 1108 want: []interface{}{int32(1), int32(2)}, 1109 want2: []interface{}{int64(1), int64(2)}}, 1110 {in: []float32{1.5, 2.5}, want: []interface{}{float64(1.5), float64(2.5)}}, 1111 {in: []float64{1.5, 2.5}, want: []interface{}{float64(1.5), float64(2.5)}}, 1112 {in: milliTime, want: milliTime, want2: "2019-03-27T00:00:00.005Z"}, 1113 {in: []time.Time{milliTime}, 1114 want: []interface{}{milliTime}, 1115 want2: []interface{}{"2019-03-27T00:00:00.005Z"}, 1116 }, 1117 {in: map[string]int{"a": 1}, 1118 want: map[string]interface{}{"a": int64(1)}, 1119 want2: map[string]interface{}{"a": int32(1)}, 1120 }, 1121 {in: map[string][]byte{"a": {1, 2}}, want: map[string]interface{}{"a": []byte{1, 2}}}, 1122 } { 1123 enc, err := encode(&S{test.in}) 1124 if err != nil { 1125 t.Fatalf("encoding %T: %v", test.in, err) 1126 } 1127 var got S 1128 if err := decode(enc, &got); err != nil { 1129 t.Fatalf("decoding %T: %v", test.in, err) 1130 } 1131 matched := false 1132 wants := []interface{}{test.want} 1133 if test.want2 != nil { 1134 wants = append(wants, test.want2) 1135 } 1136 for _, w := range wants { 1137 if cmp.Equal(got.X, w) { 1138 matched = true 1139 break 1140 } 1141 } 1142 if !matched { 1143 t.Errorf("%T: got %#v (%T), not equal to %#v or %#v", test.in, got.X, got.X, test.want, test.want2) 1144 } 1145 } 1146 } 1147 1148 // A round trip with the docstore codec should work for all docstore-supported types, 1149 // regardless of native driver support. 1150 type docstoreRoundTrip struct { 1151 N *int 1152 I int 1153 U uint 1154 F float64 1155 St string 1156 B bool 1157 By []byte 1158 L []int 1159 A [2]int 1160 A2 [2]int8 1161 At ByteArray 1162 Uu uuid.UUID 1163 M map[string]bool 1164 P *string 1165 T time.Time 1166 } 1167 1168 // TODO(jba): add more fields: structs; embedding. 1169 1170 // All native codecs should support these types. If one doesn't, remove it from this 1171 // struct and make a new single-field struct for it. 1172 type nativeMinimal struct { 1173 N *int 1174 I int 1175 F float64 1176 St string 1177 B bool 1178 By []byte 1179 L []int 1180 A [2]int 1181 A2 [2]int8 1182 At ByteArray 1183 M map[string]bool 1184 P *string 1185 T time.Time 1186 LF []float64 1187 LS []string 1188 } 1189 1190 // testProto tests encoding/decoding of a document with protocol buffer 1191 // and pointer-to-protocol-buffer fields. 1192 func testProto(t *testing.T, _ Harness, coll *ds.Collection) { 1193 ctx := context.Background() 1194 type protoStruct struct { 1195 Name string `docstore:"name"` 1196 Proto tspb.Timestamp 1197 PtrToProto *tspb.Timestamp 1198 DocstoreRevision interface{} 1199 } 1200 doc := &protoStruct{ 1201 Name: "testing", 1202 Proto: tspb.Timestamp{Seconds: 42}, 1203 PtrToProto: &tspb.Timestamp{Seconds: 43}, 1204 } 1205 1206 err := coll.Create(ctx, doc) 1207 if err != nil { 1208 t.Fatal(err) 1209 } 1210 got := &protoStruct{} 1211 err = coll.Query().Get(ctx).Next(ctx, got) 1212 if err != nil { 1213 t.Fatal(err) 1214 } 1215 if diff := cmp.Diff(got, doc, cmpopts.IgnoreUnexported(tspb.Timestamp{})); diff != "" { 1216 t.Error(diff) 1217 } 1218 } 1219 1220 // The following is the schema for the collection where the ID is composed from 1221 // multiple fields instead of one. It can be used for query testing. 1222 // It is loosely borrowed from the DynamoDB documentation. 1223 // It is rich enough to require indexes for some drivers. 1224 1225 // A HighScore records one user's high score in a particular game. 1226 // The primary key fields are Game and Player. 1227 type HighScore struct { 1228 Game string 1229 Player string 1230 Score int 1231 Time time.Time 1232 DocstoreRevision interface{} 1233 } 1234 1235 func newHighScore() interface{} { return &HighScore{} } 1236 1237 // HighScoreKey constructs a single primary key from a HighScore struct or a map 1238 // with the same fields by concatenating the Game and Player fields. 1239 func HighScoreKey(doc docstore.Document) interface{} { 1240 switch d := doc.(type) { 1241 case *HighScore: 1242 return d.key() 1243 case map[string]interface{}: 1244 return barConcat(d["Game"], d["Player"]) 1245 default: 1246 panic("bad arg") 1247 } 1248 } 1249 1250 func (h *HighScore) key() string { 1251 if h.Game == "" || h.Player == "" { 1252 return "" 1253 } 1254 return barConcat(h.Game, h.Player) 1255 } 1256 1257 func barConcat(a, b interface{}) string { return fmt.Sprintf("%v|%v", a, b) } 1258 1259 func highScoreLess(h1, h2 *HighScore) bool { return h1.key() < h2.key() } 1260 1261 func (h *HighScore) String() string { 1262 return fmt.Sprintf("%s=%d@%s", h.key(), h.Score, h.Time.Format("01/02")) 1263 } 1264 1265 func date(month, day int) time.Time { 1266 return time.Date(2019, time.Month(month), day, 0, 0, 0, 0, time.UTC) 1267 } 1268 1269 const ( 1270 game1 = "Praise All Monsters" 1271 game2 = "Zombie DMV" 1272 game3 = "Days Gone" 1273 ) 1274 1275 var highScores = []*HighScore{ 1276 {game1, "pat", 49, date(3, 13), nil}, 1277 {game1, "mel", 60, date(4, 10), nil}, 1278 {game1, "andy", 81, date(2, 1), nil}, 1279 {game1, "fran", 33, date(3, 19), nil}, 1280 {game2, "pat", 120, date(4, 1), nil}, 1281 {game2, "billie", 111, date(4, 10), nil}, 1282 {game2, "mel", 190, date(4, 18), nil}, 1283 {game2, "fran", 33, date(3, 20), nil}, 1284 } 1285 1286 func addHighScores(t *testing.T, coll *ds.Collection) { 1287 alist := coll.Actions() 1288 for _, doc := range highScores { 1289 d := *doc 1290 alist.Put(&d) 1291 } 1292 if err := alist.Do(context.Background()); err != nil { 1293 t.Fatalf("%+v", err) 1294 } 1295 } 1296 1297 func testGetQueryKeyField(t *testing.T, coll *ds.Collection, revField string) { 1298 // Query the key field of a collection that has one. 1299 // (The collection used for testGetQuery uses a key function rather than a key field.) 1300 ctx := context.Background() 1301 docs := []docmap{ 1302 {KeyField: "qkf1", "a": "one", revField: nil}, 1303 {KeyField: "qkf2", "a": "two", revField: nil}, 1304 {KeyField: "qkf3", "a": "three", revField: nil}, 1305 } 1306 al := coll.Actions() 1307 for _, d := range docs { 1308 al.Put(d) 1309 } 1310 if err := al.Do(ctx); err != nil { 1311 t.Fatal(err) 1312 } 1313 iter := coll.Query().Where(KeyField, "<", "qkf3").Get(ctx) 1314 defer iter.Stop() 1315 got := mustCollect(ctx, t, iter) 1316 want := docs[:2] 1317 diff := cmpDiff(got, want, cmpopts.SortSlices(sortByKeyField)) 1318 if diff != "" { 1319 t.Error(diff) 1320 } 1321 1322 // Test that queries with selected fields always return the key. 1323 iter = coll.Query().Get(ctx, "a", ds.FieldPath(revField)) 1324 defer iter.Stop() 1325 got = mustCollect(ctx, t, iter) 1326 for _, d := range docs { 1327 checkHasRevisionField(t, d, revField) 1328 } 1329 diff = cmpDiff(got, docs, cmpopts.SortSlices(sortByKeyField)) 1330 if diff != "" { 1331 t.Error(diff) 1332 } 1333 } 1334 1335 func sortByKeyField(d1, d2 docmap) bool { return d1[KeyField].(string) < d2[KeyField].(string) } 1336 1337 // TODO(shantuo): consider add this test to all action tests, like the AltRev 1338 // ones. 1339 func testActionsWithCompositeID(t *testing.T, _ Harness, coll *ds.Collection) { 1340 ctx := context.Background() 1341 // Create cannot generate an ID for the document when using IDFunc. 1342 checkCode(t, coll.Create(ctx, &HighScore{}), gcerrors.InvalidArgument) 1343 checkCode(t, coll.Get(ctx, &HighScore{}), gcerrors.InvalidArgument) 1344 1345 // Put 1346 addHighScores(t, coll) 1347 // Get 1348 gots := make([]*HighScore, len(highScores)) 1349 actions := coll.Actions() 1350 for i, doc := range highScores { 1351 gots[i] = &HighScore{Game: doc.Game, Player: doc.Player} 1352 actions.Get(gots[i]) 1353 } 1354 if err := actions.Do(ctx); err != nil { 1355 t.Fatal(err) 1356 } 1357 for i, got := range gots { 1358 if got.DocstoreRevision == nil { 1359 t.Errorf("%v missing DocstoreRevision", got) 1360 } else { 1361 got.DocstoreRevision = nil 1362 } 1363 if diff := cmp.Diff(got, highScores[i]); diff != "" { 1364 t.Error(diff) 1365 } 1366 } 1367 } 1368 1369 func testGetQuery(t *testing.T, _ Harness, coll *ds.Collection) { 1370 ctx := context.Background() 1371 addHighScores(t, coll) 1372 1373 // Query filters should have the same behavior when doing string and number 1374 // comparison. 1375 tests := []struct { 1376 name string 1377 q *ds.Query 1378 fields []docstore.FieldPath // fields to get 1379 want func(*HighScore) bool // filters highScores 1380 before func(x, y *HighScore) bool // if present, checks result order 1381 }{ 1382 { 1383 name: "All", 1384 q: coll.Query(), 1385 want: func(*HighScore) bool { return true }, 1386 }, 1387 { 1388 name: "Game", 1389 q: coll.Query().Where("Game", "=", game2), 1390 want: func(h *HighScore) bool { return h.Game == game2 }, 1391 }, 1392 { 1393 name: "Score", 1394 q: coll.Query().Where("Score", ">", 100), 1395 want: func(h *HighScore) bool { return h.Score > 100 }, 1396 }, 1397 { 1398 name: "Player", 1399 q: coll.Query().Where("Player", "=", "billie"), 1400 want: func(h *HighScore) bool { return h.Player == "billie" }, 1401 }, 1402 { 1403 name: "GamePlayer", 1404 q: coll.Query().Where("Player", "=", "andy").Where("Game", "=", game1), 1405 want: func(h *HighScore) bool { return h.Player == "andy" && h.Game == game1 }, 1406 }, 1407 { 1408 name: "PlayerScore", 1409 q: coll.Query().Where("Player", "=", "pat").Where("Score", "<", 100), 1410 want: func(h *HighScore) bool { return h.Player == "pat" && h.Score < 100 }, 1411 }, 1412 { 1413 name: "GameScore", 1414 q: coll.Query().Where("Game", "=", game1).Where("Score", ">=", 50), 1415 want: func(h *HighScore) bool { return h.Game == game1 && h.Score >= 50 }, 1416 }, 1417 { 1418 name: "PlayerTime", 1419 q: coll.Query().Where("Player", "=", "mel").Where("Time", ">", date(4, 1)), 1420 want: func(h *HighScore) bool { return h.Player == "mel" && h.Time.After(date(4, 1)) }, 1421 }, 1422 { 1423 name: "ScoreTime", 1424 q: coll.Query().Where("Score", ">=", 50).Where("Time", ">", date(4, 1)), 1425 want: func(h *HighScore) bool { return h.Score >= 50 && h.Time.After(date(4, 1)) }, 1426 }, 1427 { 1428 name: "AllByPlayerAsc", 1429 q: coll.Query().OrderBy("Player", docstore.Ascending), 1430 want: func(h *HighScore) bool { return true }, 1431 before: func(h1, h2 *HighScore) bool { return h1.Player < h2.Player }, 1432 }, 1433 { 1434 name: "AllByPlayerDesc", 1435 q: coll.Query().OrderBy("Player", docstore.Descending), 1436 want: func(h *HighScore) bool { return true }, 1437 before: func(h1, h2 *HighScore) bool { return h1.Player > h2.Player }, 1438 }, 1439 { 1440 name: "GameByPlayerAsc", 1441 // We need a filter on Player, and it can't be the empty string (DynamoDB limitation). 1442 // So pick any string that sorts less than all valid player names. 1443 q: coll.Query().Where("Game", "=", game1).Where("Player", ">", "."). 1444 OrderBy("Player", docstore.Ascending), 1445 want: func(h *HighScore) bool { return h.Game == game1 }, 1446 before: func(h1, h2 *HighScore) bool { return h1.Player < h2.Player }, 1447 }, 1448 { 1449 // Same as above, but descending. 1450 name: "GameByPlayerDesc", 1451 q: coll.Query().Where("Game", "=", game1).Where("Player", ">", "."). 1452 OrderBy("Player", docstore.Descending), 1453 want: func(h *HighScore) bool { return h.Game == game1 }, 1454 before: func(h1, h2 *HighScore) bool { return h1.Player > h2.Player }, 1455 }, 1456 // TODO(jba): add more OrderBy tests. 1457 { 1458 name: "AllWithKeyFields", 1459 q: coll.Query(), 1460 fields: []docstore.FieldPath{"Game", "Player", ds.FieldPath(ds.DefaultRevisionField)}, 1461 want: func(h *HighScore) bool { 1462 h.Score = 0 1463 h.Time = time.Time{} 1464 return true 1465 }, 1466 }, 1467 { 1468 name: "AllWithScore", 1469 q: coll.Query(), 1470 fields: []docstore.FieldPath{"Game", "Player", "Score", ds.FieldPath(ds.DefaultRevisionField)}, 1471 want: func(h *HighScore) bool { 1472 h.Time = time.Time{} 1473 return true 1474 }, 1475 }, 1476 } 1477 for _, tc := range tests { 1478 t.Run(tc.name, func(t *testing.T) { 1479 got, err := collectHighScores(ctx, tc.q.Get(ctx, tc.fields...)) 1480 if err != nil { 1481 t.Fatal(err) 1482 } 1483 for _, g := range got { 1484 if g.DocstoreRevision == nil { 1485 t.Errorf("%v missing DocstoreRevision", g) 1486 } else { 1487 g.DocstoreRevision = nil 1488 } 1489 } 1490 want := filterHighScores(highScores, tc.want) 1491 _, err = tc.q.Plan() 1492 if err != nil { 1493 t.Fatal(err) 1494 } 1495 diff := cmp.Diff(got, want, cmpopts.SortSlices(highScoreLess)) 1496 if diff != "" { 1497 t.Fatal(diff) 1498 } 1499 if tc.before != nil { 1500 // Verify that the results are sorted according to tc.less. 1501 for i := 1; i < len(got); i++ { 1502 if tc.before(got[i], got[i-1]) { 1503 t.Errorf("%s at %d sorts before previous %s", got[i], i, got[i-1]) 1504 } 1505 } 1506 } 1507 // We can't assume anything about the query plan. Just verify that Plan returns 1508 // successfully. 1509 if _, err := tc.q.Plan(KeyField); err != nil { 1510 t.Fatal(err) 1511 } 1512 }) 1513 } 1514 t.Run("Limit", func(t *testing.T) { 1515 // For limit, we can't be sure which documents will be returned, only their count. 1516 limitQ := coll.Query().Limit(2) 1517 got := mustCollectHighScores(ctx, t, limitQ.Get(ctx)) 1518 if len(got) != 2 { 1519 t.Errorf("got %v, wanted two documents", got) 1520 } 1521 }) 1522 } 1523 1524 func filterHighScores(hs []*HighScore, f func(*HighScore) bool) []*HighScore { 1525 var res []*HighScore 1526 for _, h := range hs { 1527 c := *h // Copy in case f modifies its argument. 1528 if f(&c) { 1529 res = append(res, &c) 1530 } 1531 } 1532 return res 1533 } 1534 1535 // clearCollection delete all documents from this collection after test. 1536 func clearCollection(fataler interface{ Fatalf(string, ...interface{}) }, coll *docstore.Collection) { 1537 ctx := context.Background() 1538 iter := coll.Query().Get(ctx) 1539 dels := coll.Actions() 1540 for { 1541 doc := map[string]interface{}{} 1542 err := iter.Next(ctx, doc) 1543 if err == io.EOF { 1544 break 1545 } 1546 if err != nil { 1547 fataler.Fatalf("%+v", err) 1548 } 1549 dels.Delete(doc) 1550 } 1551 if err := dels.Do(ctx); err != nil { 1552 fataler.Fatalf("%+v", err) 1553 } 1554 } 1555 1556 func forEach(ctx context.Context, iter *ds.DocumentIterator, create func() interface{}, handle func(interface{}) error) error { 1557 for { 1558 doc := create() 1559 err := iter.Next(ctx, doc) 1560 if err == io.EOF { 1561 break 1562 } 1563 if err != nil { 1564 return err 1565 } 1566 if err := handle(doc); err != nil { 1567 return err 1568 } 1569 } 1570 return nil 1571 } 1572 1573 func mustCollect(ctx context.Context, t *testing.T, iter *ds.DocumentIterator) []docmap { 1574 var ms []docmap 1575 newDocmap := func() interface{} { return docmap{} } 1576 collect := func(m interface{}) error { ms = append(ms, m.(docmap)); return nil } 1577 if err := forEach(ctx, iter, newDocmap, collect); err != nil { 1578 t.Fatal(err) 1579 } 1580 return ms 1581 } 1582 1583 func mustCollectHighScores(ctx context.Context, t *testing.T, iter *ds.DocumentIterator) []*HighScore { 1584 hs, err := collectHighScores(ctx, iter) 1585 if err != nil { 1586 t.Fatal(err) 1587 } 1588 return hs 1589 } 1590 1591 func collectHighScores(ctx context.Context, iter *ds.DocumentIterator) ([]*HighScore, error) { 1592 var hs []*HighScore 1593 collect := func(h interface{}) error { hs = append(hs, h.(*HighScore)); return nil } 1594 if err := forEach(ctx, iter, newHighScore, collect); err != nil { 1595 return nil, err 1596 } 1597 return hs, nil 1598 } 1599 1600 func testMultipleActions(t *testing.T, coll *ds.Collection, revField string) { 1601 ctx := context.Background() 1602 1603 must := func(err error) { 1604 t.Helper() 1605 if err != nil { 1606 t.Fatal(err) 1607 } 1608 } 1609 1610 var docs []docmap 1611 for i := 0; i < 9; i++ { 1612 docs = append(docs, docmap{ 1613 KeyField: fmt.Sprintf("testUnorderedActions%d", i), 1614 "s": fmt.Sprint(i), 1615 revField: nil, 1616 }) 1617 } 1618 1619 compare := func(gots, wants []docmap) { 1620 t.Helper() 1621 for i := 0; i < len(gots); i++ { 1622 got := gots[i] 1623 want := clone(wants[i]) 1624 want[revField] = got[revField] 1625 if !cmp.Equal(got, want, cmpopts.IgnoreUnexported(tspb.Timestamp{})) { 1626 t.Errorf("index #%d:\ngot %v\nwant %v", i, got, want) 1627 } 1628 } 1629 } 1630 1631 // Put the first three docs. 1632 actions := coll.Actions() 1633 for i := 0; i < 6; i++ { 1634 actions.Create(docs[i]) 1635 } 1636 must(actions.Do(ctx)) 1637 1638 // Replace the first three and put six more. 1639 actions = coll.Actions() 1640 for i := 0; i < 3; i++ { 1641 docs[i]["s"] = fmt.Sprintf("%d'", i) 1642 actions.Replace(docs[i]) 1643 } 1644 for i := 3; i < 9; i++ { 1645 actions.Put(docs[i]) 1646 } 1647 must(actions.Do(ctx)) 1648 1649 // Delete the first three, get the second three, and put three more. 1650 gdocs := []docmap{ 1651 {KeyField: docs[3][KeyField]}, 1652 {KeyField: docs[4][KeyField]}, 1653 {KeyField: docs[5][KeyField]}, 1654 } 1655 actions = coll.Actions() 1656 actions.Update(docs[6], ds.Mods{"s": "6'", "n": ds.Increment(1)}) 1657 actions.Get(gdocs[0]) 1658 actions.Delete(docs[0]) 1659 actions.Delete(docs[1]) 1660 actions.Update(docs[7], ds.Mods{"s": "7'"}) 1661 actions.Get(gdocs[1]) 1662 actions.Delete(docs[2]) 1663 actions.Get(gdocs[2]) 1664 actions.Update(docs[8], ds.Mods{"n": ds.Increment(-1)}) 1665 must(actions.Do(ctx)) 1666 compare(gdocs, docs[3:6]) 1667 1668 // At this point, the existing documents are 3 - 9. 1669 1670 // Get the first four, try to create one that already exists, delete a 1671 // nonexistent doc, and put one. Only the Get of #3, the Delete and the Put 1672 // should succeed. 1673 actions = coll.Actions() 1674 for _, doc := range []docmap{ 1675 {KeyField: docs[0][KeyField]}, 1676 {KeyField: docs[1][KeyField]}, 1677 {KeyField: docs[2][KeyField]}, 1678 {KeyField: docs[3][KeyField]}, 1679 } { 1680 actions.Get(doc) 1681 } 1682 docs[4][revField] = nil 1683 actions.Create(docs[4]) // create existing doc 1684 actions.Put(docs[5]) 1685 // TODO(jba): Understand why the following line is necessary for dynamo but not the others. 1686 docs[0][revField] = nil 1687 actions.Delete(docs[0]) // delete nonexistent doc 1688 err := actions.Do(ctx) 1689 if err == nil { 1690 t.Fatal("want error, got nil") 1691 } 1692 alerr, ok := err.(docstore.ActionListError) 1693 if !ok { 1694 t.Fatalf("got %v (%T), want ActionListError", alerr, alerr) 1695 } 1696 for _, e := range alerr { 1697 switch i := e.Index; i { 1698 case 3, 5, 6: 1699 t.Errorf("index %d: got %v, want nil", i, e.Err) 1700 1701 case 4, -1: // -1 for mongodb issue, see https://jira.mongodb.org/browse/GODRIVER-1028 1702 if ec := gcerrors.Code(e.Err); ec != gcerrors.AlreadyExists && 1703 ec != gcerrors.FailedPrecondition { // TODO(shantuo): distinguish this case for dyanmo 1704 t.Errorf("index 4: create an existing document: got %v, want error", e.Err) 1705 } 1706 1707 default: 1708 if gcerrors.Code(e.Err) != gcerrors.NotFound { 1709 t.Errorf("index %d: got %v, want NotFound", i, e.Err) 1710 } 1711 } 1712 } 1713 } 1714 1715 func testActionsOnStructNoRev(t *testing.T, _ Harness, coll *ds.Collection) { 1716 type item struct { 1717 Name string `docstore:"name"` 1718 I int 1719 } 1720 doc1 := item{Name: "createandreplace"} 1721 doc2 := item{Name: "putandupdate"} 1722 ctx := context.Background() 1723 1724 got1 := item{Name: doc1.Name} 1725 got2 := map[string]interface{}{"name": doc2.Name} 1726 if err := coll.Actions(). 1727 Create(&doc1).Put(&doc2). 1728 Get(&got1).Get(got2). 1729 Do(ctx); err != nil { 1730 t.Fatal(err) 1731 } 1732 checkNoRevisionField(t, got2, ds.DefaultRevisionField) 1733 1734 got3 := map[string]interface{}{"name": doc1.Name} 1735 got4 := item{Name: doc2.Name} 1736 if err := coll.Actions(). 1737 Replace(&doc1).Update(&item{Name: doc2.Name}, ds.Mods{"I": 1}). 1738 Get(got3, "I").Get(&got4, "I"). 1739 Do(ctx); err != nil { 1740 t.Fatal(err) 1741 } 1742 checkNoRevisionField(t, got3, ds.DefaultRevisionField) 1743 } 1744 1745 func testExampleInDoc(t *testing.T, _ Harness, coll *ds.Collection) { 1746 type Name struct { 1747 First, Last string 1748 } 1749 type Book struct { 1750 Title string `docstore:"name"` 1751 Author Name `docstore:"author"` 1752 PublicationYears []int `docstore:"pub_years,omitempty"` 1753 NumPublications int `docstore:"-"` 1754 } 1755 1756 must := func(err error) { 1757 t.Helper() 1758 if err != nil { 1759 t.Fatal(err) 1760 } 1761 } 1762 checkFieldEqual := func(got, want interface{}, field string) { 1763 t.Helper() 1764 fvg, err := MustDocument(got).GetField(field) 1765 must(err) 1766 fvw, err := MustDocument(want).GetField(field) 1767 must(err) 1768 if !cmp.Equal(fvg, fvw) { 1769 t.Errorf("%s: got %v want %v", field, fvg, fvw) 1770 } 1771 } 1772 1773 doc1 := &Book{ 1774 Title: "The Master and Margarita", 1775 Author: Name{ 1776 First: "Mikhail", 1777 Last: "Bulgakov", 1778 }, 1779 PublicationYears: []int{1967, 1973}, 1780 NumPublications: 2, 1781 } 1782 1783 doc2 := map[string]interface{}{ 1784 KeyField: "The Heart of a Dog", 1785 "author": map[string]interface{}{ 1786 "First": "Mikhail", 1787 "Last": "Bulgakov", 1788 }, 1789 "pub_years": []int{1968, 1987}, 1790 } 1791 1792 ctx := context.Background() 1793 must(coll.Actions().Create(doc1).Put(doc2).Do(ctx)) 1794 got1 := &Book{Title: doc1.Title} 1795 got2 := &Book{Title: doc2[KeyField].(string)} 1796 must(coll.Actions().Get(got1).Get(got2).Do(ctx)) 1797 1798 if got1.NumPublications != 0 { 1799 t.Errorf("docstore:\"-\" tagged field isn't ignored") 1800 } 1801 checkFieldEqual(got1, doc1, "author") 1802 checkFieldEqual(got2, doc2, "pub_years") 1803 1804 gots := mustCollect(ctx, t, coll.Query().Where("author.Last", "=", "Bulgakov").Get(ctx)) 1805 if len(gots) != 2 { 1806 t.Errorf("got %v want all two results", gots) 1807 } 1808 must(coll.Actions().Delete(doc1).Delete(doc2).Do(ctx)) 1809 } 1810 1811 // Verify that BeforeDo is invoked, and its as function behaves as expected. 1812 func testBeforeDo(t *testing.T, newHarness HarnessMaker) { 1813 ctx := context.Background() 1814 withCollection(t, newHarness, SingleKey, func(t *testing.T, h Harness, coll *ds.Collection) { 1815 var called bool 1816 beforeDo := func(asFunc func(interface{}) bool) error { 1817 called = true 1818 if asFunc(nil) { 1819 return errors.New("asFunc returned true when called with nil, want false") 1820 } 1821 // At least one of the expected types must return true. Special case: if 1822 // there are no types, then the as function never returns true, so skip the 1823 // check. 1824 if len(h.BeforeDoTypes()) > 0 { 1825 found := false 1826 for _, b := range h.BeforeDoTypes() { 1827 v := reflect.New(reflect.TypeOf(b)).Interface() 1828 if asFunc(v) { 1829 found = true 1830 break 1831 } 1832 } 1833 if !found { 1834 return errors.New("none of the BeforeDoTypes works with the as function") 1835 } 1836 } 1837 return nil 1838 } 1839 1840 check := func(f func(*ds.ActionList)) { 1841 t.Helper() 1842 // First, verify that if a BeforeDo function returns an error, so does ActionList.Do. 1843 // We depend on that for the rest of the test. 1844 al := coll.Actions().BeforeDo(func(func(interface{}) bool) error { return errors.New("") }) 1845 f(al) 1846 if err := al.Do(ctx); err == nil { 1847 t.Error("beforeDo returning error: got nil from Do, want error") 1848 return 1849 } 1850 called = false 1851 al = coll.Actions().BeforeDo(beforeDo) 1852 f(al) 1853 if err := al.Do(ctx); err != nil { 1854 t.Error(err) 1855 return 1856 } 1857 if !called { 1858 t.Error("BeforeDo function never called") 1859 } 1860 } 1861 1862 doc := docmap{KeyField: "testBeforeDo"} 1863 check(func(l *docstore.ActionList) { l.Create(doc) }) 1864 check(func(l *docstore.ActionList) { l.Replace(doc) }) 1865 check(func(l *docstore.ActionList) { l.Put(doc) }) 1866 check(func(l *docstore.ActionList) { l.Update(doc, docstore.Mods{"a": 1}) }) 1867 check(func(l *docstore.ActionList) { l.Get(doc) }) 1868 check(func(l *docstore.ActionList) { l.Delete(doc) }) 1869 }) 1870 } 1871 1872 // Verify that BeforeQuery is invoked, and its as function behaves as expected. 1873 func testBeforeQuery(t *testing.T, newHarness HarnessMaker) { 1874 ctx := context.Background() 1875 withCollection(t, newHarness, SingleKey, func(t *testing.T, h Harness, coll *ds.Collection) { 1876 var called bool 1877 beforeQuery := func(asFunc func(interface{}) bool) error { 1878 called = true 1879 if asFunc(nil) { 1880 return errors.New("asFunc returned true when called with nil, want false") 1881 } 1882 // At least one of the expected types must return true. Special case: if 1883 // there are no types, then the as function never returns true, so skip the 1884 // check. 1885 if len(h.BeforeQueryTypes()) > 0 { 1886 found := false 1887 for _, b := range h.BeforeQueryTypes() { 1888 v := reflect.New(reflect.TypeOf(b)).Interface() 1889 if asFunc(v) { 1890 found = true 1891 break 1892 } 1893 } 1894 if !found { 1895 return errors.New("none of the BeforeQueryTypes works with the as function") 1896 } 1897 } 1898 return nil 1899 } 1900 1901 iter := coll.Query().BeforeQuery(beforeQuery).Get(ctx) 1902 if err := iter.Next(ctx, docmap{}); err != io.EOF { 1903 t.Fatalf("got %v, wanted io.EOF", err) 1904 } 1905 if !called { 1906 t.Error("BeforeQuery function never called for Get") 1907 } 1908 }) 1909 } 1910 1911 func testAs(t *testing.T, coll *ds.Collection, st AsTest) { 1912 // Verify Collection.As 1913 if err := st.CollectionCheck(coll); err != nil { 1914 t.Error(err) 1915 } 1916 1917 ctx := context.Background() 1918 1919 // Query 1920 qs := []*docstore.Query{ 1921 coll.Query().Where("Game", "=", game3), 1922 // Note: don't use filter on Player, the test table has Player as the 1923 // partition key of a Global Secondary Index, which doesn't support 1924 // ConsistentRead mode, which is what the As test does in its BeforeQuery 1925 // function. 1926 coll.Query().Where("Score", ">", 50), 1927 } 1928 for _, q := range qs { 1929 iter := q.Get(ctx) 1930 if err := st.QueryCheck(iter); err != nil { 1931 t.Error(err) 1932 } 1933 } 1934 1935 // ErrorCheck 1936 doc := &HighScore{game3, "steph", 24, date(4, 25), nil} 1937 if err := coll.Create(ctx, doc); err != nil { 1938 t.Fatal(err) 1939 } 1940 doc.DocstoreRevision = nil 1941 if err := coll.Create(ctx, doc); err == nil { 1942 t.Fatal("got nil error from creating an existing item, want an error") 1943 } else { 1944 if alerr, ok := err.(docstore.ActionListError); ok { 1945 for _, aerr := range alerr { 1946 if checkerr := st.ErrorCheck(coll, aerr.Err); checkerr != nil { 1947 t.Error(checkerr) 1948 } 1949 } 1950 } else if checkerr := st.ErrorCheck(coll, err); checkerr != nil { 1951 t.Error(checkerr) 1952 } 1953 } 1954 } 1955 1956 func clone(m docmap) docmap { 1957 r := docmap{} 1958 for k, v := range m { 1959 r[k] = v 1960 } 1961 return r 1962 } 1963 1964 func cmpDiff(a, b interface{}, opts ...cmp.Option) string { 1965 // Firestore revisions can be protos. 1966 return cmp.Diff(a, b, append([]cmp.Option{cmp.Comparer(proto.Equal)}, opts...)...) 1967 } 1968 1969 func checkCode(t *testing.T, err error, code gcerrors.ErrorCode) { 1970 t.Helper() 1971 if gcerrors.Code(err) != code { 1972 t.Errorf("got %v, want %s", err, code) 1973 } 1974 }