From 490a94ee93d02ea86f065d7f258d3103a3596d1b Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Wed, 5 Aug 2026 18:26:33 -0500 Subject: [PATCH] TOOLS-4278 Convert mongodump BSON and metadata tests to testify Converts TestMongoDumpBSON, TestMongoDumpBSONLongCollectionName, TestDumpPreludeMetadataJson and TestMongoDumpMetaData, along with the three helpers they call. The rest of the file stays on GoConvey for now, so the dot-import remains. Thirteen Reset calls become t.Cleanup registered at the scope matching the leaves each one originally fired for. The prelude default-directory leaves each get their own removal rather than sharing one, and the "dump directory not created" case keeps its after-only cleanup, both matching the original. Cleanups use assert and delete with context.Background(), since t.Context() is cancelled before they run; that is why tearDownMongoDumpTestDataInCleanup exists alongside the t-taking version the unconverted tests still use. testDumpOneCollection's count check is NotZero rather than NotEqual against 0. restoredCount is an int64, and testify compares types, so NotEqual against an untyped 0 is satisfied by the type difference alone and passes even when nothing was restored. ShouldNotEqual was tolerant here and did fail. Assertion sites go from 133 to 130. The three fewer are unchecked Close and Drop calls in the long-collection-name test, which is skipped and which the original left unchecked. No behavior change. --- mongodump/mongodump_test.go | 951 ++++++++++++++++++++---------------- 1 file changed, 520 insertions(+), 431 deletions(-) diff --git a/mongodump/mongodump_test.go b/mongodump/mongodump_test.go index 541e1f241..a54e0ae6e 100644 --- a/mongodump/mongodump_test.go +++ b/mongodump/mongodump_test.go @@ -462,6 +462,17 @@ func tearDownMongoDumpTestData(t *testing.T) error { return nil } +// tearDownMongoDumpTestDataInCleanup mirrors tearDownMongoDumpTestData for use +// inside a t.Cleanup body, where t.Context() is already canceled. +func tearDownMongoDumpTestDataInCleanup() error { + session, err := testutil.GetBareSession() + if err != nil { + return err + } + + return session.Database(testDB).Drop(context.Background()) +} + func dropDB(t *testing.T, dbName string) error { session, err := testutil.GetBareSession() if err != nil { @@ -484,7 +495,13 @@ func fileDirExists(name string) bool { return true } +// testQuery dumps each of testCollectionNames using --query* filters already +// set on md, restores the dump, and checks that only the filtered documents +// came back. It shares one restore database across every collection, so +// callers own tearing that database down. func testQuery(t *testing.T, md *MongoDump, session *mongo.Client) string { + t.Helper() + origDB := session.Database(testDB) restoredDB := session.Database(testRestoreDB) @@ -495,96 +512,130 @@ func testQuery(t *testing.T, md *MongoDump, session *mongo.Client) string { for _, testCollName := range testCollectionNames { md.ToolOptions.Collection = testCollName - err := md.Init() - So(err, ShouldBeNil) - - err = md.Dump() - So(err, ShouldBeNil) + require.NoError(t, md.Init(), "should initialize mongodump for %q", testCollName) + require.NoError(t, md.Dump(), "should dump %q with the query applied", testCollName) } path, err := os.Getwd() - So(err, ShouldBeNil) + require.NoError(t, err, "should get the working directory") dumpDir := filepath.FromSlash(filepath.Join(path, "dump")) dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, testDB)) - So(fileDirExists(dumpDir), ShouldBeTrue) - So(fileDirExists(dumpDBDir), ShouldBeTrue) + require.True(t, fileDirExists(dumpDir), "should create the dump directory") + require.True(t, fileDirExists(dumpDBDir), "should create the database directory") - So(restoredDB.Drop(t.Context()), ShouldBeNil) - err = readBSONIntoDatabase(t, dumpDBDir, testRestoreDB) - So(err, ShouldBeNil) + require.NoError( + t, + restoredDB.Drop(t.Context()), + "should drop any pre-existing restore database", + ) + require.NoError( + t, + readBSONIntoDatabase(t, dumpDBDir, testRestoreDB), + "should restore the dumped bson into the database", + ) for _, testCollName := range testCollectionNames { // count filtered docs origDocCount, err := origDB.Collection(testCollName). CountDocuments(t.Context(), bsonQuery) - So(err, ShouldBeNil) + require.NoError(t, err, "should count the original filtered documents for %q", testCollName) // count number of all restored documents restDocCount, err := restoredDB.Collection(testCollName). CountDocuments(t.Context(), bson.D{}) - So(err, ShouldBeNil) + require.NoError(t, err, "should count the restored documents for %q", testCollName) - So(restDocCount, ShouldEqual, origDocCount) + require.EqualValues( + t, + origDocCount, + restDocCount, + "should restore exactly the documents matching the query for %q", + testCollName, + ) } return dumpDir } +// testDumpOneCollection dumps md's configured collection to dumpDir, restores +// it, and checks the restored collection matches the original. The two +// checks below (count, then per-document content) read the same restored +// data and don't need independent setup, so they run as one pass rather than +// as separate scenarios. func testDumpOneCollection(t *testing.T, md *MongoDump, dumpDir string) { + t.Helper() + path, err := os.Getwd() - So(err, ShouldBeNil) + require.NoError(t, err, "should get the working directory") absDumpDir := filepath.FromSlash(filepath.Join(path, dumpDir)) - So(os.RemoveAll(absDumpDir), ShouldBeNil) - So(fileDirExists(absDumpDir), ShouldBeFalse) + require.NoError(t, os.RemoveAll(absDumpDir), "should remove any pre-existing dump directory") + require.False(t, fileDirExists(absDumpDir), "should not have a dump directory before dumping") dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, testDB)) - So(fileDirExists(dumpDBDir), ShouldBeFalse) + require.False( + t, + fileDirExists(dumpDBDir), + "should not have a database directory before dumping", + ) md.OutputOptions.Out = dumpDir - err = md.Dump() - So(err, ShouldBeNil) - So(fileDirExists(dumpDBDir), ShouldBeTrue) + require.NoError(t, md.Dump(), "should dump the collection") + require.True(t, fileDirExists(dumpDBDir), "should create the database directory") session, err := testutil.GetBareSession() - So(err, ShouldBeNil) + require.NoError(t, err, "should connect to the server") countColls, err := countNonIndexBSONFiles(dumpDBDir) - So(err, ShouldBeNil) - So(countColls, ShouldEqual, 1) + require.NoError(t, err, "should count the dumped bson files") + require.EqualValues(t, 1, countColls, "should dump exactly one collection") collOriginal := session.Database(testDB).Collection(md.ToolOptions.Collection) - So(session.Database(testRestoreDB).Drop(t.Context()), ShouldBeNil) + require.NoError( + t, + session.Database(testRestoreDB).Drop(t.Context()), + "should drop any pre-existing restore database", + ) collRestore := session.Database(testRestoreDB).Collection(md.ToolOptions.Collection) - err = readBSONIntoDatabase(t, dumpDBDir, testRestoreDB) - So(err, ShouldBeNil) + require.NoError( + t, + readBSONIntoDatabase(t, dumpDBDir, testRestoreDB), + "should restore the dumped bson into the database", + ) - Convey("with the correct number of documents", func() { - numDocsOrig, err := collOriginal.CountDocuments(t.Context(), bson.D{}) - So(err, ShouldBeNil) + // with the correct number of documents + numDocsOrig, err := collOriginal.CountDocuments(t.Context(), bson.D{}) + require.NoError(t, err, "should count the original documents") - numDocsRestore, err := collRestore.CountDocuments(t.Context(), bson.D{}) - So(err, ShouldBeNil) + numDocsRestore, err := collRestore.CountDocuments(t.Context(), bson.D{}) + require.NoError(t, err, "should count the restored documents") - So(numDocsRestore, ShouldEqual, numDocsOrig) - }) + require.EqualValues( + t, + numDocsOrig, + numDocsRestore, + "should restore the correct number of documents", + ) - Convey("that are the same as the documents in the test database", func() { - iter, err := collOriginal.Find(t.Context(), bson.D{}) - So(err, ShouldBeNil) + // that are the same as the documents in the test database + iter, err := collOriginal.Find(t.Context(), bson.D{}) + require.NoError(t, err, "should query the original collection") - var result bson.D - for iter.Next(t.Context()) { - So(iter.Decode(&result), ShouldBeNil) - restoredCount, err := collRestore.CountDocuments(t.Context(), result) - So(err, ShouldBeNil) - So(restoredCount, ShouldNotEqual, 0) - } - So(iter.Err(), ShouldBeNil) - So(iter.Close(t.Context()), ShouldBeNil) - }) + var result bson.D + for iter.Next(t.Context()) { + require.NoError(t, iter.Decode(&result), "should decode the original document") + restoredCount, err := collRestore.CountDocuments(t.Context(), result) + require.NoError(t, err, "should count matching restored documents") + require.NotZero( + t, + restoredCount, + "should find each original document in the restored collection", + ) + } + require.NoError(t, iter.Err(), "should iterate every original document without error") + require.NoError(t, iter.Close(t.Context()), "should close the original cursor") } func TestMongoDumpValidateOptions(t *testing.T) { @@ -666,189 +717,221 @@ func TestMongoDumpBSON(t *testing.T) { testtype.SkipUnlessTestType(t, testtype.IntegrationTestType) log.SetWriter(io.Discard) - Convey("With a MongoDump instance", t, func() { - err := setUpMongoDumpTestData(t) - So(err, ShouldBeNil) + t.Run("dumps a particular collection to the default output directory", func(t *testing.T) { + setUpMongoDumpBSONSubtest(t) + md := newMongoDumpForBSONSubtest(t, testCollectionNames[0]) + require.NoError(t, md.Init(), "should initialize mongodump") + testDumpOneCollection(t, md, "dump") + }) - Convey( - "testing that using MongoDump WITHOUT giving a query dumps everything in the database and/or collection", - func() { - md, err := simpleMongoDumpInstance() - So(err, ShouldBeNil) + t.Run("dumps a particular collection to a user-specified output directory", func(t *testing.T) { + setUpMongoDumpBSONSubtest(t) + md := newMongoDumpForBSONSubtest(t, testCollectionNames[0]) + require.NoError(t, md.Init(), "should initialize mongodump") + testDumpOneCollection(t, md, "dump_user") + }) - md.InputOptions.Query = "" - - Convey("and that for a particular collection", func() { - md.ToolOptions.Collection = testCollectionNames[0] - err = md.Init() - So(err, ShouldBeNil) - - Convey("it dumps to the default output directory", func() { - testDumpOneCollection(t, md, "dump") - }) - - Convey("it dumps to a user-specified output directory", func() { - testDumpOneCollection(t, md, "dump_user") - }) - - Convey("it dumps to standard output", func() { - md.OutputOptions.Out = "-" - stdoutBuf := &bytes.Buffer{} - md.OutputWriter = stdoutBuf - err = md.Dump() - So(err, ShouldBeNil) - var count int - bsonSource := db.NewDecodedBSONSource( - db.NewBSONSource(io.NopCloser(stdoutBuf)), - ) - defer bsonSource.Close() - - var result bson.Raw - for bsonSource.Next(&result) { - count++ - } - So(bsonSource.Err(), ShouldBeNil) - So(count, ShouldEqual, 10) //The 0th collection has 10 documents - - Reset(func() { - }) - - }) - - }) - - Convey("and that it dumps a collection with a slash in its name", func() { - md.ToolOptions.Collection = testCollectionNames[2] - - Convey("to the filesystem", func() { - err = md.Init() - So(err, ShouldBeNil) - testDumpOneCollection(t, md, "dump_slash") - }) - - Convey("to an archive", func() { - md.OutputOptions.Archive = "dump_slash.archive" - err = md.Init() - So(err, ShouldBeNil) - }) - }) - - Convey("for an entire database", func() { - md.ToolOptions.Collection = "" - err = md.Init() - So(err, ShouldBeNil) - - Convey( - "that exists. The dumped directory should contain the necessary bson files", - func() { - md.OutputOptions.Out = "dump" - err = md.Dump() - So(err, ShouldBeNil) - path, err := os.Getwd() - So(err, ShouldBeNil) - - dumpDir := filepath.FromSlash(filepath.Join(path, "dump")) - dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, testDB)) - So(fileDirExists(dumpDir), ShouldBeTrue) - So(fileDirExists(dumpDBDir), ShouldBeTrue) - - countColls, err := countNonIndexBSONFiles(dumpDBDir) - So(err, ShouldBeNil) - So(countColls, ShouldEqual, len(testCollectionNames)) - - Reset(func() { - So(os.RemoveAll(dumpDir), ShouldBeNil) - }) + t.Run("dumps a particular collection to standard output", func(t *testing.T) { + setUpMongoDumpBSONSubtest(t) + md := newMongoDumpForBSONSubtest(t, testCollectionNames[0]) + require.NoError(t, md.Init(), "should initialize mongodump") - }, - ) + md.OutputOptions.Out = "-" + stdoutBuf := &bytes.Buffer{} + md.OutputWriter = stdoutBuf + require.NoError(t, md.Dump(), "should dump the collection to standard output") - Convey( - "that does not exist. The dumped directory shouldn't be created", - func() { - md.OutputOptions.Out = "dump" - md.ToolOptions.DB = "nottestdb" - err = md.Dump() - So(err, ShouldBeNil) + var count int + bsonSource := db.NewDecodedBSONSource(db.NewBSONSource(io.NopCloser(stdoutBuf))) + defer bsonSource.Close() - path, err := os.Getwd() - So(err, ShouldBeNil) + var result bson.Raw + for bsonSource.Next(&result) { + count++ + } + require.NoError(t, bsonSource.Err(), "should read every dumped document") + // The 0th collection has 10 documents. + require.EqualValues(t, 10, count, "should dump every document in the collection") + }) - dumpDir := filepath.FromSlash(filepath.Join(path, "dump")) - dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, "nottestdb")) + t.Run("dumps a collection with a slash in its name to the filesystem", func(t *testing.T) { + setUpMongoDumpBSONSubtest(t) + md := newMongoDumpForBSONSubtest(t, testCollectionNames[2]) + require.NoError(t, md.Init(), "should initialize mongodump") + testDumpOneCollection(t, md, "dump_slash") + }) - So(fileDirExists(dumpDir), ShouldBeFalse) - So(fileDirExists(dumpDBDir), ShouldBeFalse) - }, - ) + t.Run( + "initializes a collection with a slash in its name for archive output", + func(t *testing.T) { + setUpMongoDumpBSONSubtest(t) + md := newMongoDumpForBSONSubtest(t, testCollectionNames[2]) + md.OutputOptions.Archive = "dump_slash.archive" + require.NoError(t, md.Init(), "should initialize mongodump for archive output") + }, + ) - }) - }, - ) + t.Run( + "dumps an entire database that exists, producing bson files for every collection", + func(t *testing.T) { + setUpMongoDumpBSONSubtest(t) + md := newMongoDumpForBSONSubtest(t, "") + require.NoError(t, md.Init(), "should initialize mongodump") - Convey( - "testing that using MongoDump WITH a query dumps a subset of documents in a database and/or collection", - func() { - session, err := testutil.GetBareSession() - So(err, ShouldBeNil) - md, err := simpleMongoDumpInstance() - So(err, ShouldBeNil) + md.OutputOptions.Out = "dump" + require.NoError(t, md.Dump(), "should dump the whole database") - // expect 10 documents per collection - bsonQuery := bson.M{"age": bson.M{"$lt": 10}} - jsonQuery, err := bsonutil.ConvertBSONValueToLegacyExtJSON(bsonQuery) - So(err, ShouldBeNil) - jsonQueryBytes, err := json.Marshal(jsonQuery) - So(err, ShouldBeNil) + path, err := os.Getwd() + require.NoError(t, err, "should get the working directory") - Convey("using --query for all the collections in the database", func() { - md.InputOptions.Query = string(jsonQueryBytes) - md.ToolOptions.DB = testDB - md.OutputOptions.Out = "dump" - dumpDir := testQuery(t, md, session) - - Reset(func() { - So(session.Database(testRestoreDB).Drop(t.Context()), ShouldBeNil) - So(os.RemoveAll(dumpDir), ShouldBeNil) - }) - - }) - - Convey("using --queryFile for all the collections in the database", func() { - err = os.WriteFile("example.json", jsonQueryBytes, 0777) - So(err, ShouldBeNil) - md.InputOptions.QueryFile = "example.json" - md.ToolOptions.DB = testDB - md.OutputOptions.Out = "dump" - dumpDir := testQuery(t, md, session) - - Reset(func() { - So(session.Database(testRestoreDB).Drop(t.Context()), ShouldBeNil) - So(os.RemoveAll(dumpDir), ShouldBeNil) - So(os.Remove("example.json"), ShouldBeNil) - }) - - }) - }, - ) + dumpDir := filepath.FromSlash(filepath.Join(path, "dump")) + dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, testDB)) + require.True(t, fileDirExists(dumpDir), "should create the dump directory") + require.True(t, fileDirExists(dumpDBDir), "should create the database directory") - Convey("using MongoDump against a collection that doesn't exist succeeds", func() { - md, err := simpleMongoDumpInstance() - So(err, ShouldBeNil) + countColls, err := countNonIndexBSONFiles(dumpDBDir) + require.NoError(t, err, "should count the dumped bson files") + require.EqualValues( + t, + len(testCollectionNames), + countColls, + "should dump every collection", + ) - md.ToolOptions.DB = "nonExistentDB" - md.ToolOptions.Collection = "nonExistentColl" + t.Cleanup(func() { + assert.NoError(t, os.RemoveAll(dumpDir), "should remove the dump directory") + }) + }, + ) - err = md.Init() - So(err, ShouldBeNil) - err = md.Dump() - So(err, ShouldBeNil) + t.Run( + "does not create a dump directory for a database that does not exist", + func(t *testing.T) { + setUpMongoDumpBSONSubtest(t) + md := newMongoDumpForBSONSubtest(t, "") + require.NoError(t, md.Init(), "should initialize mongodump") + + md.OutputOptions.Out = "dump" + md.ToolOptions.DB = "nottestdb" + require.NoError(t, md.Dump(), "should succeed dumping a nonexistent database") + + path, err := os.Getwd() + require.NoError(t, err, "should get the working directory") + + dumpDir := filepath.FromSlash(filepath.Join(path, "dump")) + dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, "nottestdb")) + require.False(t, fileDirExists(dumpDir), "should not create the dump directory") + require.False( + t, + fileDirExists(dumpDBDir), + "should not create the database directory", + ) + }, + ) + + t.Run("using --query for all the collections in the database", func(t *testing.T) { + setUpMongoDumpBSONSubtest(t) + session, jsonQueryBytes := newMongoDumpQuerySubtestFixture(t) + md := newMongoDumpForBSONSubtest(t, "") + + md.InputOptions.Query = string(jsonQueryBytes) + md.ToolOptions.DB = testDB + md.OutputOptions.Out = "dump" + dumpDir := testQuery(t, md, session) + + t.Cleanup(func() { + assert.NoError( + t, + session.Database(testRestoreDB).Drop(context.Background()), + "should drop the restore database", + ) + assert.NoError(t, os.RemoveAll(dumpDir), "should remove the dump directory") }) + }) - Reset(func() { - So(tearDownMongoDumpTestData(t), ShouldBeNil) + t.Run("using --queryFile for all the collections in the database", func(t *testing.T) { + setUpMongoDumpBSONSubtest(t) + session, jsonQueryBytes := newMongoDumpQuerySubtestFixture(t) + md := newMongoDumpForBSONSubtest(t, "") + + require.NoError( + t, + os.WriteFile("example.json", jsonQueryBytes, 0777), + "should write the query file", + ) + md.InputOptions.QueryFile = "example.json" + md.ToolOptions.DB = testDB + md.OutputOptions.Out = "dump" + dumpDir := testQuery(t, md, session) + + t.Cleanup(func() { + assert.NoError( + t, + session.Database(testRestoreDB).Drop(context.Background()), + "should drop the restore database", + ) + assert.NoError(t, os.RemoveAll(dumpDir), "should remove the dump directory") + assert.NoError(t, os.Remove("example.json"), "should remove the query file") }) }) + + t.Run("using mongodump against a collection that doesn't exist succeeds", func(t *testing.T) { + setUpMongoDumpBSONSubtest(t) + md, err := simpleMongoDumpInstance() + require.NoError(t, err, "should build a mongodump instance") + + md.ToolOptions.DB = "nonExistentDB" + md.ToolOptions.Collection = "nonExistentColl" + + require.NoError(t, md.Init(), "should initialize mongodump") + require.NoError(t, md.Dump(), "should succeed dumping a nonexistent collection") + }) +} + +// setUpMongoDumpBSONSubtest inserts fresh test data for one TestMongoDumpBSON +// subtest and registers its teardown, matching the setUp/Reset pair GoConvey +// ran around every leaf of the original nested test. +func setUpMongoDumpBSONSubtest(t *testing.T) { + t.Helper() + + require.NoError(t, setUpMongoDumpTestData(t), "should set up test data") + t.Cleanup(func() { + assert.NoError(t, tearDownMongoDumpTestDataInCleanup(), "should tear down test data") + }) +} + +// newMongoDumpForBSONSubtest builds the MongoDump instance shared by the +// WITHOUT-a-query subtests, with collName applied when it isn't empty. +func newMongoDumpForBSONSubtest(t *testing.T, collName string) *MongoDump { + t.Helper() + + md, err := simpleMongoDumpInstance() + require.NoError(t, err, "should build a mongodump instance") + + md.InputOptions.Query = "" + if collName != "" { + md.ToolOptions.Collection = collName + } + + return md +} + +// newMongoDumpQuerySubtestFixture builds the session and marshaled query +// shared by the --query and --queryFile subtests. +func newMongoDumpQuerySubtestFixture(t *testing.T) (*mongo.Client, []byte) { + t.Helper() + + session, err := testutil.GetBareSession() + require.NoError(t, err, "should connect to the server") + + // expect 10 documents per collection + bsonQuery := bson.M{"age": bson.M{"$lt": 10}} + jsonQuery, err := bsonutil.ConvertBSONValueToLegacyExtJSON(bsonQuery) + require.NoError(t, err, "should convert the query to extended json") + jsonQueryBytes, err := json.Marshal(jsonQuery) + require.NoError(t, err, "should marshal the query to json") + + return session, jsonQueryBytes } func TestMongoDumpBSONLongCollectionName(t *testing.T) { @@ -868,302 +951,308 @@ func TestMongoDumpBSONLongCollectionName(t *testing.T) { log.SetWriter(io.Discard) - Convey("With a MongoDump instance", t, func() { - err = setUpMongoDumpTestData(t) - So(err, ShouldBeNil) - - md, err := simpleMongoDumpInstance() - So(err, ShouldBeNil) + require.NoError(t, setUpMongoDumpTestData(t), "should set up test data") + t.Cleanup(func() { + assert.NoError(t, tearDownMongoDumpTestDataInCleanup(), "should tear down test data") + }) - Convey( - "testing that it dumps a collection with a name >238 bytes in the right format", - func() { - coll := session.Database(testDB).Collection(longCollectionName) - _, err = coll.InsertOne(t.Context(), bson.M{"a": 1}) - So(err, ShouldBeNil) - //nolint:errcheck - defer coll.Drop(t.Context()) + md, err := simpleMongoDumpInstance() + require.NoError(t, err, "should build a mongodump instance") - md.ToolOptions.Collection = longCollectionName - err = md.Init() - So(err, ShouldBeNil) + // testing that it dumps a collection with a name >238 bytes in the right format + coll := session.Database(testDB).Collection(longCollectionName) + _, err = coll.InsertOne(t.Context(), bson.M{"a": 1}) + require.NoError(t, err, "should insert a document into the long-named collection") + //nolint:errcheck + defer coll.Drop(t.Context()) - path, err := os.Getwd() - So(err, ShouldBeNil) + md.ToolOptions.Collection = longCollectionName + require.NoError(t, md.Init(), "should initialize mongodump") - absDumpDir := filepath.FromSlash(filepath.Join(path, "dump_slash")) - So(os.RemoveAll(absDumpDir), ShouldBeNil) - So(fileDirExists(absDumpDir), ShouldBeFalse) + path, err := os.Getwd() + require.NoError(t, err, "should get the working directory") - dumpDBDir := filepath.FromSlash(filepath.Join("dump_slash", testDB)) - So(fileDirExists(dumpDBDir), ShouldBeFalse) + absDumpDir := filepath.FromSlash(filepath.Join(path, "dump_slash")) + require.NoError(t, os.RemoveAll(absDumpDir), "should remove any pre-existing dump directory") + require.False(t, fileDirExists(absDumpDir), "should not have a dump directory before dumping") - md.OutputOptions.Out = "dump_slash" - err = md.Dump() - So(err, ShouldBeNil) - So(fileDirExists(dumpDBDir), ShouldBeTrue) + dumpDBDir := filepath.FromSlash(filepath.Join("dump_slash", testDB)) + require.False( + t, + fileDirExists(dumpDBDir), + "should not have a database directory before dumping", + ) - Convey("to a bson file", func() { - oneBsonFile, err := os.Open( - filepath.FromSlash(filepath.Join(dumpDBDir, longBsonName)), - ) - So(err, ShouldBeNil) - oneBsonFile.Close() - }) + md.OutputOptions.Out = "dump_slash" + require.NoError(t, md.Dump(), "should dump the long-named collection") + require.True(t, fileDirExists(dumpDBDir), "should create the database directory") - Convey("to a metadata file", func() { - oneMetaFile, err := os.Open( - filepath.FromSlash(filepath.Join(dumpDBDir, longMetadataName)), - ) - So(err, ShouldBeNil) - oneMetaFile.Close() - }) - }, - ) + // to a bson file + oneBsonFile, err := os.Open(filepath.FromSlash(filepath.Join(dumpDBDir, longBsonName))) + require.NoError(t, err, "should open the dumped bson file for the long collection name") + oneBsonFile.Close() - Reset(func() { - So(tearDownMongoDumpTestData(t), ShouldBeNil) - }) - }) + // to a metadata file + oneMetaFile, err := os.Open(filepath.FromSlash(filepath.Join(dumpDBDir, longMetadataName))) + require.NoError(t, err, "should open the dumped metadata file for the long collection name") + oneMetaFile.Close() } -func testPreludeMetadata(md *MongoDump, dir string, serverVersion string) { - So(fileDirExists(dir), ShouldBeFalse) - err := md.Init() - So(err, ShouldBeNil) +func testPreludeMetadata(t *testing.T, md *MongoDump, dir string, serverVersion string) { + t.Helper() - err = md.Dump() - So(err, ShouldBeNil) + require.False(t, fileDirExists(dir), "should not have a dump directory before dumping") + require.NoError(t, md.Init(), "should initialize mongodump") + require.NoError(t, md.Dump(), "should dump the database") preludeFilepath := filepath.Join(dir, "prelude.json") if md.OutputOptions.Gzip { preludeFilepath += ".gz" } - So(fileDirExists(preludeFilepath), ShouldBeTrue) + require.True(t, fileDirExists(preludeFilepath), "should write a prelude file") + var reader io.Reader preludeFile, err := os.Open(filepath.FromSlash(preludeFilepath)) - So(err, ShouldBeNil) + require.NoError(t, err, "should open the prelude file") reader = preludeFile defer preludeFile.Close() if md.OutputOptions.Gzip { zipfile, err := gzip.NewReader(preludeFile) - So(err, ShouldBeNil) + require.NoError(t, err, "should open the gzipped prelude file") defer zipfile.Close() reader = zipfile } contents, err := io.ReadAll(reader) - So(err, ShouldBeNil) + require.NoError(t, err, "should read the prelude file") + var jsonResult map[string]any - err = json.Unmarshal(contents, &jsonResult) - So(err, ShouldBeNil) - So(jsonResult["ServerVersion"], ShouldEqual, serverVersion) + require.NoError(t, json.Unmarshal(contents, &jsonResult), "should unmarshal the prelude json") + require.EqualValues( + t, + serverVersion, + jsonResult["ServerVersion"], + "should record the connected server version in the prelude", + ) } func TestDumpPreludeMetadataJson(t *testing.T) { testtype.SkipUnlessTestType(t, testtype.IntegrationTestType) log.SetWriter(io.Discard) - Convey("With a MongoDump instance", t, func() { - path, err := os.Getwd() - So(err, ShouldBeNil) - - err = setUpMongoDumpTestData(t) - So(err, ShouldBeNil) + t.Run( + "writes prelude.json to the dump directory when dumping all databases", + func(t *testing.T) { + setUpPreludeMetadataSubtest(t) + md, serverVersion := newMongoDumpForPreludeSubtest(t, "") - sessionProvider, _, _ := testutil.GetBareSessionProvider() - So(sessionProvider, ShouldNotBeNil) - serverVersion, err := sessionProvider.ServerVersion() - So(err, ShouldBeNil) - - Convey("when dumping all databases", func() { - md, err := simpleMongoDumpInstance() - So(err, ShouldBeNil) + dumpDir := preludeSubtestDumpDir(t, "dump") + testPreludeMetadata(t, md, dumpDir, serverVersion) + }, + ) - md.ToolOptions.DB = "" - md.ToolOptions.Collection = "" + t.Run( + "writes prelude.json.gz to the dump directory when dumping all databases with --gzip", + func(t *testing.T) { + setUpPreludeMetadataSubtest(t) + md, serverVersion := newMongoDumpForPreludeSubtest(t, "") + md.OutputOptions.Gzip = true - Convey("when dumping to the default directory", func() { - dumpDir := filepath.FromSlash(filepath.Join(path, "dump")) - So(os.RemoveAll(dumpDir), ShouldBeNil) + dumpDir := preludeSubtestDumpDir(t, "dump") + testPreludeMetadata(t, md, dumpDir, serverVersion) + }, + ) - Convey("writes prelude.json to dump directory", func() { - testPreludeMetadata(md, dumpDir, serverVersion) - }) + t.Run( + "writes prelude.json to a user-specified output directory when dumping all databases", + func(t *testing.T) { + setUpPreludeMetadataSubtest(t) + md, serverVersion := newMongoDumpForPreludeSubtest(t, "") + md.OutputOptions.Out = "dump_output" - Convey("writes prelude.json.gz to dump directory when --gzip is used", func() { - md.OutputOptions.Gzip = true - testPreludeMetadata(md, dumpDir, serverVersion) - }) + dumpDir := preludeSubtestDumpDir(t, "dump_output") + testPreludeMetadata(t, md, dumpDir, serverVersion) + }, + ) - Reset(func() { - So(os.RemoveAll(dumpDir), ShouldBeNil) - }) - }) + t.Run("writes prelude.json to the dump directory when dumping one db", func(t *testing.T) { + setUpPreludeMetadataSubtest(t) + md, serverVersion := newMongoDumpForPreludeSubtest(t, testDB) - Convey("when output directory is specified", func() { - dumpDir := filepath.FromSlash(filepath.Join(path, "dump_output")) - So(os.RemoveAll(dumpDir), ShouldBeNil) + dumpDir := preludeSubtestDumpDir(t, "dump") + dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, testDB)) + testPreludeMetadata(t, md, dumpDBDir, serverVersion) + }) - Convey("writes prelude.json to output directory", func() { - md.OutputOptions.Out = "dump_output" - testPreludeMetadata(md, dumpDir, serverVersion) - }) + t.Run( + "does not fail and does not create prelude.json when the dump directory is not created", + func(t *testing.T) { + setUpPreludeMetadataSubtest(t) + md, _ := newMongoDumpForPreludeSubtest(t, "nonExistentDB") - Reset(func() { - So(os.RemoveAll(dumpDir), ShouldBeNil) - }) + path, err := os.Getwd() + require.NoError(t, err, "should get the working directory") + dumpDir := filepath.FromSlash(filepath.Join(path, "dump")) + dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, "nottestdb")) + t.Cleanup(func() { + assert.NoError(t, os.RemoveAll(dumpDir), "should remove the dump directory") }) - }) - Convey("when dumping one db", func() { - md, err := simpleMongoDumpInstance() - So(err, ShouldBeNil) + require.NoError(t, md.Init(), "should initialize mongodump") + require.NoError(t, md.Dump(), "should succeed dumping a nonexistent database") - dumpDir := filepath.FromSlash(filepath.Join(path, "dump")) - dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, testDB)) - So(os.RemoveAll(dumpDir), ShouldBeNil) + assert.False(t, fileDirExists(dumpDir), "should not create the dump directory") + assert.False(t, fileDirExists(dumpDBDir), "should not create the database directory") + assert.False( + t, + fileDirExists(filepath.Join(dumpDir, "prelude.json")), + "should not create a prelude file in the dump directory", + ) + assert.False( + t, + fileDirExists(filepath.Join(dumpDBDir, "prelude.json")), + "should not create a prelude file in the database directory", + ) + }, + ) +} - Convey("writes prelude.json to dump directory", func() { - testPreludeMetadata(md, dumpDBDir, serverVersion) - }) +// setUpPreludeMetadataSubtest inserts fresh test data for one +// TestDumpPreludeMetadataJson subtest and registers its teardown. +func setUpPreludeMetadataSubtest(t *testing.T) { + t.Helper() - Reset(func() { - So(os.RemoveAll(dumpDir), ShouldBeNil) - }) - }) + require.NoError(t, setUpMongoDumpTestData(t), "should set up test data") + t.Cleanup(func() { + assert.NoError(t, tearDownMongoDumpTestDataInCleanup(), "should tear down test data") + }) +} - Convey("when the dump directory is not created", func() { +// newMongoDumpForPreludeSubtest builds a MongoDump instance and reports the +// connected server version. dbName, when non-empty, restricts the dump to +// that database; empty clears both the database and collection so the whole +// server gets dumped. +func newMongoDumpForPreludeSubtest(t *testing.T, dbName string) (*MongoDump, string) { + t.Helper() - dumpDir := filepath.FromSlash(filepath.Join(path, "dump")) - dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, "nottestdb")) + sessionProvider, _, _ := testutil.GetBareSessionProvider() + require.NotNil(t, sessionProvider, "should get a session provider") + serverVersion, err := sessionProvider.ServerVersion() + require.NoError(t, err, "should get the server version") - Convey("the dump does not fail and prelude.json should not be created", func() { - md, err := simpleMongoDumpInstance() - So(err, ShouldBeNil) + md, err := simpleMongoDumpInstance() + require.NoError(t, err, "should build a mongodump instance") - md.ToolOptions.DB = "nonExistentDB" + if dbName != "" { + md.ToolOptions.DB = dbName + } else { + md.ToolOptions.DB = "" + md.ToolOptions.Collection = "" + } - err = md.Init() - So(err, ShouldBeNil) - err = md.Dump() - So(err, ShouldBeNil) + return md, serverVersion +} - So(fileDirExists(dumpDir), ShouldBeFalse) - So(fileDirExists(dumpDBDir), ShouldBeFalse) - So(fileDirExists(filepath.Join(dumpDir, "prelude.json")), ShouldBeFalse) - So(fileDirExists(filepath.Join(dumpDBDir, "prelude.json")), ShouldBeFalse) - }) +// preludeSubtestDumpDir removes any pre-existing dump directory named name +// under the working directory and returns its absolute path, matching the +// per-subtest cleanup GoConvey's Reset performed for each dump location. +func preludeSubtestDumpDir(t *testing.T, name string) string { + t.Helper() - Reset(func() { - So(os.RemoveAll(dumpDir), ShouldBeNil) - }) - }) + path, err := os.Getwd() + require.NoError(t, err, "should get the working directory") - Reset(func() { - So(tearDownMongoDumpTestData(t), ShouldBeNil) - }) + dumpDir := filepath.FromSlash(filepath.Join(path, name)) + require.NoError(t, os.RemoveAll(dumpDir), "should remove any pre-existing dump directory") + t.Cleanup(func() { + assert.NoError(t, os.RemoveAll(dumpDir), "should remove the dump directory") }) + + return dumpDir } func TestMongoDumpMetaData(t *testing.T) { testtype.SkipUnlessTestType(t, testtype.IntegrationTestType) log.SetWriter(io.Discard) - Convey("With a MongoDump instance", t, func() { - session, err := testutil.GetBareSession() - So(session, ShouldNotBeNil) - So(err, ShouldBeNil) - - err = setUpMongoDumpTestData(t) - So(err, ShouldBeNil) - - Convey("testing that the dumped directory contains information about indexes", func() { + session, err := testutil.GetBareSession() + require.NotNil(t, session, "should get a session") + require.NoError(t, err, "should connect to the server") - md, err := simpleMongoDumpInstance() - So(err, ShouldBeNil) + require.NoError(t, setUpMongoDumpTestData(t), "should set up test data") + t.Cleanup(func() { + assert.NoError(t, tearDownMongoDumpTestDataInCleanup(), "should tear down test data") + }) - md.OutputOptions.Out = "dump" - err = md.Init() - So(err, ShouldBeNil) + // testing that the dumped directory contains information about indexes + md, err := simpleMongoDumpInstance() + require.NoError(t, err, "should build a mongodump instance") - err = md.Dump() - So(err, ShouldBeNil) + md.OutputOptions.Out = "dump" + require.NoError(t, md.Init(), "should initialize mongodump") + require.NoError(t, md.Dump(), "should dump the database") - path, err := os.Getwd() - So(err, ShouldBeNil) - dumpDir := filepath.FromSlash(filepath.Join(path, "dump")) - dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, testDB)) - So(fileDirExists(dumpDir), ShouldBeTrue) - So(fileDirExists(dumpDBDir), ShouldBeTrue) + path, err := os.Getwd() + require.NoError(t, err, "should get the working directory") + dumpDir := filepath.FromSlash(filepath.Join(path, "dump")) + dumpDBDir := filepath.FromSlash(filepath.Join(dumpDir, testDB)) + require.True(t, fileDirExists(dumpDir), "should create the dump directory") + require.True(t, fileDirExists(dumpDBDir), "should create the database directory") + t.Cleanup(func() { + assert.NoError(t, os.RemoveAll(dumpDir), "should remove the dump directory") + }) - Convey("having one metadata file per collection", func() { - c1, err := countNonIndexBSONFiles(dumpDBDir) - So(err, ShouldBeNil) + // having one metadata file per collection + c1, err := countNonIndexBSONFiles(dumpDBDir) + require.NoError(t, err, "should count the dumped bson files") - c2, err := countMetaDataFiles(dumpDBDir) - So(err, ShouldBeNil) + c2, err := countMetaDataFiles(dumpDBDir) + require.NoError(t, err, "should count the dumped metadata files") - So(c1, ShouldEqual, c2) + require.EqualValues(t, c2, c1, "should write one metadata file per collection") - Convey("and that the JSON in a metadata file is valid", func() { - metaFiles, err := getMatchingFiles(dumpDBDir, ".*\\.metadata\\.json") - So(err, ShouldBeNil) - So(len(metaFiles), ShouldBeGreaterThan, 0) + // and that the JSON in a metadata file is valid + metaFiles, err := getMatchingFiles(dumpDBDir, ".*\\.metadata\\.json") + require.NoError(t, err, "should list the metadata files") + require.Greater(t, len(metaFiles), 0, "should find at least one metadata file") - oneMetaFile, err := os.Open( - filepath.FromSlash(filepath.Join(dumpDBDir, metaFiles[0])), - ) - defer oneMetaFile.Close() - So(err, ShouldBeNil) - contents, err := io.ReadAll(oneMetaFile) - So(err, ShouldBeNil) - var jsonResult map[string]any - err = json.Unmarshal(contents, &jsonResult) - So(err, ShouldBeNil) - - Convey("and contains an 'indexes' key", func() { - _, ok := jsonResult["indexes"] - So(ok, ShouldBeTrue) - }) - - Convey("and contains a 'collectionName' key", func() { - _, ok := jsonResult["collectionName"] - So(ok, ShouldBeTrue) - }) - - fcv := testutil.GetFCV(session) - cmp, err := testutil.CompareFCV(fcv, "3.6") - So(err, ShouldBeNil) - if cmp >= 0 { - Convey("and on FCV 3.6+, contains a 'uuid' key", func() { - uuid, ok := jsonResult["uuid"] - So(ok, ShouldBeTrue) - checkUUID := regexp.MustCompile(`(?i)^[a-z0-9]{32}$`) - - uuidStr, ok := uuid.(string) - So(ok, ShouldBeTrue) - - So(checkUUID.MatchString(uuidStr), ShouldBeTrue) - // XXX useless -- xdg, 2018-09-21 - So(err, ShouldBeNil) - }) - } - - }) + oneMetaFile, err := os.Open(filepath.FromSlash(filepath.Join(dumpDBDir, metaFiles[0]))) + require.NoError(t, err, "should open a metadata file") + defer oneMetaFile.Close() - }) + contents, err := io.ReadAll(oneMetaFile) + require.NoError(t, err, "should read the metadata file") + var jsonResult map[string]any + require.NoError(t, json.Unmarshal(contents, &jsonResult), "should unmarshal the metadata json") - Reset(func() { - So(os.RemoveAll(dumpDir), ShouldBeNil) - }) - }) + // and contains an 'indexes' key + _, ok := jsonResult["indexes"] + assert.True(t, ok, "should include an indexes key in the metadata") - Reset(func() { - So(tearDownMongoDumpTestData(t), ShouldBeNil) - }) + // and contains a 'collectionName' key + _, ok = jsonResult["collectionName"] + assert.True(t, ok, "should include a collectionName key in the metadata") - }) + fcv := testutil.GetFCV(session) + cmp, err := testutil.CompareFCV(fcv, "3.6") + require.NoError(t, err, "should compare the server's FCV") + if cmp >= 0 { + // and on FCV 3.6+, contains a 'uuid' key + uuid, ok := jsonResult["uuid"] + require.True(t, ok, "should include a uuid key in the metadata on FCV 3.6+") + checkUUID := regexp.MustCompile(`(?i)^[a-z0-9]{32}$`) + + uuidStr, ok := uuid.(string) + require.True(t, ok, "should represent the uuid as a string") + + assert.True( + t, + checkUUID.MatchString(uuidStr), + "should format the uuid as 32 hex characters", + ) + // XXX useless -- xdg, 2018-09-21 + assert.NoError(t, err, "should compare the server's FCV") + } } func TestMongoDumpOplog(t *testing.T) {