github.com/thiagoyeds/go-cloud@v0.26.0/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 "gocloud.dev/docstore" 29 "gocloud.dev/docstore/driver" 30 "gocloud.dev/docstore/drivertest" 31 "gocloud.dev/gcerrors" 32 "gocloud.dev/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 }