1
2
3
4
5
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}
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
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