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)