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  }