From 83cb61c98ab2e53c5977195ed6630fcbca280d62 Mon Sep 17 00:00:00 2001 From: Matt Dale <9760375+matthewdale@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:51:26 -0700 Subject: [PATCH 1/3] Move fail point structs to an internal package. (#1833) --- internal/failpoint/failpoint.go | 63 +++++++++++++++++++ internal/integration/change_stream_test.go | 19 +++--- internal/integration/client_test.go | 7 ++- internal/integration/crud_prose_test.go | 33 +++++----- internal/integration/csot_prose_test.go | 25 ++++---- internal/integration/csot_test.go | 25 ++++---- internal/integration/cursor_test.go | 13 ++-- internal/integration/database_test.go | 9 +-- internal/integration/errors_test.go | 21 ++++--- internal/integration/index_view_test.go | 13 ++-- internal/integration/mtest/global_state.go | 3 +- internal/integration/mtest/mongotest.go | 47 ++------------ internal/integration/primary_stepdown_test.go | 7 ++- .../integration/retryable_reads_prose_test.go | 13 ++-- .../retryable_writes_prose_test.go | 27 ++++---- .../integration/retryable_writes_spec_test.go | 13 ++-- .../integration/sdam_error_handling_test.go | 39 ++++++------ internal/integration/sdam_prose_test.go | 13 ++-- .../server_selection_prose_test.go | 7 ++- internal/integration/unified_spec_test.go | 3 +- 20 files changed, 222 insertions(+), 178 deletions(-) create mode 100644 internal/failpoint/failpoint.go diff --git a/internal/failpoint/failpoint.go b/internal/failpoint/failpoint.go new file mode 100644 index 0000000000..9fe25ba63f --- /dev/null +++ b/internal/failpoint/failpoint.go @@ -0,0 +1,63 @@ +// Copyright (C) MongoDB, Inc. 2024-present. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + +package failpoint + +import ( + "go.mongodb.org/mongo-driver/v2/bson" +) + +const ( + // ModeAlwaysOn is the fail point mode that enables the fail point for an + // indefinite number of matching commands. + ModeAlwaysOn = "alwaysOn" + + // ModeOff is the fail point mode that disables the fail point. + ModeOff = "off" +) + +// FailPoint is used to configure a server fail point. It is intended to be +// passed as the command argument to RunCommand. +// +// For more information about fail points, see +// https://github.com/mongodb/specifications/tree/HEAD/source/transactions/tests#server-fail-point +type FailPoint struct { + ConfigureFailPoint string `bson:"configureFailPoint"` + // Mode should be a string, FailPointMode, or map[string]interface{} + Mode interface{} `bson:"mode"` + Data Data `bson:"data"` +} + +// Mode configures when a fail point will be enabled. It is used to set the +// FailPoint.Mode field. +type Mode struct { + Times int32 `bson:"times"` + Skip int32 `bson:"skip"` +} + +// Data configures how a fail point will behave. It is used to set the +// FailPoint.Data field. +type Data struct { + FailCommands []string `bson:"failCommands,omitempty"` + CloseConnection bool `bson:"closeConnection,omitempty"` + ErrorCode int32 `bson:"errorCode,omitempty"` + FailBeforeCommitExceptionCode int32 `bson:"failBeforeCommitExceptionCode,omitempty"` + ErrorLabels *[]string `bson:"errorLabels,omitempty"` + WriteConcernError *WriteConcernError `bson:"writeConcernError,omitempty"` + BlockConnection bool `bson:"blockConnection,omitempty"` + BlockTimeMS int32 `bson:"blockTimeMS,omitempty"` + AppName string `bson:"appName,omitempty"` +} + +// WriteConcernError is the write concern error to return when the fail point is +// triggered. It is used to set the FailPoint.Data.WriteConcernError field. +type WriteConcernError struct { + Code int32 `bson:"code"` + Name string `bson:"codeName"` + Errmsg string `bson:"errmsg"` + ErrorLabels *[]string `bson:"errorLabels,omitempty"` + ErrInfo bson.Raw `bson:"errInfo,omitempty"` +} diff --git a/internal/integration/change_stream_test.go b/internal/integration/change_stream_test.go index 1738b67650..4bacc746bd 100644 --- a/internal/integration/change_stream_test.go +++ b/internal/integration/change_stream_test.go @@ -17,6 +17,7 @@ import ( "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/eventtest" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/mongo" @@ -523,12 +524,12 @@ func TestChangeStream_ReplicaSet(t *testing.T) { SetReadConcern(mtest.MajorityRc). SetRetryReads(false)) - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"aggregate"}, CloseConnection: true, }, @@ -546,12 +547,12 @@ func TestChangeStream_ReplicaSet(t *testing.T) { SetReadConcern(mtest.MajorityRc). SetRetryReads(false)) - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"getMore"}, CloseConnection: true, }, @@ -574,12 +575,12 @@ func TestChangeStream_ReplicaSet(t *testing.T) { SetPoolMonitor(tpm.PoolMonitor). SetRetryReads(true)) - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 2, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"aggregate"}, CloseConnection: true, }, diff --git a/internal/integration/client_test.go b/internal/integration/client_test.go index 98d8752e00..7b6298cd79 100644 --- a/internal/integration/client_test.go +++ b/internal/integration/client_test.go @@ -20,6 +20,7 @@ import ( "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/eventtest" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/handshake" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/integtest" @@ -678,10 +679,10 @@ func TestClient(t *testing.T) { _, err := mt.Coll.InsertOne(context.Background(), bson.D{}) require.NoError(mt, err) - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: "alwaysOn", - Data: mtest.FailPointData{ + Mode: failpoint.ModeAlwaysOn, + Data: failpoint.Data{ FailCommands: []string{"find", "insert"}, BlockConnection: true, BlockTimeMS: 500, diff --git a/internal/integration/crud_prose_test.go b/internal/integration/crud_prose_test.go index 8233763807..2072d6db67 100644 --- a/internal/integration/crud_prose_test.go +++ b/internal/integration/crud_prose_test.go @@ -14,6 +14,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/assert" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" @@ -29,14 +30,14 @@ func TestWriteErrorsWithLabels(t *testing.T) { label := "ExampleError" mt.Run("InsertMany errors with label", func(mt *mtest.T) { - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, - WriteConcernError: &mtest.WriteConcernErrorData{ + WriteConcernError: &failpoint.WriteConcernError{ Code: 100, ErrorLabels: &[]string{label}, }, @@ -60,14 +61,14 @@ func TestWriteErrorsWithLabels(t *testing.T) { }) mt.Run("WriteException with label", func(mt *mtest.T) { - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"delete"}, - WriteConcernError: &mtest.WriteConcernErrorData{ + WriteConcernError: &failpoint.WriteConcernError{ Code: 100, ErrorLabels: &[]string{label}, }, @@ -83,14 +84,14 @@ func TestWriteErrorsWithLabels(t *testing.T) { }) mt.Run("BulkWriteException with label", func(mt *mtest.T) { - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"delete"}, - WriteConcernError: &mtest.WriteConcernErrorData{ + WriteConcernError: &failpoint.WriteConcernError{ Code: 100, ErrorLabels: &[]string{label}, }, @@ -333,14 +334,14 @@ func TestWriteConcernError(t *testing.T) { errInfoDoc := bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendDocumentElement(nil, "writeConcern", wcDoc), ) - fp := mtest.FailPoint{ + fp := failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, - WriteConcernError: &mtest.WriteConcernErrorData{ + WriteConcernError: &failpoint.WriteConcernError{ Code: 100, Name: "UnsatisfiableWriteConcern", Errmsg: "Not enough data-bearing nodes", diff --git a/internal/integration/csot_prose_test.go b/internal/integration/csot_prose_test.go index 3923efd6e7..e8df78acbe 100644 --- a/internal/integration/csot_prose_test.go +++ b/internal/integration/csot_prose_test.go @@ -16,6 +16,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/assert" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/integtest" "go.mongodb.org/mongo-driver/v2/internal/mongoutil" @@ -35,12 +36,12 @@ func TestCSOTProse(t *testing.T) { assert.Nil(mt, err, "Drop error: %v", err) // Configure a fail point to block both inserts of the multi-write for 1010ms (2020ms total). - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 2, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, BlockConnection: true, BlockTimeMS: 1010, @@ -191,12 +192,12 @@ func TestCSOTProse_GridFS(t *testing.T) { SetHosts([]string{failpointHost})) // Set a blocking "insert" fail point. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, BlockConnection: true, BlockTimeMS: 1250, @@ -251,12 +252,12 @@ func TestCSOTProse_GridFS(t *testing.T) { SetHosts([]string{failpointHost})) // Set a blocking "delete" fail point. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"delete"}, BlockConnection: true, BlockTimeMS: 1250, @@ -367,12 +368,12 @@ func TestCSOTProse_GridFS(t *testing.T) { SetHosts([]string{failpointHost})) // Set a blocking "insert" fail point. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, BlockConnection: true, BlockTimeMS: 200, diff --git a/internal/integration/csot_test.go b/internal/integration/csot_test.go index 3112ba8be0..674ec071a0 100644 --- a/internal/integration/csot_test.go +++ b/internal/integration/csot_test.go @@ -16,6 +16,7 @@ import ( "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/eventtest" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/mongo" @@ -351,10 +352,10 @@ func TestCSOT_maxTimeMS(t *testing.T) { require.NoError(mt, err) } - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: "alwaysOn", - Data: mtest.FailPointData{ + Mode: failpoint.ModeAlwaysOn, + Data: failpoint.Data{ FailCommands: []string{tc.commandName}, BlockConnection: true, // Note that some operations (currently Find and @@ -424,12 +425,12 @@ func TestCSOT_errors(t *testing.T) { _, err := mt.Coll.InsertOne(context.Background(), bson.D{}) require.NoError(mt, err, "InsertOne error") - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"find"}, ErrorCode: 50, // MaxTimeMSExceeded }, @@ -454,12 +455,12 @@ func TestCSOT_errors(t *testing.T) { _, err := mt.Coll.InsertOne(context.Background(), bson.D{}) require.NoError(mt, err, "InsertOne error") - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"find"}, BlockConnection: true, BlockTimeMS: 500, @@ -488,12 +489,12 @@ func TestCSOT_errors(t *testing.T) { _, err := mt.Coll.InsertOne(context.Background(), bson.D{}) require.NoError(mt, err, "InsertOne error") - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"find"}, BlockConnection: true, BlockTimeMS: 100, diff --git a/internal/integration/cursor_test.go b/internal/integration/cursor_test.go index 6c77e7cc6a..5ee9986ec2 100644 --- a/internal/integration/cursor_test.go +++ b/internal/integration/cursor_test.go @@ -15,6 +15,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/assert" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" @@ -180,13 +181,13 @@ func TestCursor(t *testing.T) { mt.RunOpts("all", noClientOpts, func(mt *mtest.T) { failpointOpts := mtest.NewOptions().Topologies(mtest.ReplicaSet).MinServerVersion("4.0") mt.RunOpts("getMore error", failpointOpts, func(mt *mtest.T) { - failpointData := mtest.FailPointData{ + failpointData := failpoint.Data{ FailCommands: []string{"getMore"}, ErrorCode: 100, } - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: "alwaysOn", + Mode: failpoint.ModeAlwaysOn, Data: failpointData, }) initCollection(mt, mt.Coll) @@ -252,13 +253,13 @@ func TestCursor(t *testing.T) { mt.RunOpts("close", noClientOpts, func(mt *mtest.T) { failpointOpts := mtest.NewOptions().Topologies(mtest.ReplicaSet).MinServerVersion("4.0") mt.RunOpts("killCursors error", failpointOpts, func(mt *mtest.T) { - failpointData := mtest.FailPointData{ + failpointData := failpoint.Data{ FailCommands: []string{"killCursors"}, ErrorCode: 100, } - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: "alwaysOn", + Mode: failpoint.ModeAlwaysOn, Data: failpointData, }) initCollection(mt, mt.Coll) diff --git a/internal/integration/database_test.go b/internal/integration/database_test.go index 89d9d87f9f..c661b3b49b 100644 --- a/internal/integration/database_test.go +++ b/internal/integration/database_test.go @@ -16,6 +16,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/assert" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/handshake" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/mongo" @@ -90,14 +91,14 @@ func TestDatabase(t *testing.T) { }) failpointOpts := mtest.NewOptions().MinServerVersion("4.0").Topologies(mtest.ReplicaSet) mt.RunOpts("gets result and error", failpointOpts, func(mt *mtest.T) { - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, - WriteConcernError: &mtest.WriteConcernErrorData{ + WriteConcernError: &failpoint.WriteConcernError{ Code: 100, }, }, diff --git a/internal/integration/errors_test.go b/internal/integration/errors_test.go index cfc4715512..a1f232bd50 100644 --- a/internal/integration/errors_test.go +++ b/internal/integration/errors_test.go @@ -21,6 +21,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/assert" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/integtest" "go.mongodb.org/mongo-driver/v2/mongo" @@ -72,12 +73,12 @@ func TestErrors(t *testing.T) { authOpts := mtest.NewOptions().Auth(true).Topologies(mtest.ReplicaSet, mtest.Single).MinServerVersion("4.0") mt.RunOpts("network error during auth", authOpts, func(mt *mtest.T) { - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ // Set the fail point for saslContinue rather than saslStart because the driver will use speculative // auth on 4.4+ so there won't be an explicit saslStart command. FailCommands: []string{"saslContinue"}, @@ -316,12 +317,12 @@ func TestErrors(t *testing.T) { mt.RunOpts("Raw response", mtOpts, func(mt *mtest.T) { mt.Run("CommandError", func(mt *mtest.T) { // Mock a CommandError via failpoint with an arbitrary code. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"find"}, ErrorCode: 123, }, @@ -364,14 +365,14 @@ func TestErrors(t *testing.T) { }) mt.Run("WriteException", func(mt *mtest.T) { // Mock a WriteException via failpoint with an arbitrary WriteConcernError. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"delete"}, - WriteConcernError: &mtest.WriteConcernErrorData{ + WriteConcernError: &failpoint.WriteConcernError{ Code: 123, }, }, diff --git a/internal/integration/index_view_test.go b/internal/integration/index_view_test.go index c6af75ceaa..62625c777a 100644 --- a/internal/integration/index_view_test.go +++ b/internal/integration/index_view_test.go @@ -13,6 +13,7 @@ import ( "github.com/google/go-cmp/cmp" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/assert" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" @@ -249,10 +250,10 @@ func TestIndexView(t *testing.T) { }) // Needs to run on these versions for failpoints mt.RunOpts("replace error", mtest.NewOptions().Topologies(mtest.ReplicaSet).MinServerVersion("4.0"), func(mt *mtest.T) { - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: "alwaysOn", - Data: mtest.FailPointData{ + Mode: failpoint.ModeAlwaysOn, + Data: failpoint.Data{ FailCommands: []string{"createIndexes"}, ErrorCode: 100, }, @@ -395,10 +396,10 @@ func TestIndexView(t *testing.T) { }) // Needs to run on these versions for failpoints mt.RunOpts("replace error", mtest.NewOptions().Topologies(mtest.ReplicaSet).MinServerVersion("4.0"), func(mt *mtest.T) { - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: "alwaysOn", - Data: mtest.FailPointData{ + Mode: failpoint.ModeAlwaysOn, + Data: failpoint.Data{ FailCommands: []string{"createIndexes"}, ErrorCode: 100, }, diff --git a/internal/integration/mtest/global_state.go b/internal/integration/mtest/global_state.go index d9f7bf916e..414112bd3f 100644 --- a/internal/integration/mtest/global_state.go +++ b/internal/integration/mtest/global_state.go @@ -11,6 +11,7 @@ import ( "fmt" "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/connstring" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/topology" @@ -76,7 +77,7 @@ func ServerVersion() string { } // SetFailPoint configures the provided fail point on the cluster under test using the provided Client. -func SetFailPoint(fp FailPoint, client *mongo.Client) error { +func SetFailPoint(fp failpoint.FailPoint, client *mongo.Client) error { admin := client.Database("admin") if err := admin.RunCommand(context.Background(), fp).Err(); err != nil { return fmt.Errorf("error creating fail point: %w", err) diff --git a/internal/integration/mtest/mongotest.go b/internal/integration/mtest/mongotest.go index fb744f0b30..65a15a60c7 100644 --- a/internal/integration/mtest/mongotest.go +++ b/internal/integration/mtest/mongotest.go @@ -19,6 +19,7 @@ import ( "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/csfle" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/mongo" @@ -47,44 +48,6 @@ const ( namespaceExistsErrCode int32 = 48 ) -// FailPoint is a representation of a server fail point. -// See https://github.com/mongodb/specifications/tree/HEAD/source/transactions/tests#server-fail-point -// for more information regarding fail points. -type FailPoint struct { - ConfigureFailPoint string `bson:"configureFailPoint"` - // Mode should be a string, FailPointMode, or map[string]interface{} - Mode interface{} `bson:"mode"` - Data FailPointData `bson:"data"` -} - -// FailPointMode is a representation of the Failpoint.Mode field. -type FailPointMode struct { - Times int32 `bson:"times"` - Skip int32 `bson:"skip"` -} - -// FailPointData is a representation of the FailPoint.Data field. -type FailPointData struct { - FailCommands []string `bson:"failCommands,omitempty"` - CloseConnection bool `bson:"closeConnection,omitempty"` - ErrorCode int32 `bson:"errorCode,omitempty"` - FailBeforeCommitExceptionCode int32 `bson:"failBeforeCommitExceptionCode,omitempty"` - ErrorLabels *[]string `bson:"errorLabels,omitempty"` - WriteConcernError *WriteConcernErrorData `bson:"writeConcernError,omitempty"` - BlockConnection bool `bson:"blockConnection,omitempty"` - BlockTimeMS int32 `bson:"blockTimeMS,omitempty"` - AppName string `bson:"appName,omitempty"` -} - -// WriteConcernErrorData is a representation of the FailPoint.Data.WriteConcern field. -type WriteConcernErrorData struct { - Code int32 `bson:"code"` - Name string `bson:"codeName"` - Errmsg string `bson:"errmsg"` - ErrorLabels *[]string `bson:"errorLabels,omitempty"` - ErrInfo bson.Raw `bson:"errInfo,omitempty"` -} - // T is a wrapper around testing.T. type T struct { // connsCheckedOut is the net number of connections checked out during test execution. @@ -536,7 +499,7 @@ func (t *T) ClearCollections() { // SetFailPoint sets a fail point for the client associated with T. Commands to create the failpoint will appear // in command monitoring channels. The fail point will automatically be disabled after this test has run. -func (t *T) SetFailPoint(fp FailPoint) { +func (t *T) SetFailPoint(fp failpoint.FailPoint) { // ensure mode fields are int32 if modeMap, ok := fp.Mode.(map[string]interface{}); ok { var key string @@ -585,9 +548,9 @@ func (t *T) TrackFailPoint(fpName string) { func (t *T) ClearFailPoints() { db := t.Client.Database("admin") for _, fp := range t.failPointNames { - cmd := bson.D{ - {"configureFailPoint", fp}, - {"mode", "off"}, + cmd := failpoint.FailPoint{ + ConfigureFailPoint: fp, + Mode: failpoint.ModeOff, } err := db.RunCommand(context.Background(), cmd).Err() if err != nil { diff --git a/internal/integration/primary_stepdown_test.go b/internal/integration/primary_stepdown_test.go index e308532b8e..ac7f7b4f21 100644 --- a/internal/integration/primary_stepdown_test.go +++ b/internal/integration/primary_stepdown_test.go @@ -13,6 +13,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/eventtest" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" @@ -81,12 +82,12 @@ func TestConnectionsSurvivePrimaryStepDown(t *testing.T) { // failpoints that cause SDAM state changes. SetHeartbeatInterval(defaultHeartbeatInterval)) - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, ErrorCode: tc.errCode, }, diff --git a/internal/integration/retryable_reads_prose_test.go b/internal/integration/retryable_reads_prose_test.go index 8bffd69ea8..3b2b50f2c8 100644 --- a/internal/integration/retryable_reads_prose_test.go +++ b/internal/integration/retryable_reads_prose_test.go @@ -16,6 +16,7 @@ import ( "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/eventtest" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/internal/require" @@ -46,12 +47,12 @@ func TestRetryableReadsProse(t *testing.T) { assert.Nil(mt, err, "InsertOne error: %v", err) // Force Find to block for 1 second once. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"find"}, ErrorCode: 91, BlockConnection: true, @@ -144,12 +145,12 @@ func TestRetryableReadsProse(t *testing.T) { "test cluster must have at least %v mongos hosts", tc.hostCount) // Configure the failpoint options for each mongos. - failPoint := mtest.FailPoint{ + failPoint := failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"find"}, ErrorCode: tc.failpointErrorCode, CloseConnection: false, diff --git a/internal/integration/retryable_writes_prose_test.go b/internal/integration/retryable_writes_prose_test.go index 355a4858d3..64f070ff7c 100644 --- a/internal/integration/retryable_writes_prose_test.go +++ b/internal/integration/retryable_writes_prose_test.go @@ -18,6 +18,7 @@ import ( "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/eventtest" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/internal/require" @@ -157,12 +158,12 @@ func TestRetryableWritesProse(t *testing.T) { Topologies(mtest.ReplicaSet, mtest.Sharded) mt.RunOpts("PoolClearedError retryability", mtPceOpts, func(mt *mtest.T) { // Force Find to block for 1 second once. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, ErrorCode: 91, BlockConnection: true, @@ -227,11 +228,11 @@ func TestRetryableWritesProse(t *testing.T) { mt.ResetClient(options.Client().SetRetryWrites(true).SetMonitor(monitor)) // Configure a fail point for a "ShutdownInProgress" error. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{Times: 1}, - Data: mtest.FailPointData{ - WriteConcernError: &mtest.WriteConcernErrorData{ + Mode: failpoint.Mode{Times: 1}, + Data: failpoint.Data{ + WriteConcernError: &failpoint.WriteConcernError{ Code: shutdownInProgressErrorCode, }, FailCommands: []string{"insert"}, @@ -261,10 +262,10 @@ func TestRetryableWritesProse(t *testing.T) { return } - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{Times: 1}, - Data: mtest.FailPointData{ + Mode: failpoint.Mode{Times: 1}, + Data: failpoint.Data{ ErrorCode: notWritablePrimaryErrorCode, ErrorLabels: &[]string{ driver.NoWritesPerformed, @@ -324,12 +325,12 @@ func TestRetryableWritesProse(t *testing.T) { "test cluster must have at least %v mongos hosts", tc.hostCount) // Configure the failpoint options for each mongos. - failPoint := mtest.FailPoint{ + failPoint := failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, ErrorLabels: &[]string{"RetryableWriteError"}, ErrorCode: tc.failpointErrorCode, diff --git a/internal/integration/retryable_writes_spec_test.go b/internal/integration/retryable_writes_spec_test.go index 324c508621..d4cc783554 100644 --- a/internal/integration/retryable_writes_spec_test.go +++ b/internal/integration/retryable_writes_spec_test.go @@ -14,6 +14,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/assert" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" ) @@ -26,12 +27,12 @@ type retryableWritesTestFile struct { } type retryableWritesTest struct { - Description string `bson:"description"` - ClientOptions bson.Raw `bson:"clientOptions"` - UseMultipleMongoses bool `bson:"useMultipleMongoses"` - FailPoint *mtest.FailPoint `bson:"failPoint"` - Operation crudOperation `bson:"operation"` - Outcome crudOutcome `bson:"outcome"` + Description string `bson:"description"` + ClientOptions bson.Raw `bson:"clientOptions"` + UseMultipleMongoses bool `bson:"useMultipleMongoses"` + FailPoint *failpoint.FailPoint `bson:"failPoint"` + Operation crudOperation `bson:"operation"` + Outcome crudOutcome `bson:"outcome"` } func TestRetryableWritesSpec(t *testing.T) { diff --git a/internal/integration/sdam_error_handling_test.go b/internal/integration/sdam_error_handling_test.go index ee6485cff1..ceaa5a59f8 100644 --- a/internal/integration/sdam_error_handling_test.go +++ b/internal/integration/sdam_error_handling_test.go @@ -19,6 +19,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/eventtest" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" @@ -57,12 +58,12 @@ func TestSDAMErrorHandling(t *testing.T) { appName := "authConnectTimeoutTest" // Set failpoint on saslContinue instead of saslStart because saslStart isn't done when using // speculative auth. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"saslContinue"}, BlockConnection: true, BlockTimeMS: 150, @@ -101,12 +102,12 @@ func TestSDAMErrorHandling(t *testing.T) { // routine encounters a non-timeout network error during handshaking. appName := "authNetworkErrorTestBackground" - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"saslContinue"}, CloseConnection: true, AppName: appName, @@ -134,12 +135,12 @@ func TestSDAMErrorHandling(t *testing.T) { // checkout encounters a non-timeout network error during handshaking. appName := "authNetworkErrorTestForeground" - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"saslContinue"}, CloseConnection: true, AppName: appName, @@ -169,12 +170,12 @@ func TestSDAMErrorHandling(t *testing.T) { mt.Run("pool cleared on non-timeout network error", func(mt *mtest.T) { appName := "afterHandshakeNetworkError" - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, CloseConnection: true, AppName: appName, @@ -268,12 +269,12 @@ func TestSDAMErrorHandling(t *testing.T) { appName := fmt.Sprintf("command_error_%s", tc.name) // Cause the next insert to fail with an ok:0 response. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, ErrorCode: tc.errorCode, AppName: appName, @@ -290,14 +291,14 @@ func TestSDAMErrorHandling(t *testing.T) { appName := fmt.Sprintf("write_concern_error_%s", tc.name) // Cause the next insert to fail with a write concern error. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"insert"}, - WriteConcernError: &mtest.WriteConcernErrorData{ + WriteConcernError: &failpoint.WriteConcernError{ Code: tc.errorCode, }, AppName: appName, diff --git a/internal/integration/sdam_prose_test.go b/internal/integration/sdam_prose_test.go index 776c63dd6e..74e5d809fe 100644 --- a/internal/integration/sdam_prose_test.go +++ b/internal/integration/sdam_prose_test.go @@ -19,6 +19,7 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/assert" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/handshake" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/mongoutil" @@ -118,12 +119,12 @@ func TestSDAMProse(t *testing.T) { } // Force hello requests to block for 500ms and wait until a server's average RTT goes over 250ms. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 1000, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{handshake.LegacyHello, "hello"}, BlockConnection: true, BlockTimeMS: 500, @@ -152,12 +153,12 @@ func TestSDAMProse(t *testing.T) { mt.RunOpts("client waits between failed Hellos", mtest.NewOptions().MinServerVersion("4.9").Topologies(mtest.Single), func(mt *mtest.T) { // Force hello requests to fail 5 times. - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 5, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{handshake.LegacyHello, "hello"}, ErrorCode: 1234, AppName: "SDAMMinHeartbeatFrequencyTest", diff --git a/internal/integration/server_selection_prose_test.go b/internal/integration/server_selection_prose_test.go index 746d4bf477..05641dbf75 100644 --- a/internal/integration/server_selection_prose_test.go +++ b/internal/integration/server_selection_prose_test.go @@ -16,6 +16,7 @@ import ( "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/eventtest" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/internal/require" @@ -126,12 +127,12 @@ func TestServerSelectionProse(t *testing.T) { failpointHost := hosts[0] mt.ResetClient(options.Client(). SetHosts([]string{failpointHost})) - mt.SetFailPoint(mtest.FailPoint{ + mt.SetFailPoint(failpoint.FailPoint{ ConfigureFailPoint: "failCommand", - Mode: mtest.FailPointMode{ + Mode: failpoint.Mode{ Times: 10000, }, - Data: mtest.FailPointData{ + Data: failpoint.Data{ FailCommands: []string{"find"}, BlockConnection: true, BlockTimeMS: 500, diff --git a/internal/integration/unified_spec_test.go b/internal/integration/unified_spec_test.go index fcee24a989..20668b9578 100644 --- a/internal/integration/unified_spec_test.go +++ b/internal/integration/unified_spec_test.go @@ -24,6 +24,7 @@ import ( "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/bsonutil" + "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/integtest" "go.mongodb.org/mongo-driver/v2/internal/mongoutil" @@ -472,7 +473,7 @@ func executeTestRunnerOperation(mt *mtest.T, testCase *testCase, op *operation, case "targetedFailPoint": fpDoc := op.Arguments.Lookup("failPoint") - var fp mtest.FailPoint + var fp failpoint.FailPoint if err := bson.Unmarshal(fpDoc.Document(), &fp); err != nil { return fmt.Errorf("Unmarshal error: %w", err) } From 1224ee009a6c6090668fe3dbe719f3324db627bd Mon Sep 17 00:00:00 2001 From: Matt Dale <9760375+matthewdale@users.noreply.github.com> Date: Fri, 27 Sep 2024 19:08:46 -0700 Subject: [PATCH 2/3] GODRIVER-3313 Skip CSOT spec tests on Windows and macOS. (#1818) [master] (#1835) --- .evergreen/config.yml | 14 +++++++++++++- .evergreen/run-tests.sh | 1 + internal/integration/csot_prose_test.go | 8 ++++++++ internal/integration/csot_test.go | 15 +++++++++++++++ .../integration/unified/unified_spec_runner.go | 8 ++++++++ 5 files changed, 45 insertions(+), 1 deletion(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 9673acf294..31098e633e 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -299,7 +299,7 @@ functions: params: shell: "bash" working_dir: src/go.mongodb.org/mongo-driver - include_expansions_in_env: ["TOPOLOGY", "AUTH", "SSL", "MONGODB_URI", "CRYPT_SHARED_LIB_PATH", "SKIP_CRYPT_SHARED_LIB", "RACE", "MONGO_GO_DRIVER_COMPRESSOR", "REQUIRE_API_VERSION", "LOAD_BALANCER"] + include_expansions_in_env: ["TOPOLOGY", "AUTH", "SSL", "SKIP_CSOT_TESTS", "MONGODB_URI", "CRYPT_SHARED_LIB_PATH", "SKIP_CRYPT_SHARED_LIB", "RACE", "MONGO_GO_DRIVER_COMPRESSOR", "REQUIRE_API_VERSION", "LOAD_BALANCER"] script: | ${PREPARE_SHELL} bash ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh @@ -2005,6 +2005,8 @@ axes: GCC_PATH: "/cygdrive/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw64/bin" GO_DIST: "C:\\golang\\go1.22" VENV_BIN_DIR: "Scripts" + # CSOT tests are unreliable on our slow Windows hosts. + SKIP_CSOT_TESTS: true - id: "rhel87-64" display_name: "RHEL 8.7" run_on: rhel8.7-large @@ -2016,6 +2018,8 @@ axes: batchtime: 1440 # Run at most once per 24 hours. variables: GO_DIST: "/opt/golang/go1.22" + # CSOT tests are unreliable on our slow macOS hosts. + SKIP_CSOT_TESTS: true # OSes that require >= 4.0 for SSL - id: os-ssl-40 @@ -2029,6 +2033,8 @@ axes: GCC_PATH: "/cygdrive/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw64/bin" GO_DIST: "C:\\golang\\go1.22" VENV_BIN_DIR: "Scripts" + # CSOT tests are unreliable on our slow Windows hosts. + SKIP_CSOT_TESTS: true - id: "rhel87-64" display_name: "RHEL 8.7" run_on: rhel8.7-large @@ -2040,6 +2046,8 @@ axes: batchtime: 1440 # Run at most once per 24 hours. variables: GO_DIST: "/opt/golang/go1.22" + # CSOT tests are unreliable on our slow macOS hosts. + SKIP_CSOT_TESTS: true - id: ocsp-rhel-87 display_name: OS @@ -2061,6 +2069,8 @@ axes: GCC_PATH: "/cygdrive/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw64/bin" GO_DIST: "C:\\golang\\go1.22" SKIP_ECS_AUTH_TEST: true + # CSOT tests are unreliable on our slow Windows hosts. + SKIP_CSOT_TESTS: true - id: "ubuntu2004-64" display_name: "Ubuntu 20.04" run_on: ubuntu2004-test @@ -2075,6 +2085,8 @@ axes: SKIP_ECS_AUTH_TEST: true SKIP_EC2_AUTH_TEST: true SKIP_WEB_IDENTITY_AUTH_TEST: true + # CSOT tests are unreliable on our slow macOS hosts. + SKIP_CSOT_TESTS: true - id: os-faas-80 display_name: OS diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 63d37af2a5..617b6e1d4c 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -87,6 +87,7 @@ fi AUTH=${AUTH} \ SSL=${SSL} \ +SKIP_CSOT_TESTS=${SKIP_CSOT_TESTS} \ MONGO_GO_DRIVER_CA_FILE=${MONGO_GO_DRIVER_CA_FILE} \ MONGO_GO_DRIVER_KEY_FILE=${MONGO_GO_DRIVER_KEY_FILE} \ MONGO_GO_DRIVER_PKCS8_ENCRYPTED_KEY_FILE=${MONGO_GO_DRIVER_PKCS8_ENCRYPTED_KEY_FILE} \ diff --git a/internal/integration/csot_prose_test.go b/internal/integration/csot_prose_test.go index e8df78acbe..960891ee5c 100644 --- a/internal/integration/csot_prose_test.go +++ b/internal/integration/csot_prose_test.go @@ -9,6 +9,7 @@ package integration import ( "bytes" "context" + "os" "strings" "testing" "time" @@ -26,6 +27,13 @@ import ( ) func TestCSOTProse(t *testing.T) { + // Skip CSOT tests when SKIP_CSOT_TESTS=true. In Evergreen, we typically set + // that environment variable on Windows and macOS because the CSOT spec + // tests are unreliable on those hosts. + if os.Getenv("SKIP_CSOT_TESTS") == "true" { + t.Skip("Skipping CSOT test because SKIP_CSOT_TESTS=true") + } + mt := mtest.New(t, mtest.NewOptions().CreateClient(false)) mt.RunOpts("1. multi-batch writes", mtest.NewOptions().MinServerVersion("4.4"). diff --git a/internal/integration/csot_test.go b/internal/integration/csot_test.go index 674ec071a0..6808efb2a4 100644 --- a/internal/integration/csot_test.go +++ b/internal/integration/csot_test.go @@ -9,6 +9,7 @@ package integration import ( "context" "errors" + "os" "testing" "time" @@ -27,6 +28,13 @@ import ( // Test automatic "maxTimeMS" appending and connection closing behavior. func TestCSOT_maxTimeMS(t *testing.T) { + // Skip CSOT tests when SKIP_CSOT_TESTS=true. In Evergreen, we typically set + // that environment variable on Windows and macOS because the CSOT spec + // tests are unreliable on those hosts. + if os.Getenv("SKIP_CSOT_TESTS") == "true" { + t.Skip("Skipping CSOT test because SKIP_CSOT_TESTS=true") + } + mt := mtest.New(t, mtest.NewOptions().CreateClient(false)) testCases := []struct { @@ -410,6 +418,13 @@ func TestCSOT_maxTimeMS(t *testing.T) { } func TestCSOT_errors(t *testing.T) { + // Skip CSOT tests when SKIP_CSOT_TESTS=true. In Evergreen, we typically set + // that environment variable on Windows and macOS because the CSOT spec + // tests are unreliable on those hosts. + if os.Getenv("SKIP_CSOT_TESTS") == "true" { + t.Skip("Skipping CSOT test because SKIP_CSOT_TESTS=true") + } + mt := mtest.New(t, mtest.NewOptions(). CreateClient(false). // Blocking failpoints don't work on pre-4.2 and sharded clusters. diff --git a/internal/integration/unified/unified_spec_runner.go b/internal/integration/unified/unified_spec_runner.go index 23605e8991..3066502b60 100644 --- a/internal/integration/unified/unified_spec_runner.go +++ b/internal/integration/unified/unified_spec_runner.go @@ -10,6 +10,7 @@ import ( "context" "fmt" "io/ioutil" + "os" "path" "strings" "testing" @@ -170,6 +171,13 @@ func runTestFile(t *testing.T, filepath string, expectValidFail bool, opts ...*O CreateClient(false) mt.RunOpts(testCase.Description, mtOpts, func(mt *mtest.T) { + // Skip CSOT spec tests when SKIP_CSOT_TESTS=true. In Evergreen, we + // typically set that environment variable on Windows and macOS + // because the CSOT spec tests are unreliable on those hosts. + if os.Getenv("SKIP_CSOT_TESTS") == "true" && strings.Contains(filepath, "client-side-operations-timeout") { + mt.Skip("Skipping CSOT spec test because SKIP_CSOT_TESTS=true") + } + defer func() { // catch panics from looking up elements and fail if it's unexpected if r := recover(); r != nil { From 4fa9e22e5aac6d87e7d235dee44451e75da809a5 Mon Sep 17 00:00:00 2001 From: Matt Dale <9760375+matthewdale@users.noreply.github.com> Date: Fri, 27 Sep 2024 19:10:31 -0700 Subject: [PATCH 3/3] Move and reformat errors helper unit tests. (#1834) --- internal/integration/errors_test.go | 442 -------------------- mongo/errors_test.go | 626 ++++++++++++++++++++++++++++ 2 files changed, 626 insertions(+), 442 deletions(-) diff --git a/internal/integration/errors_test.go b/internal/integration/errors_test.go index a1f232bd50..a33b8df93a 100644 --- a/internal/integration/errors_test.go +++ b/internal/integration/errors_test.go @@ -12,9 +12,7 @@ package integration import ( "context" "errors" - "fmt" "io" - "net" "regexp" "testing" "time" @@ -26,28 +24,8 @@ import ( "go.mongodb.org/mongo-driver/v2/internal/integtest" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" - "go.mongodb.org/mongo-driver/v2/x/mongo/driver" - "go.mongodb.org/mongo-driver/v2/x/mongo/driver/topology" ) -type netErr struct { - timeout bool -} - -func (n netErr) Error() string { - return "error" -} - -func (n netErr) Timeout() bool { - return n.timeout -} - -func (n netErr) Temporary() bool { - return false -} - -var _ net.Error = (*netErr)(nil) - func containsPattern(patterns []string, str string) bool { for _, pattern := range patterns { re := regexp.MustCompile(pattern) @@ -128,191 +106,6 @@ func TestErrors(t *testing.T) { }) }) mt.Run("ServerError", func(mt *mtest.T) { - matchWrapped := errors.New("wrapped err") - otherWrapped := errors.New("other err") - const matchCode = 100 - const otherCode = 120 - const label = "testError" - testCases := []struct { - name string - err mongo.ServerError - hasCode bool - hasLabel bool - hasMessage bool - hasCodeWithMessage bool - isResult bool - }{ - { - "CommandError all true", - mongo.CommandError{matchCode, "foo", []string{label}, "name", matchWrapped, nil}, - true, - true, - true, - true, - true, - }, - { - "CommandError all false", - mongo.CommandError{otherCode, "bar", []string{"otherError"}, "name", otherWrapped, nil}, - false, - false, - false, - false, - false, - }, - { - "CommandError has code not message", - mongo.CommandError{matchCode, "bar", []string{}, "name", nil, nil}, - true, - false, - false, - false, - false, - }, - { - "WriteException all in writeConcernError", - mongo.WriteException{ - &mongo.WriteConcernError{"name", matchCode, "foo", nil, nil}, - nil, - []string{label}, - nil, - }, - true, - true, - true, - true, - false, - }, - { - "WriteException all in writeError", - mongo.WriteException{ - nil, - mongo.WriteErrors{ - mongo.WriteError{0, otherCode, "bar", nil, nil}, - mongo.WriteError{0, matchCode, "foo", nil, nil}, - }, - []string{"otherError"}, - nil, - }, - true, - false, - true, - true, - false, - }, - { - "WriteException all false", - mongo.WriteException{ - &mongo.WriteConcernError{"name", otherCode, "bar", nil, nil}, - mongo.WriteErrors{ - mongo.WriteError{0, otherCode, "baz", nil, nil}, - }, - []string{"otherError"}, - nil, - }, - false, - false, - false, - false, - false, - }, - { - "WriteException HasErrorCodeAndMessage false", - mongo.WriteException{ - &mongo.WriteConcernError{"name", matchCode, "bar", nil, nil}, - mongo.WriteErrors{ - mongo.WriteError{0, otherCode, "foo", nil, nil}, - }, - []string{"otherError"}, - nil, - }, - true, - false, - true, - false, - false, - }, - { - "BulkWriteException all in writeConcernError", - mongo.BulkWriteException{ - &mongo.WriteConcernError{"name", matchCode, "foo", nil, nil}, - nil, - []string{label}, - }, - true, - true, - true, - true, - false, - }, - { - "BulkWriteException all in writeError", - mongo.BulkWriteException{ - nil, - []mongo.BulkWriteError{ - {mongo.WriteError{0, matchCode, "foo", nil, nil}, &mongo.InsertOneModel{}}, - {mongo.WriteError{0, otherCode, "bar", nil, nil}, &mongo.InsertOneModel{}}, - }, - []string{"otherError"}, - }, - true, - false, - true, - true, - false, - }, - { - "BulkWriteException all false", - mongo.BulkWriteException{ - &mongo.WriteConcernError{"name", otherCode, "bar", nil, nil}, - []mongo.BulkWriteError{ - {mongo.WriteError{0, otherCode, "baz", nil, nil}, &mongo.InsertOneModel{}}, - }, - []string{"otherError"}, - }, - false, - false, - false, - false, - false, - }, - { - "BulkWriteException HasErrorCodeAndMessage false", - mongo.BulkWriteException{ - &mongo.WriteConcernError{"name", matchCode, "bar", nil, nil}, - []mongo.BulkWriteError{ - {mongo.WriteError{0, otherCode, "foo", nil, nil}, &mongo.InsertOneModel{}}, - }, - []string{"otherError"}, - }, - true, - false, - true, - false, - false, - }, - } - for _, tc := range testCases { - mt.Run(tc.name, func(mt *mtest.T) { - has := tc.err.HasErrorCode(matchCode) - assert.Equal(mt, has, tc.hasCode, "expected HasErrorCode to return %v, got %v", tc.hasCode, has) - has = tc.err.HasErrorLabel(label) - assert.Equal(mt, has, tc.hasLabel, "expected HasErrorLabel to return %v, got %v", tc.hasLabel, has) - - // Check for full message and substring - has = tc.err.HasErrorMessage("foo") - assert.Equal(mt, has, tc.hasMessage, "expected HasErrorMessage to return %v, got %v", tc.hasMessage, has) - has = tc.err.HasErrorMessage("fo") - assert.Equal(mt, has, tc.hasMessage, "expected HasErrorMessage to return %v, got %v", tc.hasMessage, has) - has = tc.err.HasErrorCodeWithMessage(matchCode, "foo") - assert.Equal(mt, has, tc.hasCodeWithMessage, "expected HasErrorCodeWithMessage to return %v, got %v", tc.hasCodeWithMessage, has) - has = tc.err.HasErrorCodeWithMessage(matchCode, "fo") - assert.Equal(mt, has, tc.hasCodeWithMessage, "expected HasErrorCodeWithMessage to return %v, got %v", tc.hasCodeWithMessage, has) - - assert.Equal(mt, errors.Is(tc.err, matchWrapped), tc.isResult, "expected errors.Is result to be %v", tc.isResult) - }) - } - mtOpts := mtest.NewOptions().MinServerVersion("4.0").Topologies(mtest.ReplicaSet) mt.RunOpts("Raw response", mtOpts, func(mt *mtest.T) { mt.Run("CommandError", func(mt *mtest.T) { @@ -393,239 +186,4 @@ func TestErrors(t *testing.T) { }) }) }) - mt.Run("error helpers", func(mt *mtest.T) { - // IsDuplicateKeyError - mt.Run("IsDuplicateKeyError", func(mt *mtest.T) { - testCases := []struct { - name string - err error - result bool - }{ - {"CommandError true", mongo.CommandError{11000, "", nil, "blah", nil, nil}, true}, - {"CommandError false", mongo.CommandError{100, "", nil, "blah", nil, nil}, false}, - {"WriteError true", mongo.WriteError{0, 11000, "", nil, nil}, true}, - {"WriteError false", mongo.WriteError{0, 100, "", nil, nil}, false}, - { - "WriteException true in writeConcernError", - mongo.WriteException{ - &mongo.WriteConcernError{"name", 11001, "bar", nil, nil}, - mongo.WriteErrors{ - mongo.WriteError{0, 100, "baz", nil, nil}, - }, - nil, - nil, - }, - true, - }, - { - "WriteException true in writeErrors", - mongo.WriteException{ - &mongo.WriteConcernError{"name", 100, "bar", nil, nil}, - mongo.WriteErrors{ - mongo.WriteError{0, 12582, "baz", nil, nil}, - }, - nil, - nil, - }, - true, - }, - { - "WriteException false", - mongo.WriteException{ - &mongo.WriteConcernError{"name", 16460, "bar", nil, nil}, - mongo.WriteErrors{ - mongo.WriteError{0, 100, "blah E11000 blah", nil, nil}, - }, - nil, - nil, - }, - false, - }, - { - "BulkWriteException true", - mongo.BulkWriteException{ - &mongo.WriteConcernError{"name", 100, "bar", nil, nil}, - []mongo.BulkWriteError{ - {mongo.WriteError{0, 16460, "blah E11000 blah", nil, nil}, &mongo.InsertOneModel{}}, - }, - []string{"otherError"}, - }, - true, - }, - { - "BulkWriteException false", - mongo.BulkWriteException{ - &mongo.WriteConcernError{"name", 100, "bar", nil, nil}, - []mongo.BulkWriteError{ - {mongo.WriteError{0, 110, "blah", nil, nil}, &mongo.InsertOneModel{}}, - }, - []string{"otherError"}, - }, - false, - }, - {"wrapped error", fmt.Errorf("%w", mongo.CommandError{11000, "", nil, "blah", nil, nil}), true}, - {"other error type", errors.New("foo"), false}, - } - for _, tc := range testCases { - mt.Run(tc.name, func(mt *mtest.T) { - res := mongo.IsDuplicateKeyError(tc.err) - assert.Equal(mt, res, tc.result, "expected IsDuplicateKeyError %v, got %v", tc.result, res) - }) - } - }) - // IsNetworkError - mt.Run("IsNetworkError", func(mt *mtest.T) { - const networkLabel = "NetworkError" - const otherLabel = "other" - testCases := []struct { - name string - err error - result bool - }{ - {"ServerError true", mongo.CommandError{100, "", []string{networkLabel}, "blah", nil, nil}, true}, - {"ServerError false", mongo.CommandError{100, "", []string{otherLabel}, "blah", nil, nil}, false}, - {"wrapped error", fmt.Errorf("%w", mongo.CommandError{100, "", []string{networkLabel}, "blah", nil, nil}), true}, - {"other error type", errors.New("foo"), false}, - } - for _, tc := range testCases { - mt.Run(tc.name, func(mt *mtest.T) { - res := mongo.IsNetworkError(tc.err) - assert.Equal(mt, res, tc.result, "expected IsNetworkError %v, got %v", tc.result, res) - }) - } - }) - // IsTimeout - mt.Run("IsTimeout", func(mt *mtest.T) { - testCases := []struct { - name string - err error - result bool - }{ - { - name: "context timeout", - err: mongo.CommandError{ - Code: 100, - Message: "", - Labels: []string{"other"}, - Name: "blah", - Wrapped: context.DeadlineExceeded, - Raw: nil, - }, - result: true, - }, - { - name: "deadline would be exceeded", - err: mongo.CommandError{ - Code: 100, - Message: "", - Labels: []string{"other"}, - Name: "blah", - Wrapped: driver.ErrDeadlineWouldBeExceeded, - Raw: nil, - }, - result: true, - }, - { - name: "server selection timeout", - err: mongo.CommandError{ - Code: 100, - Message: "", - Labels: []string{"other"}, - Name: "blah", - Wrapped: context.DeadlineExceeded, - Raw: nil, - }, - result: true, - }, - { - name: "wait queue timeout", - err: mongo.CommandError{ - Code: 100, - Message: "", - Labels: []string{"other"}, - Name: "blah", - Wrapped: topology.WaitQueueTimeoutError{}, - Raw: nil, - }, - result: true, - }, - { - name: "ServerError NetworkTimeoutError", - err: mongo.CommandError{ - Code: 100, - Message: "", - Labels: []string{"NetworkTimeoutError"}, - Name: "blah", - Wrapped: nil, - Raw: nil, - }, - result: true, - }, - { - name: "ServerError ExceededTimeLimitError", - err: mongo.CommandError{ - Code: 100, - Message: "", - Labels: []string{"ExceededTimeLimitError"}, - Name: "blah", - Wrapped: nil, - Raw: nil, - }, - result: true, - }, - { - name: "ServerError false", - err: mongo.CommandError{ - Code: 100, - Message: "", - Labels: []string{"other"}, - Name: "blah", - Wrapped: nil, - Raw: nil, - }, - result: false, - }, - { - name: "net error true", - err: mongo.CommandError{ - Code: 100, - Message: "", - Labels: []string{"other"}, - Name: "blah", - Wrapped: netErr{true}, - Raw: nil, - }, - result: true, - }, - { - name: "net error false", - err: netErr{false}, - result: false, - }, - { - name: "wrapped error", - err: fmt.Errorf("%w", mongo.CommandError{ - Code: 100, - Message: "", - Labels: []string{"other"}, - Name: "blah", - Wrapped: context.DeadlineExceeded, - Raw: nil, - }), - result: true, - }, - { - name: "other error", - err: errors.New("foo"), - result: false, - }, - } - for _, tc := range testCases { - mt.Run(tc.name, func(mt *mtest.T) { - res := mongo.IsTimeout(tc.err) - assert.Equal(mt, res, tc.result, "expected IsTimeout %v, got %v", tc.result, res) - }) - } - }) - }) } diff --git a/mongo/errors_test.go b/mongo/errors_test.go index eabcab8afb..c39d409ac5 100644 --- a/mongo/errors_test.go +++ b/mongo/errors_test.go @@ -7,11 +7,17 @@ package mongo import ( + "context" + "errors" + "fmt" + "net" "testing" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/require" + "go.mongodb.org/mongo-driver/v2/x/mongo/driver" + "go.mongodb.org/mongo-driver/v2/x/mongo/driver/topology" ) func TestErrorMessages(t *testing.T) { @@ -70,3 +76,623 @@ func TestErrorMessages(t *testing.T) { }) } } + +func TestServerError(t *testing.T) { + matchWrapped := errors.New("wrapped err") + otherWrapped := errors.New("other err") + const matchCode = 100 + const otherCode = 120 + const label = "testError" + + testCases := []struct { + name string + err ServerError + hasCode bool + hasLabel bool + hasMessage bool + hasCodeWithMessage bool + isResult bool + }{ + { + name: "CommandError all true", + err: CommandError{ + Code: matchCode, + Message: "foo", + Labels: []string{label}, + Name: "name", + Wrapped: matchWrapped, + }, + hasCode: true, + hasLabel: true, + hasMessage: true, + hasCodeWithMessage: true, + isResult: true, + }, + { + name: "CommandError all false", + err: CommandError{ + Code: otherCode, + Message: "bar", + Labels: []string{"otherError"}, + Name: "name", + Wrapped: otherWrapped, + }, + hasCode: false, + hasLabel: false, + hasMessage: false, + hasCodeWithMessage: false, + isResult: false, + }, + { + name: "CommandError has code not message", + err: CommandError{ + Code: matchCode, + Message: "bar", + Labels: []string{}, + Name: "name", + }, + hasCode: true, + hasLabel: false, + hasMessage: false, + hasCodeWithMessage: false, + isResult: false, + }, + { + name: "WriteException all in writeConcernError", + err: WriteException{ + WriteConcernError: &WriteConcernError{ + Name: "name", + Code: matchCode, + Message: "foo", + }, + Labels: []string{label}, + }, + hasCode: true, + hasLabel: true, + hasMessage: true, + hasCodeWithMessage: true, + isResult: false, + }, + { + name: "WriteException all in writeError", + err: WriteException{ + WriteErrors: WriteErrors{ + WriteError{ + Index: 0, + Code: otherCode, + Message: "bar", + }, + WriteError{ + Index: 0, + Code: matchCode, + Message: "foo", + }, + }, + Labels: []string{"otherError"}, + }, + hasCode: true, + hasLabel: false, + hasMessage: true, + hasCodeWithMessage: true, + isResult: false, + }, + { + name: "WriteException all false", + err: WriteException{ + WriteConcernError: &WriteConcernError{ + Name: "name", + Code: otherCode, + Message: "bar", + }, + WriteErrors: WriteErrors{ + WriteError{ + Index: 0, + Code: otherCode, + Message: "baz", + }, + }, + Labels: []string{"otherError"}, + }, + hasCode: false, + hasLabel: false, + hasMessage: false, + hasCodeWithMessage: false, + isResult: false, + }, + { + name: "WriteException HasErrorCodeAndMessage false", + err: WriteException{ + WriteConcernError: &WriteConcernError{ + Name: "name", + Code: matchCode, + Message: "bar", + }, + WriteErrors: WriteErrors{ + WriteError{ + Index: 0, + Code: otherCode, + Message: "foo", + }, + }, + Labels: []string{"otherError"}, + }, + hasCode: true, + hasLabel: false, + hasMessage: true, + hasCodeWithMessage: false, + isResult: false, + }, + { + name: "BulkWriteException all in writeConcernError", + err: BulkWriteException{ + WriteConcernError: &WriteConcernError{ + Name: "name", + Code: matchCode, + Message: "foo", + }, + Labels: []string{label}, + }, + hasCode: true, + hasLabel: true, + hasMessage: true, + hasCodeWithMessage: true, + isResult: false, + }, + { + name: "BulkWriteException all in writeError", + err: BulkWriteException{ + WriteErrors: []BulkWriteError{ + { + WriteError: WriteError{ + Index: 0, + Code: matchCode, + Message: "foo", + }, + Request: &InsertOneModel{}, + }, + { + WriteError: WriteError{ + Index: 0, + Code: otherCode, + Message: "bar", + }, + Request: &InsertOneModel{}, + }, + }, + Labels: []string{"otherError"}, + }, + hasCode: true, + hasLabel: false, + hasMessage: true, + hasCodeWithMessage: true, + isResult: false, + }, + { + name: "BulkWriteException all false", + err: BulkWriteException{ + WriteConcernError: &WriteConcernError{ + Name: "name", + Code: otherCode, + Message: "bar", + }, + WriteErrors: []BulkWriteError{ + { + WriteError: WriteError{ + Index: 0, + Code: otherCode, + Message: "baz", + }, + Request: &InsertOneModel{}, + }, + }, + Labels: []string{"otherError"}, + }, + hasCode: false, + hasLabel: false, + hasMessage: false, + hasCodeWithMessage: false, + isResult: false, + }, + { + name: "BulkWriteException HasErrorCodeAndMessage false", + err: BulkWriteException{ + WriteConcernError: &WriteConcernError{ + Name: "name", + Code: matchCode, + Message: "bar", + }, + WriteErrors: []BulkWriteError{ + { + WriteError: WriteError{ + Index: 0, + Code: otherCode, + Message: "foo", + }, + Request: &InsertOneModel{}, + }, + }, + Labels: []string{"otherError"}, + }, + hasCode: true, + hasLabel: false, + hasMessage: true, + hasCodeWithMessage: false, + isResult: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + has := tc.err.HasErrorCode(matchCode) + assert.Equal(t, has, tc.hasCode, "expected HasErrorCode to return %v, got %v", tc.hasCode, has) + has = tc.err.HasErrorLabel(label) + assert.Equal(t, has, tc.hasLabel, "expected HasErrorLabel to return %v, got %v", tc.hasLabel, has) + + // Check for full message and substring + has = tc.err.HasErrorMessage("foo") + assert.Equal(t, has, tc.hasMessage, "expected HasErrorMessage to return %v, got %v", tc.hasMessage, has) + has = tc.err.HasErrorMessage("fo") + assert.Equal(t, has, tc.hasMessage, "expected HasErrorMessage to return %v, got %v", tc.hasMessage, has) + has = tc.err.HasErrorCodeWithMessage(matchCode, "foo") + assert.Equal(t, has, tc.hasCodeWithMessage, "expected HasErrorCodeWithMessage to return %v, got %v", tc.hasCodeWithMessage, has) + has = tc.err.HasErrorCodeWithMessage(matchCode, "fo") + assert.Equal(t, has, tc.hasCodeWithMessage, "expected HasErrorCodeWithMessage to return %v, got %v", tc.hasCodeWithMessage, has) + + assert.Equal(t, errors.Is(tc.err, matchWrapped), tc.isResult, "expected errors.Is result to be %v", tc.isResult) + }) + } +} + +func TestIsDuplicateKeyError(t *testing.T) { + testCases := []struct { + name string + err error + result bool + }{ + { + name: "CommandError true", + err: CommandError{ + Code: 11000, + Name: "blah", + }, + result: true, + }, + { + name: "CommandError false", + err: CommandError{ + Code: 100, + Name: "blah", + }, + result: false, + }, + { + name: "WriteError true", + err: WriteError{ + Index: 0, + Code: 11000, + }, + result: true, + }, + { + name: "WriteError false", + err: WriteError{ + Index: 0, + Code: 100, + }, + result: false, + }, + { + name: "WriteException true in writeConcernError", + err: WriteException{ + WriteConcernError: &WriteConcernError{ + Name: "name", + Code: 11001, + Message: "bar", + }, + WriteErrors: WriteErrors{ + WriteError{ + Index: 0, + Code: 100, + Message: "baz", + }, + }, + }, + result: true, + }, + { + name: "WriteException true in writeErrors", + err: WriteException{ + WriteConcernError: &WriteConcernError{ + Name: "name", + Code: 100, + Message: "bar", + }, + WriteErrors: WriteErrors{ + WriteError{ + Index: 0, + Code: 12582, + Message: "baz", + }, + }, + }, + result: true, + }, + { + name: "WriteException false", + err: WriteException{ + WriteConcernError: &WriteConcernError{ + Name: "name", + Code: 16460, + Message: "bar", + }, + WriteErrors: WriteErrors{ + WriteError{ + Index: 0, + Code: 100, + Message: "blah E11000 blah", + }, + }, + }, + result: false, + }, + { + name: "BulkWriteException true", + err: BulkWriteException{ + WriteConcernError: &WriteConcernError{ + Name: "name", + Code: 100, + Message: "bar", + }, + WriteErrors: []BulkWriteError{ + { + WriteError: WriteError{ + Index: 0, + Code: 16460, + Message: "blah E11000 blah", + }, + Request: &InsertOneModel{}, + }, + }, + Labels: []string{"otherError"}, + }, + result: true, + }, + { + name: "BulkWriteException false", + err: BulkWriteException{ + WriteConcernError: &WriteConcernError{ + Name: "name", + Code: 100, + Message: "bar", + }, + WriteErrors: []BulkWriteError{ + { + WriteError: WriteError{ + Index: 0, + Code: 110, + Message: "blah", + }, + Request: &InsertOneModel{}, + }, + }, + Labels: []string{"otherError"}, + }, + result: false, + }, + { + name: "wrapped error", + err: fmt.Errorf("%w", CommandError{Code: 11000, Name: "blah"}), + result: true, + }, + { + name: "other error type", + err: errors.New("foo"), + result: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res := IsDuplicateKeyError(tc.err) + assert.Equal(t, res, tc.result, "expected IsDuplicateKeyError %v, got %v", tc.result, res) + }) + } +} + +func TestIsNetworkError(t *testing.T) { + const networkLabel = "NetworkError" + const otherLabel = "other" + testCases := []struct { + name string + err error + result bool + }{ + { + name: "ServerError true", + err: CommandError{ + Code: 100, + Labels: []string{networkLabel}, + Name: "blah", + }, + result: true, + }, + { + name: "ServerError false", + err: CommandError{ + Code: 100, + Labels: []string{otherLabel}, + Name: "blah", + }, + result: false, + }, + { + name: "wrapped error", + err: fmt.Errorf("%w", CommandError{ + Code: 100, + Labels: []string{networkLabel}, + Name: "blah", + }), + result: true, + }, + { + name: "other error type", + err: errors.New("foo"), + result: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res := IsNetworkError(tc.err) + assert.Equal(t, res, tc.result, "expected IsNetworkError %v, got %v", tc.result, res) + }) + } +} + +func TestIsTimeout(t *testing.T) { + testCases := []struct { + name string + err error + result bool + }{ + { + name: "context timeout", + err: CommandError{ + Code: 100, + Message: "", + Labels: []string{"other"}, + Name: "blah", + Wrapped: context.DeadlineExceeded, + Raw: nil, + }, + result: true, + }, + { + name: "deadline would be exceeded", + err: CommandError{ + Code: 100, + Message: "", + Labels: []string{"other"}, + Name: "blah", + Wrapped: driver.ErrDeadlineWouldBeExceeded, + Raw: nil, + }, + result: true, + }, + { + name: "server selection timeout", + err: CommandError{ + Code: 100, + Message: "", + Labels: []string{"other"}, + Name: "blah", + Wrapped: context.DeadlineExceeded, + Raw: nil, + }, + result: true, + }, + { + name: "wait queue timeout", + err: CommandError{ + Code: 100, + Message: "", + Labels: []string{"other"}, + Name: "blah", + Wrapped: topology.WaitQueueTimeoutError{}, + Raw: nil, + }, + result: true, + }, + { + name: "ServerError NetworkTimeoutError", + err: CommandError{ + Code: 100, + Message: "", + Labels: []string{"NetworkTimeoutError"}, + Name: "blah", + Wrapped: nil, + Raw: nil, + }, + result: true, + }, + { + name: "ServerError ExceededTimeLimitError", + err: CommandError{ + Code: 100, + Message: "", + Labels: []string{"ExceededTimeLimitError"}, + Name: "blah", + Wrapped: nil, + Raw: nil, + }, + result: true, + }, + { + name: "ServerError false", + err: CommandError{ + Code: 100, + Message: "", + Labels: []string{"other"}, + Name: "blah", + Wrapped: nil, + Raw: nil, + }, + result: false, + }, + { + name: "net error true", + err: CommandError{ + Code: 100, + Message: "", + Labels: []string{"other"}, + Name: "blah", + Wrapped: netErr{true}, + Raw: nil, + }, + result: true, + }, + { + name: "net error false", + err: netErr{false}, + result: false, + }, + { + name: "wrapped error", + err: fmt.Errorf("%w", CommandError{ + Code: 100, + Message: "", + Labels: []string{"other"}, + Name: "blah", + Wrapped: context.DeadlineExceeded, + Raw: nil, + }), + result: true, + }, + { + name: "other error", + err: errors.New("foo"), + result: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res := IsTimeout(tc.err) + assert.Equal(t, res, tc.result, "expected IsTimeout %v, got %v", tc.result, res) + }) + } +} + +type netErr struct { + timeout bool +} + +func (n netErr) Error() string { + return "error" +} + +func (n netErr) Timeout() bool { + return n.timeout +} + +func (n netErr) Temporary() bool { + return false +} + +var _ net.Error = (*netErr)(nil)