1
2
3
4
5
6
7
8
9
10 package integration
11
12 import (
13 "context"
14 "errors"
15 "fmt"
16 "io"
17 "net"
18 "testing"
19 "time"
20
21 "go.mongodb.org/mongo-driver/bson"
22 "go.mongodb.org/mongo-driver/internal/assert"
23 "go.mongodb.org/mongo-driver/internal/integtest"
24 "go.mongodb.org/mongo-driver/mongo"
25 "go.mongodb.org/mongo-driver/mongo/integration/mtest"
26 "go.mongodb.org/mongo-driver/mongo/options"
27 "go.mongodb.org/mongo-driver/x/mongo/driver"
28 "go.mongodb.org/mongo-driver/x/mongo/driver/topology"
29 )
30
31 type netErr struct {
32 timeout bool
33 }
34
35 func (n netErr) Error() string {
36 return "error"
37 }
38
39 func (n netErr) Timeout() bool {
40 return n.timeout
41 }
42
43 func (n netErr) Temporary() bool {
44 return false
45 }
46
47 var _ net.Error = (*netErr)(nil)
48
49 func TestErrors(t *testing.T) {
50 mt := mtest.New(t, noClientOpts)
51
52 mt.RunOpts("errors are wrapped", noClientOpts, func(mt *mtest.T) {
53 mt.Run("network error during application operation", func(mt *mtest.T) {
54 ctx, cancel := context.WithCancel(context.Background())
55 cancel()
56
57 err := mt.Client.Ping(ctx, mtest.PrimaryRp)
58 assert.True(mt, errors.Is(err, context.Canceled), "expected error %v, got %v", context.Canceled, err)
59 })
60
61 authOpts := mtest.NewOptions().Auth(true).Topologies(mtest.ReplicaSet, mtest.Single).MinServerVersion("4.0")
62 mt.RunOpts("network error during auth", authOpts, func(mt *mtest.T) {
63 mt.SetFailPoint(mtest.FailPoint{
64 ConfigureFailPoint: "failCommand",
65 Mode: mtest.FailPointMode{
66 Times: 1,
67 },
68 Data: mtest.FailPointData{
69
70
71 FailCommands: []string{"saslContinue"},
72 CloseConnection: true,
73 },
74 })
75
76 clientOpts := options.Client().ApplyURI(mtest.ClusterURI())
77 integtest.AddTestServerAPIVersion(clientOpts)
78 client, err := mongo.Connect(context.Background(), clientOpts)
79 assert.Nil(mt, err, "Connect error: %v", err)
80 defer func() { _ = client.Disconnect(context.Background()) }()
81
82
83 err = client.Ping(context.Background(), mtest.PrimaryRp)
84 assert.True(mt, errors.Is(err, io.EOF), "expected error %v, got %v", io.EOF, err)
85 })
86 })
87
88 mt.RunOpts("network timeouts", noClientOpts, func(mt *mtest.T) {
89 mt.Run("context timeouts return DeadlineExceeded", func(mt *mtest.T) {
90 _, err := mt.Coll.InsertOne(context.Background(), bson.D{{"x", 1}})
91 assert.Nil(mt, err, "InsertOne error: %v", err)
92
93 mt.ClearEvents()
94 filter := bson.M{
95 "$where": "function() { sleep(1000); return false; }",
96 }
97 timeoutCtx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
98 defer cancel()
99 _, err = mt.Coll.Find(timeoutCtx, filter)
100
101 evt := mt.GetStartedEvent()
102 assert.Equal(mt, "find", evt.CommandName, "expected command 'find', got %q", evt.CommandName)
103 assert.True(mt, errors.Is(err, context.DeadlineExceeded),
104 "errors.Is failure: expected error %v to be %v", err, context.DeadlineExceeded)
105 })
106
107 mt.Run("socketTimeoutMS timeouts return network errors", func(mt *mtest.T) {
108 _, err := mt.Coll.InsertOne(context.Background(), bson.D{{"x", 1}})
109 assert.Nil(mt, err, "InsertOne error: %v", err)
110
111
112
113 resetClientOpts := options.Client().
114 SetSocketTimeout(100 * time.Millisecond)
115 mt.ResetClient(resetClientOpts)
116
117 mt.ClearEvents()
118 filter := bson.M{
119 "$where": "function() { sleep(1000); return false; }",
120 }
121 _, err = mt.Coll.Find(context.Background(), filter)
122
123 evt := mt.GetStartedEvent()
124 assert.Equal(mt, "find", evt.CommandName, "expected command 'find', got %q", evt.CommandName)
125
126 assert.False(mt, errors.Is(err, context.DeadlineExceeded),
127 "errors.Is failure: expected error %v to not be %v", err, context.DeadlineExceeded)
128 var netErr net.Error
129 ok := errors.As(err, &netErr)
130 assert.True(mt, ok, "errors.As failure: expected error %v to be a net.Error", err)
131 assert.True(mt, netErr.Timeout(), "expected error %v to be a network timeout", err)
132 })
133 })
134 mt.Run("ServerError", func(mt *mtest.T) {
135 matchWrapped := errors.New("wrapped err")
136 otherWrapped := errors.New("other err")
137 const matchCode = 100
138 const otherCode = 120
139 const label = "testError"
140 testCases := []struct {
141 name string
142 err mongo.ServerError
143 hasCode bool
144 hasLabel bool
145 hasMessage bool
146 hasCodeWithMessage bool
147 isResult bool
148 }{
149 {
150 "CommandError all true",
151 mongo.CommandError{matchCode, "foo", []string{label}, "name", matchWrapped, nil},
152 true,
153 true,
154 true,
155 true,
156 true,
157 },
158 {
159 "CommandError all false",
160 mongo.CommandError{otherCode, "bar", []string{"otherError"}, "name", otherWrapped, nil},
161 false,
162 false,
163 false,
164 false,
165 false,
166 },
167 {
168 "CommandError has code not message",
169 mongo.CommandError{matchCode, "bar", []string{}, "name", nil, nil},
170 true,
171 false,
172 false,
173 false,
174 false,
175 },
176 {
177 "WriteException all in writeConcernError",
178 mongo.WriteException{
179 &mongo.WriteConcernError{"name", matchCode, "foo", nil, nil},
180 nil,
181 []string{label},
182 nil,
183 },
184 true,
185 true,
186 true,
187 true,
188 false,
189 },
190 {
191 "WriteException all in writeError",
192 mongo.WriteException{
193 nil,
194 mongo.WriteErrors{
195 mongo.WriteError{0, otherCode, "bar", nil, nil},
196 mongo.WriteError{0, matchCode, "foo", nil, nil},
197 },
198 []string{"otherError"},
199 nil,
200 },
201 true,
202 false,
203 true,
204 true,
205 false,
206 },
207 {
208 "WriteException all false",
209 mongo.WriteException{
210 &mongo.WriteConcernError{"name", otherCode, "bar", nil, nil},
211 mongo.WriteErrors{
212 mongo.WriteError{0, otherCode, "baz", nil, nil},
213 },
214 []string{"otherError"},
215 nil,
216 },
217 false,
218 false,
219 false,
220 false,
221 false,
222 },
223 {
224 "WriteException HasErrorCodeAndMessage false",
225 mongo.WriteException{
226 &mongo.WriteConcernError{"name", matchCode, "bar", nil, nil},
227 mongo.WriteErrors{
228 mongo.WriteError{0, otherCode, "foo", nil, nil},
229 },
230 []string{"otherError"},
231 nil,
232 },
233 true,
234 false,
235 true,
236 false,
237 false,
238 },
239 {
240 "BulkWriteException all in writeConcernError",
241 mongo.BulkWriteException{
242 &mongo.WriteConcernError{"name", matchCode, "foo", nil, nil},
243 nil,
244 []string{label},
245 },
246 true,
247 true,
248 true,
249 true,
250 false,
251 },
252 {
253 "BulkWriteException all in writeError",
254 mongo.BulkWriteException{
255 nil,
256 []mongo.BulkWriteError{
257 {mongo.WriteError{0, matchCode, "foo", nil, nil}, &mongo.InsertOneModel{}},
258 {mongo.WriteError{0, otherCode, "bar", nil, nil}, &mongo.InsertOneModel{}},
259 },
260 []string{"otherError"},
261 },
262 true,
263 false,
264 true,
265 true,
266 false,
267 },
268 {
269 "BulkWriteException all false",
270 mongo.BulkWriteException{
271 &mongo.WriteConcernError{"name", otherCode, "bar", nil, nil},
272 []mongo.BulkWriteError{
273 {mongo.WriteError{0, otherCode, "baz", nil, nil}, &mongo.InsertOneModel{}},
274 },
275 []string{"otherError"},
276 },
277 false,
278 false,
279 false,
280 false,
281 false,
282 },
283 {
284 "BulkWriteException HasErrorCodeAndMessage false",
285 mongo.BulkWriteException{
286 &mongo.WriteConcernError{"name", matchCode, "bar", nil, nil},
287 []mongo.BulkWriteError{
288 {mongo.WriteError{0, otherCode, "foo", nil, nil}, &mongo.InsertOneModel{}},
289 },
290 []string{"otherError"},
291 },
292 true,
293 false,
294 true,
295 false,
296 false,
297 },
298 }
299 for _, tc := range testCases {
300 mt.Run(tc.name, func(mt *mtest.T) {
301 has := tc.err.HasErrorCode(matchCode)
302 assert.Equal(mt, has, tc.hasCode, "expected HasErrorCode to return %v, got %v", tc.hasCode, has)
303 has = tc.err.HasErrorLabel(label)
304 assert.Equal(mt, has, tc.hasLabel, "expected HasErrorLabel to return %v, got %v", tc.hasLabel, has)
305
306
307 has = tc.err.HasErrorMessage("foo")
308 assert.Equal(mt, has, tc.hasMessage, "expected HasErrorMessage to return %v, got %v", tc.hasMessage, has)
309 has = tc.err.HasErrorMessage("fo")
310 assert.Equal(mt, has, tc.hasMessage, "expected HasErrorMessage to return %v, got %v", tc.hasMessage, has)
311 has = tc.err.HasErrorCodeWithMessage(matchCode, "foo")
312 assert.Equal(mt, has, tc.hasCodeWithMessage, "expected HasErrorCodeWithMessage to return %v, got %v", tc.hasCodeWithMessage, has)
313 has = tc.err.HasErrorCodeWithMessage(matchCode, "fo")
314 assert.Equal(mt, has, tc.hasCodeWithMessage, "expected HasErrorCodeWithMessage to return %v, got %v", tc.hasCodeWithMessage, has)
315
316 assert.Equal(mt, errors.Is(tc.err, matchWrapped), tc.isResult, "expected errors.Is result to be %v", tc.isResult)
317 })
318 }
319
320 mtOpts := mtest.NewOptions().MinServerVersion("4.0").Topologies(mtest.ReplicaSet)
321 mt.RunOpts("Raw response", mtOpts, func(mt *mtest.T) {
322 mt.Run("CommandError", func(mt *mtest.T) {
323
324 mt.SetFailPoint(mtest.FailPoint{
325 ConfigureFailPoint: "failCommand",
326 Mode: mtest.FailPointMode{
327 Times: 1,
328 },
329 Data: mtest.FailPointData{
330 FailCommands: []string{"find"},
331 ErrorCode: 123,
332 },
333 })
334
335 res := mt.Coll.FindOne(context.Background(), bson.D{})
336 assert.NotNil(mt, res.Err(), "expected FindOne error, got nil")
337 ce, ok := res.Err().(mongo.CommandError)
338 assert.True(mt, ok, "expected FindOne error to be CommandError, got %T", res.Err())
339
340
341 assert.NotNil(mt, ce.Raw, "ce.Raw is nil")
342 val, err := ce.Raw.LookupErr("code")
343 assert.Nil(mt, err, "expected 'code' field in ce.Raw, got %v", ce.Raw)
344 code, ok := val.AsInt64OK()
345 assert.True(mt, ok, "expected 'code' to be int64, got %v", val)
346 assert.Equal(mt, code, int64(123), "expected 'code' 123, got %d", code)
347 })
348 mt.Run("WriteError", func(mt *mtest.T) {
349
350 _, err := mt.Coll.InsertOne(context.Background(), bson.D{{"_id", 1}})
351 assert.Nil(mt, err, "InsertOne error: %v", err)
352
353 _, err = mt.Coll.InsertOne(context.Background(), bson.D{{"_id", 1}})
354 assert.NotNil(mt, err, "expected InsertOne error, got nil")
355 we, ok := err.(mongo.WriteException)
356 assert.True(mt, ok, "expected InsertOne error to be WriteException, got %T", err)
357
358 assert.NotNil(mt, we.WriteErrors, "expected we.WriteErrors, got nil")
359 assert.NotNil(mt, we.WriteErrors[0], "expected at least one WriteError")
360
361
362 raw := we.WriteErrors[0].Raw
363 assert.NotNil(mt, raw, "Raw of WriteError is nil")
364 val, err := raw.LookupErr("code")
365 assert.Nil(mt, err, "expected 'code' field in Raw field, got %v", raw)
366 code, ok := val.AsInt64OK()
367 assert.True(mt, ok, "expected 'code' to be int64, got %v", val)
368 assert.Equal(mt, code, int64(11000), "expected 'code' 11000, got %d", code)
369 })
370 mt.Run("WriteException", func(mt *mtest.T) {
371
372 mt.SetFailPoint(mtest.FailPoint{
373 ConfigureFailPoint: "failCommand",
374 Mode: mtest.FailPointMode{
375 Times: 1,
376 },
377 Data: mtest.FailPointData{
378 FailCommands: []string{"delete"},
379 WriteConcernError: &mtest.WriteConcernErrorData{
380 Code: 123,
381 },
382 },
383 })
384
385 _, err := mt.Coll.DeleteMany(context.Background(), bson.D{})
386 assert.NotNil(mt, err, "expected DeleteMany error, got nil")
387 we, ok := err.(mongo.WriteException)
388 assert.True(mt, ok, "expected DeleteMany error to be WriteException, got %T", err)
389
390
391 assert.NotNil(mt, we.Raw, "expected RawResponse, got nil")
392 val, err := we.Raw.LookupErr("writeConcernError", "code")
393 assert.Nil(mt, err, "expected 'code' field in RawResponse, got %v", we.Raw)
394 code, ok := val.AsInt64OK()
395 assert.True(mt, ok, "expected 'code' to be int64, got %v", val)
396 assert.Equal(mt, code, int64(123), "expected 'code' 123, got %d", code)
397 })
398 })
399 })
400 mt.Run("error helpers", func(mt *mtest.T) {
401
402 mt.Run("IsDuplicateKeyError", func(mt *mtest.T) {
403 testCases := []struct {
404 name string
405 err error
406 result bool
407 }{
408 {"CommandError true", mongo.CommandError{11000, "", nil, "blah", nil, nil}, true},
409 {"CommandError false", mongo.CommandError{100, "", nil, "blah", nil, nil}, false},
410 {"WriteError true", mongo.WriteError{0, 11000, "", nil, nil}, true},
411 {"WriteError false", mongo.WriteError{0, 100, "", nil, nil}, false},
412 {
413 "WriteException true in writeConcernError",
414 mongo.WriteException{
415 &mongo.WriteConcernError{"name", 11001, "bar", nil, nil},
416 mongo.WriteErrors{
417 mongo.WriteError{0, 100, "baz", nil, nil},
418 },
419 nil,
420 nil,
421 },
422 true,
423 },
424 {
425 "WriteException true in writeErrors",
426 mongo.WriteException{
427 &mongo.WriteConcernError{"name", 100, "bar", nil, nil},
428 mongo.WriteErrors{
429 mongo.WriteError{0, 12582, "baz", nil, nil},
430 },
431 nil,
432 nil,
433 },
434 true,
435 },
436 {
437 "WriteException false",
438 mongo.WriteException{
439 &mongo.WriteConcernError{"name", 16460, "bar", nil, nil},
440 mongo.WriteErrors{
441 mongo.WriteError{0, 100, "blah E11000 blah", nil, nil},
442 },
443 nil,
444 nil,
445 },
446 false,
447 },
448 {
449 "BulkWriteException true",
450 mongo.BulkWriteException{
451 &mongo.WriteConcernError{"name", 100, "bar", nil, nil},
452 []mongo.BulkWriteError{
453 {mongo.WriteError{0, 16460, "blah E11000 blah", nil, nil}, &mongo.InsertOneModel{}},
454 },
455 []string{"otherError"},
456 },
457 true,
458 },
459 {
460 "BulkWriteException false",
461 mongo.BulkWriteException{
462 &mongo.WriteConcernError{"name", 100, "bar", nil, nil},
463 []mongo.BulkWriteError{
464 {mongo.WriteError{0, 110, "blah", nil, nil}, &mongo.InsertOneModel{}},
465 },
466 []string{"otherError"},
467 },
468 false,
469 },
470 {"wrapped error", fmt.Errorf("%w", mongo.CommandError{11000, "", nil, "blah", nil, nil}), true},
471 {"other error type", errors.New("foo"), false},
472 }
473 for _, tc := range testCases {
474 mt.Run(tc.name, func(mt *mtest.T) {
475 res := mongo.IsDuplicateKeyError(tc.err)
476 assert.Equal(mt, res, tc.result, "expected IsDuplicateKeyError %v, got %v", tc.result, res)
477 })
478 }
479 })
480
481 mt.Run("IsNetworkError", func(mt *mtest.T) {
482 const networkLabel = "NetworkError"
483 const otherLabel = "other"
484 testCases := []struct {
485 name string
486 err error
487 result bool
488 }{
489 {"ServerError true", mongo.CommandError{100, "", []string{networkLabel}, "blah", nil, nil}, true},
490 {"ServerError false", mongo.CommandError{100, "", []string{otherLabel}, "blah", nil, nil}, false},
491 {"wrapped error", fmt.Errorf("%w", mongo.CommandError{100, "", []string{networkLabel}, "blah", nil, nil}), true},
492 {"other error type", errors.New("foo"), false},
493 }
494 for _, tc := range testCases {
495 mt.Run(tc.name, func(mt *mtest.T) {
496 res := mongo.IsNetworkError(tc.err)
497 assert.Equal(mt, res, tc.result, "expected IsNetworkError %v, got %v", tc.result, res)
498 })
499 }
500 })
501
502 mt.Run("IsTimeout", func(mt *mtest.T) {
503 testCases := []struct {
504 name string
505 err error
506 result bool
507 }{
508 {"context timeout", mongo.CommandError{
509 100, "", []string{"other"}, "blah", context.DeadlineExceeded, nil}, true},
510 {"deadline would be exceeded", mongo.CommandError{
511 100, "", []string{"other"}, "blah", driver.ErrDeadlineWouldBeExceeded, nil}, true},
512 {"server selection timeout", mongo.CommandError{
513 100, "", []string{"other"}, "blah", topology.ErrServerSelectionTimeout, nil}, true},
514 {"wait queue timeout", mongo.CommandError{
515 100, "", []string{"other"}, "blah", topology.WaitQueueTimeoutError{}, nil}, true},
516 {"ServerError NetworkTimeoutError", mongo.CommandError{
517 100, "", []string{"NetworkTimeoutError"}, "blah", nil, nil}, true},
518 {"ServerError ExceededTimeLimitError", mongo.CommandError{
519 100, "", []string{"ExceededTimeLimitError"}, "blah", nil, nil}, true},
520 {"ServerError false", mongo.CommandError{
521 100, "", []string{"other"}, "blah", nil, nil}, false},
522 {"net error true", mongo.CommandError{
523 100, "", []string{"other"}, "blah", netErr{true}, nil}, true},
524 {"net error false", netErr{false}, false},
525 {"wrapped error", fmt.Errorf("%w", mongo.CommandError{
526 100, "", []string{"other"}, "blah", context.DeadlineExceeded, nil}), true},
527 {"other error", errors.New("foo"), false},
528 }
529 for _, tc := range testCases {
530 mt.Run(tc.name, func(mt *mtest.T) {
531 res := mongo.IsTimeout(tc.err)
532 assert.Equal(mt, res, tc.result, "expected IsTimeout %v, got %v", tc.result, res)
533 })
534 }
535 })
536 })
537 }
538
View as plain text