@@ -3,15 +3,13 @@ package verifier
33import (
44 "context"
55 "strings"
6- "testing"
76 "time"
87
98 "github.com/10gen/migration-verifier/internal/testutil"
109 "github.com/10gen/migration-verifier/internal/util"
1110 "github.com/10gen/migration-verifier/mslices"
1211 "github.com/pkg/errors"
1312 "github.com/samber/lo"
14- "github.com/stretchr/testify/assert"
1513 "github.com/stretchr/testify/require"
1614 "go.mongodb.org/mongo-driver/bson"
1715 "go.mongodb.org/mongo-driver/bson/primitive"
@@ -20,18 +18,17 @@ import (
2018 "go.mongodb.org/mongo-driver/mongo/readconcern"
2119)
2220
23- func TestChangeStreamFilter (t * testing.T ) {
24- verifier := Verifier {}
25- verifier .SetMetaDBName ("metadb" )
26- assert .Contains (t ,
27- verifier .GetChangeStreamFilter (),
21+ func (suite * IntegrationTestSuite ) TestChangeStreamFilter () {
22+ verifier := suite .BuildVerifier ()
23+ suite .Assert ().Contains (
24+ verifier .srcChangeStreamReader .GetChangeStreamFilter (),
2825 bson.D {
29- {"$match" , bson.D {{"ns.db" , bson.D {{"$ne" , "metadb" }}}}},
26+ {"$match" , bson.D {{"ns.db" , bson.D {{"$ne" , metaDBName }}}}},
3027 },
3128 )
32- verifier .srcNamespaces = []string {"foo.bar" , "foo.baz" , "test.car" , "test.chaz" }
33- assert . Contains (t ,
34- verifier .GetChangeStreamFilter (),
29+ verifier .srcChangeStreamReader . namespaces = []string {"foo.bar" , "foo.baz" , "test.car" , "test.chaz" }
30+ suite . Assert (). Contains (
31+ verifier .srcChangeStreamReader . GetChangeStreamFilter (),
3532 bson.D {{"$match" , bson.D {
3633 {"$or" , []bson.D {
3734 {{"ns" , bson.D {{"db" , "foo" }, {"coll" , "bar" }}}},
@@ -43,6 +40,18 @@ func TestChangeStreamFilter(t *testing.T) {
4340 )
4441}
4542
43+ func (suite * IntegrationTestSuite ) startSrcChangeStreamReaderAndHandler (ctx context.Context , verifier * Verifier ) {
44+ err := verifier .srcChangeStreamReader .StartChangeStream (ctx )
45+ suite .Require ().NoError (err )
46+ go func () {
47+ err := verifier .StartChangeEventHandler (ctx , verifier .srcChangeStreamReader )
48+ if errors .Is (err , context .Canceled ) {
49+ return
50+ }
51+ suite .Require ().NoError (err )
52+ }()
53+ }
54+
4655// TestChangeStreamResumability creates a verifier, starts its change stream,
4756// terminates that verifier, updates the source cluster, starts a new
4857// verifier with change stream, and confirms that things look as they should.
@@ -57,8 +66,7 @@ func (suite *IntegrationTestSuite) TestChangeStreamResumability() {
5766 verifier1 := suite .BuildVerifier ()
5867 ctx , cancel := context .WithCancel (suite .Context ())
5968 defer cancel ()
60- err := verifier1 .StartChangeStream (ctx )
61- suite .Require ().NoError (err )
69+ suite .startSrcChangeStreamReaderAndHandler (ctx , verifier1 )
6270 }()
6371
6472 ctx , cancel := context .WithCancel (suite .Context ())
@@ -82,13 +90,12 @@ func (suite *IntegrationTestSuite) TestChangeStreamResumability() {
8290
8391 newTime := suite .getClusterTime (ctx , suite .srcMongoClient )
8492
85- err = verifier2 .StartChangeStream (ctx )
86- suite .Require ().NoError (err )
93+ suite .startSrcChangeStreamReaderAndHandler (ctx , verifier2 )
8794
88- suite .Require ().NotNil (verifier2 .srcStartAtTs )
95+ suite .Require ().NotNil (verifier2 .srcChangeStreamReader . startAtTs )
8996
9097 suite .Assert ().False (
91- verifier2 .srcStartAtTs .After (newTime ),
98+ verifier2 .srcChangeStreamReader . startAtTs .After (newTime ),
9299 "verifier2's change stream should be no later than this new session" ,
93100 )
94101
@@ -156,12 +163,11 @@ func (suite *IntegrationTestSuite) TestStartAtTimeNoChanges() {
156163 suite .Require ().NoError (err )
157164 origStartTs := sess .OperationTime ()
158165 suite .Require ().NotNil (origStartTs )
159- err = verifier .StartChangeStream (ctx )
160- suite .Require ().NoError (err )
161- suite .Require ().Equal (verifier .srcStartAtTs , origStartTs )
162- verifier .changeStreamWritesOffTsChan <- * origStartTs
163- <- verifier .changeStreamDoneChan
164- suite .Require ().Equal (verifier .srcStartAtTs , origStartTs )
166+ suite .startSrcChangeStreamReaderAndHandler (ctx , verifier )
167+ suite .Require ().Equal (verifier .srcChangeStreamReader .startAtTs , origStartTs )
168+ verifier .srcChangeStreamReader .writesOffTsChan <- * origStartTs
169+ <- verifier .srcChangeStreamReader .doneChan
170+ suite .Require ().Equal (verifier .srcChangeStreamReader .startAtTs , origStartTs )
165171}
166172
167173func (suite * IntegrationTestSuite ) TestStartAtTimeWithChanges () {
@@ -176,13 +182,12 @@ func (suite *IntegrationTestSuite) TestStartAtTimeWithChanges() {
176182
177183 origSessionTime := sess .OperationTime ()
178184 suite .Require ().NotNil (origSessionTime )
179- err = verifier .StartChangeStream (ctx )
180- suite .Require ().NoError (err )
185+ suite .startSrcChangeStreamReaderAndHandler (ctx , verifier )
181186
182187 // srcStartAtTs derives from the change stream’s resume token, which can
183188 // postdate our session time but should not precede it.
184189 suite .Require ().False (
185- verifier .srcStartAtTs .Before (* origSessionTime ),
190+ verifier .srcChangeStreamReader . startAtTs .Before (* origSessionTime ),
186191 "srcStartAtTs should be >= the insert’s optime" ,
187192 )
188193
@@ -206,12 +211,12 @@ func (suite *IntegrationTestSuite) TestStartAtTimeWithChanges() {
206211 "session time after events should exceed the original" ,
207212 )
208213
209- verifier .changeStreamWritesOffTsChan <- * postEventsSessionTime
210- <- verifier .changeStreamDoneChan
214+ verifier .srcChangeStreamReader . writesOffTsChan <- * postEventsSessionTime
215+ <- verifier .srcChangeStreamReader . doneChan
211216
212217 suite .Assert ().Equal (
213218 * postEventsSessionTime ,
214- * verifier .srcStartAtTs ,
219+ * verifier .srcChangeStreamReader . startAtTs ,
215220 "verifier.srcStartAtTs should now be our session timestamp" ,
216221 )
217222}
@@ -227,10 +232,9 @@ func (suite *IntegrationTestSuite) TestNoStartAtTime() {
227232 suite .Require ().NoError (err )
228233 origStartTs := sess .OperationTime ()
229234 suite .Require ().NotNil (origStartTs )
230- err = verifier .StartChangeStream (ctx )
231- suite .Require ().NoError (err )
232- suite .Require ().NotNil (verifier .srcStartAtTs )
233- suite .Require ().LessOrEqual (origStartTs .Compare (* verifier .srcStartAtTs ), 0 )
235+ suite .startSrcChangeStreamReaderAndHandler (ctx , verifier )
236+ suite .Require ().NotNil (verifier .srcChangeStreamReader .startAtTs )
237+ suite .Require ().LessOrEqual (origStartTs .Compare (* verifier .srcChangeStreamReader .startAtTs ), 0 )
234238}
235239
236240func (suite * IntegrationTestSuite ) TestWithChangeEventsBatching () {
@@ -246,7 +250,7 @@ func (suite *IntegrationTestSuite) TestWithChangeEventsBatching() {
246250
247251 verifier := suite .BuildVerifier ()
248252
249- suite .Require (). NoError ( verifier . StartChangeStream ( ctx ) )
253+ suite .startSrcChangeStreamReaderAndHandler ( ctx , verifier )
250254
251255 _ , err := coll1 .InsertOne (ctx , bson.D {{"_id" , 1 }})
252256 suite .Require ().NoError (err )
@@ -267,7 +271,6 @@ func (suite *IntegrationTestSuite) TestWithChangeEventsBatching() {
267271 500 * time .Millisecond ,
268272 "the verifier should flush a recheck doc after a batch" ,
269273 )
270-
271274}
272275
273276func (suite * IntegrationTestSuite ) TestCursorKilledResilience () {
@@ -451,6 +454,77 @@ func (suite *IntegrationTestSuite) TestCreateForbidden() {
451454 suite .Assert ().Equal ("create" , eventErr .Event .OpType )
452455}
453456
457+ func (suite * IntegrationTestSuite ) TestRecheckDocsWithDstChangeEvents () {
458+ ctx := suite .Context ()
459+
460+ srcDBName := suite .DBNameForTest ("src" )
461+ dstDBName := suite .DBNameForTest ("dst" )
462+
463+ db := suite .dstMongoClient .Database (dstDBName )
464+ coll1 := db .Collection ("dstColl1" )
465+ coll2 := db .Collection ("dstColl2" )
466+
467+ for _ , coll := range mslices .Of (coll1 , coll2 ) {
468+ suite .Require ().NoError (db .CreateCollection (ctx , coll .Name ()))
469+ }
470+
471+ verifier := suite .BuildVerifier ()
472+ verifier .SetSrcNamespaces ([]string {srcDBName + ".srcColl1" , srcDBName + ".srcColl2" })
473+ verifier .SetDstNamespaces ([]string {dstDBName + ".dstColl1" , dstDBName + ".dstColl2" })
474+ verifier .SetNamespaceMap ()
475+
476+ suite .Require ().NoError (verifier .dstChangeStreamReader .StartChangeStream (ctx ))
477+ go func () {
478+ err := verifier .StartChangeEventHandler (ctx , verifier .dstChangeStreamReader )
479+ if errors .Is (err , context .Canceled ) {
480+ return
481+ }
482+ suite .Require ().NoError (err )
483+ }()
484+
485+ _ , err := coll1 .InsertOne (ctx , bson.D {{"_id" , 1 }})
486+ suite .Require ().NoError (err )
487+ _ , err = coll1 .InsertOne (ctx , bson.D {{"_id" , 2 }})
488+ suite .Require ().NoError (err )
489+
490+ _ , err = coll2 .InsertOne (ctx , bson.D {{"_id" , 1 }})
491+ suite .Require ().NoError (err )
492+
493+ var rechecks []RecheckDoc
494+ require .Eventually (
495+ suite .T (),
496+ func () bool {
497+ recheckColl := verifier .verificationDatabase ().Collection (recheckQueue )
498+ cursor , err := recheckColl .Find (ctx , bson.D {})
499+ if errors .Is (err , mongo .ErrNoDocuments ) {
500+ return false
501+ }
502+
503+ suite .Require ().NoError (err )
504+ suite .Require ().NoError (cursor .All (ctx , & rechecks ))
505+ return len (rechecks ) == 3
506+ },
507+ time .Minute ,
508+ 500 * time .Millisecond ,
509+ "the verifier should flush a recheck doc after a batch" ,
510+ )
511+
512+ coll1RecheckCount , coll2RecheckCount := 0 , 0
513+ for _ , recheck := range rechecks {
514+ suite .Require ().Equal (srcDBName , recheck .PrimaryKey .SrcDatabaseName )
515+ switch recheck .PrimaryKey .SrcCollectionName {
516+ case "srcColl1" :
517+ coll1RecheckCount ++
518+ case "srcColl2" :
519+ coll2RecheckCount ++
520+ default :
521+ suite .T ().Fatalf ("unknown collection name: %v" , recheck .PrimaryKey .SrcCollectionName )
522+ }
523+ }
524+ suite .Require ().Equal (2 , coll1RecheckCount )
525+ suite .Require ().Equal (1 , coll2RecheckCount )
526+ }
527+
454528func (suite * IntegrationTestSuite ) TestLargeEvents () {
455529 ctx := suite .Context ()
456530
0 commit comments