File: //opt/go/pkg/mod/go.mongodb.org/
[email protected]/mongo/read_write_concern_spec_test.go
// Copyright (C) MongoDB, Inc. 2017-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 mongo
import (
"bytes"
"errors"
"io/ioutil"
"path"
"reflect"
"testing"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/internal/assert"
"go.mongodb.org/mongo-driver/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/x/mongo/driver/connstring"
)
const (
readWriteConcernTestsDir = "../testdata/read-write-concern"
connstringTestsDir = "connection-string"
documentTestsDir = "document"
)
var (
serverDefaultConcern = []byte{5, 0, 0, 0, 0} // server default read concern and write concern is empty document
specTestRegistry = bson.NewRegistryBuilder().
RegisterTypeMapEntry(bson.TypeEmbeddedDocument, reflect.TypeOf(bson.Raw{})).Build()
)
type connectionStringTestFile struct {
Tests []connectionStringTest `bson:"tests"`
}
type connectionStringTest struct {
Description string `bson:"description"`
URI string `bson:"uri"`
Valid bool `bson:"valid"`
ReadConcern bson.Raw `bson:"readConcern"`
WriteConcern bson.Raw `bson:"writeConcern"`
}
type documentTestFile struct {
Tests []documentTest `bson:"tests"`
}
type documentTest struct {
Description string `bson:"description"`
Valid bool `bson:"valid"`
ReadConcern bson.Raw `bson:"readConcern"`
ReadConcernDocument *bson.Raw `bson:"readConcernDocument"`
WriteConcern bson.Raw `bson:"writeConcern"`
WriteConcernDocument *bson.Raw `bson:"writeConcernDocument"`
IsServerDefault *bool `bson:"isServerDefault"`
IsAcknowledged *bool `bson:"isAcknowledged"`
}
func TestReadWriteConcernSpec(t *testing.T) {
t.Run("connstring", func(t *testing.T) {
for _, file := range jsonFilesInDir(t, path.Join(readWriteConcernTestsDir, connstringTestsDir)) {
t.Run(file, func(t *testing.T) {
runConnectionStringTestFile(t, path.Join(readWriteConcernTestsDir, connstringTestsDir, file))
})
}
})
t.Run("document", func(t *testing.T) {
for _, file := range jsonFilesInDir(t, path.Join(readWriteConcernTestsDir, documentTestsDir)) {
t.Run(file, func(t *testing.T) {
runDocumentTestFile(t, path.Join(readWriteConcernTestsDir, documentTestsDir, file))
})
}
})
}
func runConnectionStringTestFile(t *testing.T, filePath string) {
content, err := ioutil.ReadFile(filePath)
assert.Nil(t, err, "ReadFile error for %v: %v", filePath, err)
var testFile connectionStringTestFile
err = bson.UnmarshalExtJSONWithRegistry(specTestRegistry, content, false, &testFile)
assert.Nil(t, err, "UnmarshalExtJSONWithRegistry error: %v", err)
for _, test := range testFile.Tests {
t.Run(test.Description, func(t *testing.T) {
runConnectionStringTest(t, test)
})
}
}
func runConnectionStringTest(t *testing.T, test connectionStringTest) {
cs, err := connstring.ParseAndValidate(test.URI)
if !test.Valid {
assert.NotNil(t, err, "expected Parse error, got nil")
return
}
assert.Nil(t, err, "Parse error: %v", err)
if test.ReadConcern != nil {
expected := readConcernFromRaw(t, test.ReadConcern)
assert.Equal(t, expected.GetLevel(), cs.ReadConcernLevel,
"expected level %v, got %v", expected.GetLevel(), cs.ReadConcernLevel)
}
if test.WriteConcern != nil {
expectedWc := writeConcernFromRaw(t, test.WriteConcern)
if expectedWc.wSet {
expected := expectedWc.GetW()
if _, ok := expected.(int); ok {
assert.True(t, cs.WNumberSet, "expected WNumberSet, got false")
assert.Equal(t, expected, cs.WNumber, "expected w value %v, got %v", expected, cs.WNumber)
} else {
assert.False(t, cs.WNumberSet, "expected WNumberSet to be false, got true")
assert.Equal(t, expected, cs.WString, "expected w value %v, got %v", expected, cs.WString)
}
}
if expectedWc.timeoutSet {
assert.True(t, cs.WTimeoutSet, "expected WTimeoutSet, got false")
assert.Equal(t, expectedWc.GetWTimeout(), cs.WTimeout,
"expected timeout value %v, got %v", expectedWc.GetWTimeout(), cs.WTimeout)
}
if expectedWc.jSet {
assert.True(t, cs.JSet, "expected JSet, got false")
assert.Equal(t, expectedWc.GetJ(), cs.J, "expected j value %v, got %v", expectedWc.GetJ(), cs.J)
}
}
}
func runDocumentTestFile(t *testing.T, filePath string) {
content, err := ioutil.ReadFile(filePath)
assert.Nil(t, err, "ReadFile error: %v", err)
var testFile documentTestFile
err = bson.UnmarshalExtJSONWithRegistry(specTestRegistry, content, false, &testFile)
assert.Nil(t, err, "UnmarshalExtJSONWithRegistry error: %v", err)
for _, test := range testFile.Tests {
t.Run(test.Description, func(t *testing.T) {
runDocumentTest(t, test)
})
}
}
func runDocumentTest(t *testing.T, test documentTest) {
if test.ReadConcern != nil {
_, actual, err := readConcernFromRaw(t, test.ReadConcern).MarshalBSONValue()
if !test.Valid {
assert.NotNil(t, err, "expected MarshalBSONValue error, got nil")
} else {
assert.Nil(t, err, "MarshalBSONValue error: %v", err)
compareDocuments(t, *test.ReadConcernDocument, actual)
}
if test.IsServerDefault != nil {
gotServerDefault := bytes.Equal(actual, serverDefaultConcern)
assert.Equal(t, *test.IsServerDefault, gotServerDefault, "expected server default read concern, got %s", actual)
}
}
if test.WriteConcern != nil {
actualWc := writeConcernFromRaw(t, test.WriteConcern)
_, actual, err := actualWc.MarshalBSONValue()
if !test.Valid {
assert.NotNil(t, err, "expected MarshalBSONValue error, got nil")
return
}
if test.IsAcknowledged != nil {
actualAck := actualWc.Acknowledged()
assert.Equal(t, *test.IsAcknowledged, actualAck,
"expected acknowledged %v, got %v", *test.IsAcknowledged, actualAck)
}
expected := *test.WriteConcernDocument
if errors.Is(err, writeconcern.ErrEmptyWriteConcern) {
elems, _ := expected.Elements()
if len(elems) == 0 {
assert.NotNil(t, test.IsServerDefault, "expected write concern %s, got empty", expected)
assert.True(t, *test.IsServerDefault, "expected write concern %s, got empty", expected)
return
}
if _, jErr := expected.LookupErr("j"); jErr == nil && len(elems) == 1 {
return
}
}
assert.Nil(t, err, "MarshalBSONValue error: %v", err)
if jVal, err := expected.LookupErr("j"); err == nil && !jVal.Boolean() {
actual = actual[:len(actual)-1]
actual = bsoncore.AppendBooleanElement(actual, "j", false)
actual, _ = bsoncore.AppendDocumentEnd(actual, 0)
}
compareDocuments(t, expected, actual)
}
}
func readConcernFromRaw(t *testing.T, rc bson.Raw) *readconcern.ReadConcern {
t.Helper()
var opts []readconcern.Option
elems, _ := rc.Elements()
for _, elem := range elems {
key := elem.Key()
val := elem.Value()
switch key {
case "level":
opts = append(opts, readconcern.Level(val.StringValue()))
default:
t.Fatalf("unrecognized read concern field %v", key)
}
}
return readconcern.New(opts...)
}
type writeConcern struct {
*writeconcern.WriteConcern
jSet bool
wSet bool
timeoutSet bool
}
func writeConcernFromRaw(t *testing.T, wcRaw bson.Raw) writeConcern {
var wc writeConcern
var opts []writeconcern.Option
elems, _ := wcRaw.Elements()
for _, elem := range elems {
key := elem.Key()
val := elem.Value()
switch key {
case "w":
wc.wSet = true
switch val.Type {
case bsontype.Int32:
w := int(val.Int32())
opts = append(opts, writeconcern.W(w))
case bsontype.String:
opts = append(opts, writeconcern.WTagSet(val.StringValue()))
default:
t.Fatalf("unexpected type for w: %v", val.Type)
}
case "wtimeoutMS":
wc.timeoutSet = true
timeout := time.Duration(val.Int32()) * time.Millisecond
opts = append(opts, writeconcern.WTimeout(timeout))
case "journal":
wc.jSet = true
j := val.Boolean()
opts = append(opts, writeconcern.J(j))
default:
t.Fatalf("unrecognized write concern field: %v", key)
}
}
wc.WriteConcern = writeconcern.New(opts...)
return wc
}
// generate a slice of all JSON file names in a directory
func jsonFilesInDir(t *testing.T, dir string) []string {
t.Helper()
files := make([]string, 0)
entries, err := ioutil.ReadDir(dir)
assert.Nil(t, err, "unable to read json file: %v", err)
for _, entry := range entries {
if entry.IsDir() || path.Ext(entry.Name()) != ".json" {
continue
}
files = append(files, entry.Name())
}
return files
}