Skip to content

Commit b059a1b

Browse files
authored
SWIFT-587 Retryable Reads (#341)
1 parent b880706 commit b059a1b

File tree

54 files changed

+18794
-188
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+18794
-188
lines changed

Sources/MongoSwift/MongoClient.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,10 @@ public struct ClientOptions: CodingStrategyProvider, Decodable {
3232
/// Specifies a ReadPreference to use for the client.
3333
public var readPreference: ReadPreference? = nil
3434

35-
/// Determines whether the client should retry supported read operations.
36-
// TODO: SWIFT-587 make this public.
37-
internal var retryReads: Bool?
35+
/// Determines whether the client should retry supported read operations (on by default).
36+
public var retryReads: Bool?
3837

39-
/// Determines whether the client should retry supported write operations.
38+
/// Determines whether the client should retry supported write operations (on by default).
4039
public var retryWrites: Bool?
4140

4241
/**
@@ -67,7 +66,7 @@ public struct ClientOptions: CodingStrategyProvider, Decodable {
6766
public var writeConcern: WriteConcern?
6867

6968
private enum CodingKeys: CodingKey {
70-
case retryWrites, readConcern, writeConcern
69+
case retryWrites, retryReads, readConcern, writeConcern
7170
}
7271

7372
/// Convenience initializer allowing any/all to be omitted or optional.
@@ -78,6 +77,7 @@ public struct ClientOptions: CodingStrategyProvider, Decodable {
7877
notificationCenter: NotificationCenter? = nil,
7978
readConcern: ReadConcern? = nil,
8079
readPreference: ReadPreference? = nil,
80+
retryReads: Bool? = nil,
8181
retryWrites: Bool? = nil,
8282
serverMonitoring: Bool = false,
8383
tlsOptions: TLSOptions? = nil,
@@ -91,6 +91,7 @@ public struct ClientOptions: CodingStrategyProvider, Decodable {
9191
self.readConcern = readConcern
9292
self.readPreference = readPreference
9393
self.retryWrites = retryWrites
94+
self.retryReads = retryReads
9495
self.serverMonitoring = serverMonitoring
9596
self.tlsOptions = tlsOptions
9697
self.uuidCodingStrategy = uuidCodingStrategy

Sources/MongoSwift/MongoCollection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public struct SyncMongoCollection<T: Codable> {
1717
internal let _client: SyncMongoClient
1818

1919
/// The namespace for this collection.
20-
private let namespace: MongoNamespace
20+
internal let namespace: MongoNamespace
2121

2222
/// Encoder used by this collection for BSON conversions. (e.g. converting `CollectionType`s, indexes, and options
2323
/// to documents).

Tests/LinuxMain.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,12 @@ extension ReadWriteConcernTests {
276276
]
277277
}
278278

279+
extension RetryableReadsTests {
280+
static var allTests = [
281+
("testRetryableReads", testRetryableReads),
282+
]
283+
}
284+
279285
extension RetryableWritesTests {
280286
static var allTests = [
281287
("testRetryableWrites", testRetryableWrites),
@@ -310,6 +316,7 @@ XCTMain([
310316
testCase(OptionsTests.allTests),
311317
testCase(ReadPreferenceTests.allTests),
312318
testCase(ReadWriteConcernTests.allTests),
319+
testCase(RetryableReadsTests.allTests),
313320
testCase(RetryableWritesTests.allTests),
314321
testCase(SDAMTests.allTests),
315322
])

Tests/MongoSwiftTests/ChangeStreamTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ internal struct ChangeStreamTestOperation: Decodable {
6969
internal func execute(using client: SyncMongoClient) throws -> TestOperationResult? {
7070
let db = client.db(self.database)
7171
let coll = db.collection(self.collection)
72-
return try self.operation.op.execute(client: client, database: db, collection: coll, session: nil)
72+
return try self.operation.execute(on: .collection(coll), session: nil)
7373
}
7474
}
7575

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import Foundation
2+
import MongoSwift
3+
import Nimble
4+
import XCTest
5+
6+
/// Struct representing a single test within a spec test JSON file.
7+
private struct RetryableReadsTest: SpecTest {
8+
let description: String
9+
10+
let operations: [TestOperationDescription]
11+
12+
let clientOptions: ClientOptions?
13+
14+
let useMultipleMongoses: Bool?
15+
16+
let skipReason: String?
17+
18+
let failPoint: FailPoint?
19+
20+
let expectations: [TestCommandStartedEvent]?
21+
}
22+
23+
/// Struct representing a single retryable-writes spec test JSON file.
24+
private struct RetryableReadsTestFile: Decodable, SpecTestFile {
25+
private enum CodingKeys: String, CodingKey {
26+
case name, runOn, databaseName = "database_name", collectionName = "collection_name", data, tests
27+
}
28+
29+
let name: String
30+
31+
let runOn: [TestRequirement]?
32+
33+
let databaseName: String
34+
35+
let collectionName: String?
36+
37+
let data: TestData
38+
39+
let tests: [RetryableReadsTest]
40+
}
41+
42+
final class RetryableReadsTests: MongoSwiftTestCase, FailPointConfigured {
43+
var activeFailPoint: FailPoint?
44+
45+
override func tearDown() {
46+
self.disableActiveFailPoint()
47+
}
48+
49+
override func setUp() {
50+
self.continueAfterFailure = false
51+
}
52+
53+
func testRetryableReads() throws {
54+
let skippedTestKeywords = [
55+
"findOne", // TODO: SWIFT-643: Unskip this test
56+
"changeStream", // TODO: SWIFT-648: Unskip this test
57+
"gridfs",
58+
"count.",
59+
"count-",
60+
"mapReduce"
61+
]
62+
63+
let tests = try retrieveSpecTestFiles(specName: "retryable-reads", asType: RetryableReadsTestFile.self)
64+
for (_, testFile) in tests {
65+
guard skippedTestKeywords.allSatisfy({ !testFile.name.contains($0) }) else {
66+
fileLevelLog("Skipping tests from file \(testFile.name)...")
67+
continue
68+
}
69+
try testFile.runTests(parent: self)
70+
}
71+
}
72+
}

Tests/MongoSwiftTests/RetryableWritesTests.swift

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ import Foundation
33
import Nimble
44
import XCTest
55

6-
/// Struct representing a single test within a spec test JSON file.
7-
private struct RetryableWritesTest: Decodable, SpecTest {
6+
/// Struct representing a single test within a retryable-writes spec test JSON file.
7+
private struct RetryableWritesTest: Decodable {
8+
/// Description of the test.
89
let description: String
10+
11+
/// The expected outcome of executing the operation.
912
let outcome: TestOutcome
13+
14+
/// The operation to execute as part of this test case.
1015
let operation: AnyTestOperation
1116

1217
/// Options used to configure the `SyncMongoClient` used for this test.
@@ -67,12 +72,12 @@ final class RetryableWritesTests: MongoSwiftTestCase, FailPointConfigured {
6772

6873
if let requirements = testFile.runOn {
6974
guard requirements.contains(where: { $0.isMet(by: version, MongoSwiftTestCase.topologyType) }) else {
70-
print("Skipping tests from file \(fileName), deployment requirements not met.")
75+
fileLevelLog("Skipping tests from file \(fileName), deployment requirements not met.")
7176
continue
7277
}
7378
}
7479

75-
print("\n------------\nExecuting tests from file \(fileName)...\n")
80+
fileLevelLog("Executing tests from file \(fileName)...\n")
7681
for test in testFile.tests {
7782
print("Executing test: \(test.description)")
7883

@@ -90,7 +95,35 @@ final class RetryableWritesTests: MongoSwiftTestCase, FailPointConfigured {
9095
}
9196
defer { self.disableActiveFailPoint() }
9297

93-
try test.run(client: client, db: db, collection: collection, session: nil)
98+
var result: TestOperationResult?
99+
var seenError: Error?
100+
101+
do {
102+
result = try test.operation.execute(on: .collection(collection), session: nil)
103+
} catch {
104+
if case let ServerError.bulkWriteError(_, _, _, bulkResult, _) = error {
105+
result = TestOperationResult(from: bulkResult)
106+
}
107+
seenError = error
108+
}
109+
110+
if test.outcome.error ?? false {
111+
expect(seenError).toNot(beNil(), description: test.description)
112+
} else {
113+
expect(seenError).to(beNil(), description: test.description)
114+
}
115+
116+
if let expectedResult = test.outcome.result {
117+
expect(result).toNot(beNil())
118+
expect(result).to(equal(expectedResult))
119+
}
120+
121+
let verifyColl = db.collection(test.outcome.collection.name ?? collection.name)
122+
let foundDocs = try Array(verifyColl.find())
123+
expect(foundDocs.count).to(equal(test.outcome.collection.data.count))
124+
zip(foundDocs, test.outcome.collection.data).forEach {
125+
expect($0).to(sortedEqual($1), description: test.description)
126+
}
94127
}
95128
}
96129
}

Tests/MongoSwiftTests/SpecTestRunner/Match.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ extension BSON: Matchable {
9090
case let (.array(actual), .array(expected)):
9191
return actual.matches(expected: expected)
9292
default:
93+
if let selfInt = self.asInt(), let expectedInt = expected.asInt() {
94+
return selfInt == expectedInt
95+
}
9396
return self == expected
9497
}
9598
}

0 commit comments

Comments
 (0)