github.com/cornelk/go-cloud@v0.17.1/docstore/awsdynamodb/dynamo_test.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 awsdynamodb
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"testing"
    23  
    24  	"github.com/aws/aws-sdk-go/aws"
    25  	"github.com/aws/aws-sdk-go/aws/awserr"
    26  	"github.com/aws/aws-sdk-go/aws/session"
    27  	dyn "github.com/aws/aws-sdk-go/service/dynamodb"
    28  	"github.com/cornelk/go-cloud/docstore"
    29  	"github.com/cornelk/go-cloud/docstore/driver"
    30  	"github.com/cornelk/go-cloud/docstore/drivertest"
    31  	"github.com/cornelk/go-cloud/gcerrors"
    32  	"github.com/cornelk/go-cloud/internal/testing/setup"
    33  )
    34  
    35  // To create the tables and indexes needed for these tests, run create_tables.sh in
    36  // this directory.
    37  //
    38  // The docstore-test-2 table is set up to work with queries on the drivertest.HighScore
    39  // struct like so:
    40  //   table:        "Game" partition key, "Player" sort key
    41  //   local index:  "Game" partition key, "Score" sort key
    42  //   global index: "Player" partition key, "Time" sort key
    43  // The conformance test queries should exercise all of these.
    44  //
    45  // The docstore-test-3 table is used for running benchmarks only. To eliminate
    46  // the effect of dynamo auto-scaling, run:
    47  // aws dynamodb update-table --table-name docstore-test-3 \
    48  //   --provisioned-throughput ReadCapacityUnits=1000,WriteCapacityUnits=1000
    49  // Don't forget to change it back when done benchmarking.
    50  
    51  const (
    52  	region          = "us-east-2"
    53  	collectionName1 = "docstore-test-1"
    54  	collectionName2 = "docstore-test-2"
    55  	collectionName3 = "docstore-test-3" // for benchmark
    56  )
    57  
    58  type harness struct {
    59  	sess   *session.Session
    60  	closer func()
    61  }
    62  
    63  func newHarness(ctx context.Context, t *testing.T) (drivertest.Harness, error) {
    64  	sess, _, done, state := setup.NewAWSSession(ctx, t, region)
    65  	drivertest.MakeUniqueStringDeterministicForTesting(state)
    66  	return &harness{sess: sess, closer: done}, nil
    67  }
    68  
    69  func (*harness) BeforeDoTypes() []interface{} {
    70  	return []interface{}{&dyn.BatchGetItemInput{}, &dyn.TransactWriteItemsInput{},
    71  		&dyn.PutItemInput{}, &dyn.DeleteItemInput{}, &dyn.UpdateItemInput{}}
    72  }
    73  
    74  func (*harness) BeforeQueryTypes() []interface{} {
    75  	return []interface{}{&dyn.QueryInput{}, &dyn.ScanInput{}}
    76  }
    77  
    78  func (*harness) RevisionsEqual(rev1, rev2 interface{}) bool {
    79  	return rev1 == rev2
    80  }
    81  
    82  func (h *harness) Close() {
    83  	h.closer()
    84  }
    85  
    86  func (h *harness) MakeCollection(_ context.Context, kind drivertest.CollectionKind) (driver.Collection, error) {
    87  	switch kind {
    88  	case drivertest.SingleKey, drivertest.NoRev:
    89  		return newCollection(dyn.New(h.sess), collectionName1, drivertest.KeyField, "", &Options{
    90  			AllowScans:     true,
    91  			ConsistentRead: true,
    92  		})
    93  	case drivertest.TwoKey:
    94  		// For query test we don't use strong consistency mode since some tests are
    95  		// running on global secondary index and it doesn't support ConsistentRead.
    96  		return newCollection(dyn.New(h.sess), collectionName2, "Game", "Player", &Options{
    97  			AllowScans:       true,
    98  			RunQueryFallback: InMemorySortFallback(func() interface{} { return new(drivertest.HighScore) }),
    99  		})
   100  	case drivertest.AltRev:
   101  		return newCollection(dyn.New(h.sess), collectionName1, drivertest.KeyField, "",
   102  			&Options{
   103  				AllowScans:     true,
   104  				RevisionField:  drivertest.AlternateRevisionField,
   105  				ConsistentRead: true,
   106  			})
   107  	default:
   108  		panic("bad kind")
   109  	}
   110  }
   111  
   112  func collectHighScores(ctx context.Context, iter driver.DocumentIterator) ([]*drivertest.HighScore, error) {
   113  	var hs []*drivertest.HighScore
   114  	for {
   115  		var h drivertest.HighScore
   116  		doc := drivertest.MustDocument(&h)
   117  		err := iter.Next(ctx, doc)
   118  		if err == io.EOF {
   119  			break
   120  		}
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		hs = append(hs, &h)
   125  	}
   126  	return hs, nil
   127  }
   128  
   129  type highScoreSliceIterator struct {
   130  	hs   []*drivertest.HighScore
   131  	next int
   132  }
   133  
   134  func (it *highScoreSliceIterator) Next(ctx context.Context, doc driver.Document) error {
   135  	if it.next >= len(it.hs) {
   136  		return io.EOF
   137  	}
   138  	dest, ok := doc.Origin.(*drivertest.HighScore)
   139  	if !ok {
   140  		return fmt.Errorf("doc is %T, not HighScore", doc.Origin)
   141  	}
   142  	*dest = *it.hs[it.next]
   143  	it.next++
   144  	return nil
   145  }
   146  
   147  func (*highScoreSliceIterator) Stop()               {}
   148  func (*highScoreSliceIterator) As(interface{}) bool { return false }
   149  
   150  type verifyAs struct{}
   151  
   152  func (verifyAs) Name() string {
   153  	return "verify As"
   154  }
   155  
   156  func (verifyAs) CollectionCheck(coll *docstore.Collection) error {
   157  	var db *dyn.DynamoDB
   158  	if !coll.As(&db) {
   159  		return errors.New("Collection.As failed")
   160  	}
   161  	return nil
   162  }
   163  
   164  func (verifyAs) QueryCheck(it *docstore.DocumentIterator) error {
   165  	var so *dyn.ScanOutput
   166  	var qo *dyn.QueryOutput
   167  	if !it.As(&so) && !it.As(&qo) {
   168  		return errors.New("DocumentIterator.As failed")
   169  	}
   170  	return nil
   171  }
   172  
   173  func (v verifyAs) ErrorCheck(k *docstore.Collection, err error) error {
   174  	var e awserr.Error
   175  	if !k.ErrorAs(err, &e) {
   176  		return errors.New("Collection.ErrorAs failed")
   177  	}
   178  	return nil
   179  }
   180  
   181  func TestConformance(t *testing.T) {
   182  	// Note: when running -record repeatedly in a short time period, change the argument
   183  	// in the call below to generate unique transaction tokens.
   184  	drivertest.MakeUniqueStringDeterministicForTesting(1)
   185  	drivertest.RunConformanceTests(t, newHarness, &codecTester{}, []drivertest.AsTest{verifyAs{}})
   186  }
   187  
   188  func BenchmarkConformance(b *testing.B) {
   189  	sess := session.Must(session.NewSession(&aws.Config{
   190  		Region: aws.String(region),
   191  	}))
   192  	coll, err := newCollection(dyn.New(sess), collectionName3, drivertest.KeyField, "", &Options{AllowScans: true})
   193  	if err != nil {
   194  		b.Fatal(err)
   195  	}
   196  	drivertest.RunBenchmarks(b, docstore.NewCollection(coll))
   197  }
   198  
   199  // awsdynamodb-specific tests.
   200  
   201  func TestQueryErrors(t *testing.T) {
   202  	// Verify that bad queries return the right errors.
   203  	ctx := context.Background()
   204  	h, err := newHarness(ctx, t)
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  	defer h.Close()
   209  	dc, err := h.MakeCollection(ctx, drivertest.TwoKey)
   210  	if err != nil {
   211  		t.Fatal(err)
   212  	}
   213  	coll := docstore.NewCollection(dc)
   214  	defer coll.Close()
   215  
   216  	// Here we are comparing a key field with the wrong type. DynamoDB cares about this
   217  	// because even though it's a document store and hence schemaless, the key fields
   218  	// do have a schema (that is, they have known, fixed types).
   219  	iter := coll.Query().Where("Game", "=", 1).Get(ctx)
   220  	defer iter.Stop()
   221  	err = iter.Next(ctx, &h)
   222  	if c := gcerrors.Code(err); c != gcerrors.InvalidArgument {
   223  		t.Errorf("got %v (code %s, type %T), want InvalidArgument", err, c, err)
   224  	}
   225  }