...

Source file src/go.mongodb.org/mongo-driver/mongo/read_write_concern_spec_test.go

Documentation: go.mongodb.org/mongo-driver/mongo

     1  // Copyright (C) MongoDB, Inc. 2017-present.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"); you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     6  
     7  package mongo
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"io/ioutil"
    13  	"path"
    14  	"reflect"
    15  	"testing"
    16  	"time"
    17  
    18  	"go.mongodb.org/mongo-driver/bson"
    19  	"go.mongodb.org/mongo-driver/bson/bsontype"
    20  	"go.mongodb.org/mongo-driver/internal/assert"
    21  	"go.mongodb.org/mongo-driver/mongo/readconcern"
    22  	"go.mongodb.org/mongo-driver/mongo/writeconcern"
    23  	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
    24  	"go.mongodb.org/mongo-driver/x/mongo/driver/connstring"
    25  )
    26  
    27  const (
    28  	readWriteConcernTestsDir = "../testdata/read-write-concern"
    29  	connstringTestsDir       = "connection-string"
    30  	documentTestsDir         = "document"
    31  )
    32  
    33  var (
    34  	serverDefaultConcern = []byte{5, 0, 0, 0, 0} // server default read concern and write concern is empty document
    35  	specTestRegistry     = bson.NewRegistryBuilder().
    36  				RegisterTypeMapEntry(bson.TypeEmbeddedDocument, reflect.TypeOf(bson.Raw{})).Build()
    37  )
    38  
    39  type connectionStringTestFile struct {
    40  	Tests []connectionStringTest `bson:"tests"`
    41  }
    42  
    43  type connectionStringTest struct {
    44  	Description  string   `bson:"description"`
    45  	URI          string   `bson:"uri"`
    46  	Valid        bool     `bson:"valid"`
    47  	ReadConcern  bson.Raw `bson:"readConcern"`
    48  	WriteConcern bson.Raw `bson:"writeConcern"`
    49  }
    50  
    51  type documentTestFile struct {
    52  	Tests []documentTest `bson:"tests"`
    53  }
    54  
    55  type documentTest struct {
    56  	Description          string    `bson:"description"`
    57  	Valid                bool      `bson:"valid"`
    58  	ReadConcern          bson.Raw  `bson:"readConcern"`
    59  	ReadConcernDocument  *bson.Raw `bson:"readConcernDocument"`
    60  	WriteConcern         bson.Raw  `bson:"writeConcern"`
    61  	WriteConcernDocument *bson.Raw `bson:"writeConcernDocument"`
    62  	IsServerDefault      *bool     `bson:"isServerDefault"`
    63  	IsAcknowledged       *bool     `bson:"isAcknowledged"`
    64  }
    65  
    66  func TestReadWriteConcernSpec(t *testing.T) {
    67  	t.Run("connstring", func(t *testing.T) {
    68  		for _, file := range jsonFilesInDir(t, path.Join(readWriteConcernTestsDir, connstringTestsDir)) {
    69  			t.Run(file, func(t *testing.T) {
    70  				runConnectionStringTestFile(t, path.Join(readWriteConcernTestsDir, connstringTestsDir, file))
    71  			})
    72  		}
    73  	})
    74  	t.Run("document", func(t *testing.T) {
    75  		for _, file := range jsonFilesInDir(t, path.Join(readWriteConcernTestsDir, documentTestsDir)) {
    76  			t.Run(file, func(t *testing.T) {
    77  				runDocumentTestFile(t, path.Join(readWriteConcernTestsDir, documentTestsDir, file))
    78  			})
    79  		}
    80  	})
    81  }
    82  
    83  func runConnectionStringTestFile(t *testing.T, filePath string) {
    84  	content, err := ioutil.ReadFile(filePath)
    85  	assert.Nil(t, err, "ReadFile error for %v: %v", filePath, err)
    86  
    87  	var testFile connectionStringTestFile
    88  	err = bson.UnmarshalExtJSONWithRegistry(specTestRegistry, content, false, &testFile)
    89  	assert.Nil(t, err, "UnmarshalExtJSONWithRegistry error: %v", err)
    90  
    91  	for _, test := range testFile.Tests {
    92  		t.Run(test.Description, func(t *testing.T) {
    93  			runConnectionStringTest(t, test)
    94  		})
    95  	}
    96  }
    97  
    98  func runConnectionStringTest(t *testing.T, test connectionStringTest) {
    99  	cs, err := connstring.ParseAndValidate(test.URI)
   100  	if !test.Valid {
   101  		assert.NotNil(t, err, "expected Parse error, got nil")
   102  		return
   103  	}
   104  	assert.Nil(t, err, "Parse error: %v", err)
   105  
   106  	if test.ReadConcern != nil {
   107  		expected := readConcernFromRaw(t, test.ReadConcern)
   108  		assert.Equal(t, expected.GetLevel(), cs.ReadConcernLevel,
   109  			"expected level %v, got %v", expected.GetLevel(), cs.ReadConcernLevel)
   110  	}
   111  	if test.WriteConcern != nil {
   112  		expectedWc := writeConcernFromRaw(t, test.WriteConcern)
   113  		if expectedWc.wSet {
   114  			expected := expectedWc.GetW()
   115  			if _, ok := expected.(int); ok {
   116  				assert.True(t, cs.WNumberSet, "expected WNumberSet, got false")
   117  				assert.Equal(t, expected, cs.WNumber, "expected w value %v, got %v", expected, cs.WNumber)
   118  			} else {
   119  				assert.False(t, cs.WNumberSet, "expected WNumberSet to be false, got true")
   120  				assert.Equal(t, expected, cs.WString, "expected w value %v, got %v", expected, cs.WString)
   121  			}
   122  		}
   123  		if expectedWc.timeoutSet {
   124  			assert.True(t, cs.WTimeoutSet, "expected WTimeoutSet, got false")
   125  			assert.Equal(t, expectedWc.GetWTimeout(), cs.WTimeout,
   126  				"expected timeout value %v, got %v", expectedWc.GetWTimeout(), cs.WTimeout)
   127  		}
   128  		if expectedWc.jSet {
   129  			assert.True(t, cs.JSet, "expected JSet, got false")
   130  			assert.Equal(t, expectedWc.GetJ(), cs.J, "expected j value %v, got %v", expectedWc.GetJ(), cs.J)
   131  		}
   132  	}
   133  }
   134  
   135  func runDocumentTestFile(t *testing.T, filePath string) {
   136  	content, err := ioutil.ReadFile(filePath)
   137  	assert.Nil(t, err, "ReadFile error: %v", err)
   138  
   139  	var testFile documentTestFile
   140  	err = bson.UnmarshalExtJSONWithRegistry(specTestRegistry, content, false, &testFile)
   141  	assert.Nil(t, err, "UnmarshalExtJSONWithRegistry error: %v", err)
   142  
   143  	for _, test := range testFile.Tests {
   144  		t.Run(test.Description, func(t *testing.T) {
   145  			runDocumentTest(t, test)
   146  		})
   147  	}
   148  }
   149  
   150  func runDocumentTest(t *testing.T, test documentTest) {
   151  	if test.ReadConcern != nil {
   152  		_, actual, err := readConcernFromRaw(t, test.ReadConcern).MarshalBSONValue()
   153  		if !test.Valid {
   154  			assert.NotNil(t, err, "expected MarshalBSONValue error, got nil")
   155  		} else {
   156  			assert.Nil(t, err, "MarshalBSONValue error: %v", err)
   157  			compareDocuments(t, *test.ReadConcernDocument, actual)
   158  		}
   159  
   160  		if test.IsServerDefault != nil {
   161  			gotServerDefault := bytes.Equal(actual, serverDefaultConcern)
   162  			assert.Equal(t, *test.IsServerDefault, gotServerDefault, "expected server default read concern, got %s", actual)
   163  		}
   164  	}
   165  	if test.WriteConcern != nil {
   166  		actualWc := writeConcernFromRaw(t, test.WriteConcern)
   167  		_, actual, err := actualWc.MarshalBSONValue()
   168  		if !test.Valid {
   169  			assert.NotNil(t, err, "expected MarshalBSONValue error, got nil")
   170  			return
   171  		}
   172  		if test.IsAcknowledged != nil {
   173  			actualAck := actualWc.Acknowledged()
   174  			assert.Equal(t, *test.IsAcknowledged, actualAck,
   175  				"expected acknowledged %v, got %v", *test.IsAcknowledged, actualAck)
   176  		}
   177  
   178  		expected := *test.WriteConcernDocument
   179  		if errors.Is(err, writeconcern.ErrEmptyWriteConcern) {
   180  			elems, _ := expected.Elements()
   181  			if len(elems) == 0 {
   182  				assert.NotNil(t, test.IsServerDefault, "expected write concern %s, got empty", expected)
   183  				assert.True(t, *test.IsServerDefault, "expected write concern %s, got empty", expected)
   184  				return
   185  			}
   186  			if _, jErr := expected.LookupErr("j"); jErr == nil && len(elems) == 1 {
   187  				return
   188  			}
   189  		}
   190  
   191  		assert.Nil(t, err, "MarshalBSONValue error: %v", err)
   192  		if jVal, err := expected.LookupErr("j"); err == nil && !jVal.Boolean() {
   193  			actual = actual[:len(actual)-1]
   194  			actual = bsoncore.AppendBooleanElement(actual, "j", false)
   195  			actual, _ = bsoncore.AppendDocumentEnd(actual, 0)
   196  		}
   197  		compareDocuments(t, expected, actual)
   198  	}
   199  }
   200  
   201  func readConcernFromRaw(t *testing.T, rc bson.Raw) *readconcern.ReadConcern {
   202  	t.Helper()
   203  
   204  	var opts []readconcern.Option
   205  	elems, _ := rc.Elements()
   206  	for _, elem := range elems {
   207  		key := elem.Key()
   208  		val := elem.Value()
   209  
   210  		switch key {
   211  		case "level":
   212  			opts = append(opts, readconcern.Level(val.StringValue()))
   213  		default:
   214  			t.Fatalf("unrecognized read concern field %v", key)
   215  		}
   216  	}
   217  	return readconcern.New(opts...)
   218  }
   219  
   220  type writeConcern struct {
   221  	*writeconcern.WriteConcern
   222  	jSet       bool
   223  	wSet       bool
   224  	timeoutSet bool
   225  }
   226  
   227  func writeConcernFromRaw(t *testing.T, wcRaw bson.Raw) writeConcern {
   228  	var wc writeConcern
   229  	var opts []writeconcern.Option
   230  
   231  	elems, _ := wcRaw.Elements()
   232  	for _, elem := range elems {
   233  		key := elem.Key()
   234  		val := elem.Value()
   235  
   236  		switch key {
   237  		case "w":
   238  			wc.wSet = true
   239  			switch val.Type {
   240  			case bsontype.Int32:
   241  				w := int(val.Int32())
   242  				opts = append(opts, writeconcern.W(w))
   243  			case bsontype.String:
   244  				opts = append(opts, writeconcern.WTagSet(val.StringValue()))
   245  			default:
   246  				t.Fatalf("unexpected type for w: %v", val.Type)
   247  			}
   248  		case "wtimeoutMS":
   249  			wc.timeoutSet = true
   250  			timeout := time.Duration(val.Int32()) * time.Millisecond
   251  			opts = append(opts, writeconcern.WTimeout(timeout))
   252  		case "journal":
   253  			wc.jSet = true
   254  			j := val.Boolean()
   255  			opts = append(opts, writeconcern.J(j))
   256  		default:
   257  			t.Fatalf("unrecognized write concern field: %v", key)
   258  		}
   259  	}
   260  
   261  	wc.WriteConcern = writeconcern.New(opts...)
   262  	return wc
   263  }
   264  
   265  // generate a slice of all JSON file names in a directory
   266  func jsonFilesInDir(t *testing.T, dir string) []string {
   267  	t.Helper()
   268  
   269  	files := make([]string, 0)
   270  
   271  	entries, err := ioutil.ReadDir(dir)
   272  	assert.Nil(t, err, "unable to read json file: %v", err)
   273  
   274  	for _, entry := range entries {
   275  		if entry.IsDir() || path.Ext(entry.Name()) != ".json" {
   276  			continue
   277  		}
   278  
   279  		files = append(files, entry.Name())
   280  	}
   281  
   282  	return files
   283  }
   284  

View as plain text