Skip to content

Commit e4a8a15

Browse files
author
Divjot Arora
authored
Propagate errors in unified test runner rather than failing (#422)
This commit changes the runOperation function in the unified test runner to return an error rather than failing the test so that errors can be reported with more context at a higher level.
1 parent a09c53c commit e4a8a15

File tree

3 files changed

+109
-60
lines changed

3 files changed

+109
-60
lines changed

mongo/integration/crud_spec_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ func verifyCrudError(mt *mtest.T, outcome crudOutcome, err error) {
9696
if opError == nil {
9797
return
9898
}
99-
verifyError(mt, opError, err)
99+
verificationErr := verifyError(opError, err)
100+
assert.Nil(mt, verificationErr, "error mismatch: %v", verificationErr)
100101
}
101102

102103
// run a CRUD operation and verify errors and outcomes.

mongo/integration/json_helpers_test.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package integration
88

99
import (
10+
"fmt"
1011
"io/ioutil"
1112
"math"
1213
"os"
@@ -404,47 +405,53 @@ func errorFromResult(t testing.TB, result interface{}) *operationError {
404405
}
405406

406407
// verify that an error returned by an operation matches the expected error.
407-
func verifyError(t testing.TB, expected *operationError, actual error) {
408-
t.Helper()
409-
408+
func verifyError(expected *operationError, actual error) error {
410409
// The spec test format doesn't treat ErrNoDocuments or ErrUnacknowledgedWrite as errors, so set actual to nil
411410
// to indicate that no error occurred.
412411
if actual == mongo.ErrNoDocuments || actual == mongo.ErrUnacknowledgedWrite {
413412
actual = nil
414413
}
415414

416415
if expected == nil && actual != nil {
417-
t.Fatalf("did not expect error but got %v", actual)
416+
return fmt.Errorf("did not expect error but got %v", actual)
418417
}
419418
if expected != nil && actual == nil {
420-
t.Fatalf("expected error but got nil")
419+
return fmt.Errorf("expected error but got nil")
421420
}
422421
if expected == nil {
423-
return
422+
return nil
424423
}
425424

426425
// check ErrorContains for all error types
427426
if expected.ErrorContains != nil {
428427
emsg := strings.ToLower(*expected.ErrorContains)
429428
amsg := strings.ToLower(actual.Error())
430-
assert.True(t, strings.Contains(amsg, emsg), "expected error message '%v' to contain '%v'", amsg, emsg)
429+
if !strings.Contains(amsg, emsg) {
430+
return fmt.Errorf("expected error message %q to contain %q", amsg, emsg)
431+
}
431432
}
432433

433434
cerr, ok := actual.(mongo.CommandError)
434435
if !ok {
435-
return
436+
return nil
436437
}
437438

438439
if expected.ErrorCodeName != nil {
439-
assert.Equal(t, *expected.ErrorCodeName, cerr.Name,
440-
"error name mismatch; expected %v, got %v", *expected.ErrorCodeName, cerr.Name)
440+
if *expected.ErrorCodeName != cerr.Name {
441+
return fmt.Errorf("expected error name %v, got %v", *expected.ErrorCodeName, cerr.Name)
442+
}
441443
}
442444
for _, label := range expected.ErrorLabelsContain {
443-
assert.True(t, cerr.HasErrorLabel(label), "expected error %v to contain label %v", actual, label)
445+
if !cerr.HasErrorLabel(label) {
446+
return fmt.Errorf("expected error %v to contain label %q", actual, label)
447+
}
444448
}
445449
for _, label := range expected.ErrorLabelsOmit {
446-
assert.False(t, cerr.HasErrorLabel(label), "expected error %v to not contain label %v", actual, label)
450+
if cerr.HasErrorLabel(label) {
451+
return fmt.Errorf("expected error %v to not contain label %q", actual, label)
452+
}
447453
}
454+
return nil
448455
}
449456

450457
// get the underlying value of i as an int64. returns nil if i is not an int, int32, or int64 type.

mongo/integration/unified_spec_test.go

Lines changed: 88 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
package integration
88

99
import (
10+
"errors"
11+
"fmt"
1012
"io/ioutil"
1113
"path"
1214
"reflect"
@@ -247,8 +249,9 @@ func runSpecTestCase(mt *mtest.T, test *testCase, testFile testFile) {
247249

248250
// run operations
249251
mt.ClearEvents()
250-
for _, op := range test.Operations {
251-
runOperation(mt, test, op, sess0, sess1)
252+
for idx, op := range test.Operations {
253+
err := runOperation(mt, test, op, sess0, sess1)
254+
assert.Nil(mt, err, "error running operation %q at index %d: %v", op.Name, idx, err)
252255
}
253256

254257
// Needs to be done here (in spite of defer) because some tests
@@ -285,7 +288,7 @@ func createBucket(mt *mtest.T, testFile testFile, testCase *testCase) {
285288
assert.Nil(mt, err, "NewBucket error: %v", err)
286289
}
287290

288-
func runOperation(mt *mtest.T, testCase *testCase, op *operation, sess0, sess1 mongo.Session) {
291+
func runOperation(mt *mtest.T, testCase *testCase, op *operation, sess0, sess1 mongo.Session) error {
289292
if op.Name == "count" {
290293
mt.Skip("count has been deprecated")
291294
}
@@ -299,13 +302,12 @@ func runOperation(mt *mtest.T, testCase *testCase, op *operation, sess0, sess1 m
299302
case "session1":
300303
sess = sess1
301304
default:
302-
mt.Fatalf("unrecognized session identifier: %v", sessStr)
305+
return fmt.Errorf("unrecognized session identifier: %v", sessStr)
303306
}
304307
}
305308

306309
if op.Object == "testRunner" {
307-
executeTestRunnerOperation(mt, op, sess)
308-
return
310+
return executeTestRunnerOperation(mt, op, sess)
309311
}
310312

311313
mt.CloneDatabase(createDatabaseOptions(mt, op.DatabaseOptions))
@@ -328,7 +330,7 @@ func runOperation(mt *mtest.T, testCase *testCase, op *operation, sess0, sess1 m
328330
case "client":
329331
err = executeClientOperation(mt, op, sess)
330332
default:
331-
mt.Fatalf("unrecognized operation object: %v", op.Object)
333+
return fmt.Errorf("unrecognized operation object: %v", op.Object)
332334
}
333335

334336
op.opError = errorFromResult(mt, op.Result)
@@ -338,7 +340,7 @@ func runOperation(mt *mtest.T, testCase *testCase, op *operation, sess0, sess1 m
338340
if op.Error && op.Result == nil {
339341
op.opError = &operationError{}
340342
}
341-
verifyError(mt, op.opError, err)
343+
return verifyError(op.opError, err)
342344
}
343345

344346
func executeGridFSOperation(mt *mtest.T, bucket *gridfs.Bucket, op *operation) error {
@@ -358,107 +360,146 @@ func executeGridFSOperation(mt *mtest.T, bucket *gridfs.Bucket, op *operation) e
358360
return nil
359361
}
360362

361-
func executeTestRunnerOperation(mt *mtest.T, op *operation, sess mongo.Session) {
363+
func executeTestRunnerOperation(mt *mtest.T, op *operation, sess mongo.Session) error {
362364
var clientSession *session.Client
363365
if sess != nil {
364366
xsess, ok := sess.(mongo.XSession)
365-
assert.True(mt, ok, "expected %T to implement mongo.XSession", sess)
367+
if !ok {
368+
return fmt.Errorf("expected session type %T to implement mongo.XSession", sess)
369+
}
366370
clientSession = xsess.ClientSession()
367371
}
368372

369373
switch op.Name {
370374
case "targetedFailPoint":
371-
fpDoc, err := op.Arguments.LookupErr("failPoint")
372-
assert.Nil(mt, err, "failPoint not found in arguments")
375+
fpDoc := op.Arguments.Lookup("failPoint")
373376

374377
var fp mtest.FailPoint
375-
err = bson.Unmarshal(fpDoc.Document(), &fp)
376-
assert.Nil(mt, err, "error creating fail point: %v", err)
378+
if err := bson.Unmarshal(fpDoc.Document(), &fp); err != nil {
379+
return fmt.Errorf("Unmarshal error: %v", err)
380+
}
377381

378382
targetHost := clientSession.PinnedServer.Addr.String()
379383
opts := options.Client().ApplyURI(mt.ConnString()).SetHosts([]string{targetHost})
380384
client, err := mongo.Connect(mtest.Background, opts)
381-
assert.Nil(mt, err, "error creating targeted client: %v", err)
385+
if err != nil {
386+
return fmt.Errorf("Connect error for targeted client: %v", err)
387+
}
382388
defer func() { _ = client.Disconnect(mtest.Background) }()
383389

384-
err = client.Database("admin").RunCommand(mtest.Background, fp).Err()
385-
assert.Nil(mt, err, "error setting targeted fail point: %v", err)
390+
if err = client.Database("admin").RunCommand(mtest.Background, fp).Err(); err != nil {
391+
return fmt.Errorf("error setting targeted fail point: %v", err)
392+
}
386393
mt.TrackFailPoint(fp.ConfigureFailPoint)
387394
case "assertSessionPinned":
388-
assert.NotNil(mt, clientSession.PinnedServer, "expected pinned server but got nil")
395+
if clientSession.PinnedServer == nil {
396+
return errors.New("expected pinned server, got nil")
397+
}
389398
case "assertSessionUnpinned":
390-
assert.Nil(mt, clientSession.PinnedServer,
391-
"expected pinned server to be nil but got %v", clientSession.PinnedServer)
399+
// We don't use a combined helper for assertSessionPinned and assertSessionUnpinned because the unpinned
400+
// case provides the pinned server address in the error msg for debugging.
401+
if clientSession.PinnedServer != nil {
402+
return fmt.Errorf("expected pinned server to be nil but got %q", clientSession.PinnedServer.Addr)
403+
}
392404
case "assertSessionDirty":
393-
assert.NotNil(mt, clientSession.Server, "expected server session but got nil")
394-
assert.True(mt, clientSession.Server.Dirty, "expected server session to be marked dirty but was not")
405+
return verifyDirtySessionState(clientSession, true)
395406
case "assertSessionNotDirty":
396-
assert.NotNil(mt, clientSession.Server, "expected server session but got nil")
397-
assert.False(mt, clientSession.Server.Dirty, "expected server session not to be marked dirty but was")
407+
return verifyDirtySessionState(clientSession, false)
398408
case "assertSameLsidOnLastTwoCommands":
399409
first, second := lastTwoIDs(mt)
400-
assert.Equal(mt, first, second, "expected last two lsids to be equal but got %v and %v", first, second)
410+
if !first.Equal(second) {
411+
return fmt.Errorf("expected last two lsids to be equal but got %v and %v", first, second)
412+
}
401413
case "assertDifferentLsidOnLastTwoCommands":
402414
first, second := lastTwoIDs(mt)
403-
assert.NotEqual(mt, first, second, "expected last two lsids to be not equal but got %v and %v", first, second)
415+
if first.Equal(second) {
416+
return fmt.Errorf("expected last two lsids to be not equal but both were %v", first)
417+
}
404418
case "assertCollectionExists":
405-
assertCollectionState(mt, op, true)
419+
return verifyCollectionState(mt, op, true)
406420
case "assertCollectionNotExists":
407-
assertCollectionState(mt, op, false)
421+
return verifyCollectionState(mt, op, false)
408422
case "assertIndexExists":
409-
assertIndexState(mt, op, true)
423+
return verifyIndexState(mt, op, true)
410424
case "assertIndexNotExists":
411-
assertIndexState(mt, op, false)
425+
return verifyIndexState(mt, op, false)
412426
default:
413427
mt.Fatalf("unrecognized testRunner operation %v", op.Name)
414428
}
429+
430+
return nil
431+
}
432+
433+
func verifyDirtySessionState(clientSession *session.Client, expectedDirty bool) error {
434+
if clientSession.Server == nil {
435+
return errors.New("expected valid server session, got nil")
436+
}
437+
if markedDirty := clientSession.Server.Dirty; markedDirty != expectedDirty {
438+
return fmt.Errorf("expected server session to be marked dirty: %v, got %v", expectedDirty, markedDirty)
439+
}
440+
return nil
415441
}
416442

417-
func assertIndexState(mt *mtest.T, op *operation, shouldExist bool) {
443+
func verifyIndexState(mt *mtest.T, op *operation, shouldExist bool) error {
418444
db := op.Arguments.Lookup("database").StringValue()
419445
coll := op.Arguments.Lookup("collection").StringValue()
420446
index := op.Arguments.Lookup("index").StringValue()
421-
exists := indexExists(mt, db, coll, index)
422447

423-
assert.Equal(mt, shouldExist, exists,
424-
"index state mismatch for index %s in namespace %s.%s; should exist: %v, exists: %v", index, db, coll,
425-
shouldExist, exists)
448+
exists, err := indexExists(mt, db, coll, index)
449+
if err != nil {
450+
return err
451+
}
452+
if exists != shouldExist {
453+
return fmt.Errorf("index state mismatch for index %s in namespace %s.%s; should exist: %v, exists: %v",
454+
index, db, coll, shouldExist, exists)
455+
}
456+
return nil
426457
}
427458

428-
func indexExists(mt *mtest.T, dbName, collName, indexName string) bool {
459+
func indexExists(mt *mtest.T, dbName, collName, indexName string) (bool, error) {
429460
// Use global client because listIndexes cannot be executed inside a transaction.
430461
iv := mt.GlobalClient().Database(dbName).Collection(collName).Indexes()
431462
cursor, err := iv.List(mtest.Background)
432-
assert.Nil(mt, err, "IndexView.List error: %v", err)
463+
if err != nil {
464+
return false, fmt.Errorf("IndexView.List error: %v", err)
465+
}
433466
defer cursor.Close(mtest.Background)
434467

435468
for cursor.Next(mtest.Background) {
436469
if cursor.Current.Lookup("name").StringValue() == indexName {
437-
return true
470+
return true, nil
438471
}
439472
}
440-
assert.Nil(mt, cursor.Err(), "unexpected cursor iteration error: %v", cursor.Err())
441-
return false
473+
return false, cursor.Err()
442474
}
443475

444-
func assertCollectionState(mt *mtest.T, op *operation, shouldExist bool) {
476+
func verifyCollectionState(mt *mtest.T, op *operation, shouldExist bool) error {
445477
db := op.Arguments.Lookup("database").StringValue()
446478
coll := op.Arguments.Lookup("collection").StringValue()
447-
exists := collectionExists(mt, db, coll)
448-
assert.Equal(mt, shouldExist, exists, "collection state mismatch for %s:%s; should exist: %v, exists: %v",
449-
db, coll, shouldExist, coll)
479+
480+
exists, err := collectionExists(mt, db, coll)
481+
if err != nil {
482+
return err
483+
}
484+
if exists != shouldExist {
485+
return fmt.Errorf("collection state mismatch for %s.%s; should exist %v, exists: %v", db, coll, shouldExist,
486+
exists)
487+
}
488+
return nil
450489
}
451490

452-
func collectionExists(mt *mtest.T, dbName, collName string) bool {
491+
func collectionExists(mt *mtest.T, dbName, collName string) (bool, error) {
453492
filter := bson.D{
454493
{"name", collName},
455494
}
456495

457496
// Use global client because listCollections cannot be executed inside a transaction.
458497
collections, err := mt.GlobalClient().Database(dbName).ListCollectionNames(mtest.Background, filter)
459-
assert.Nil(mt, err, "ListCollectionNames error: %v", err)
498+
if err != nil {
499+
return false, fmt.Errorf("ListCollectionNames error: %v", err)
500+
}
460501

461-
return len(collections) > 0
502+
return len(collections) > 0, nil
462503
}
463504

464505
func lastTwoIDs(mt *mtest.T) (bson.RawValue, bson.RawValue) {

0 commit comments

Comments
 (0)