Skip to content

Commit 3d2bf27

Browse files
authored
SWIFT-1310, SWIFT-1307: Make unified test runner changes needed for load balancer testing, expose serviceID in command monitoring events (#662)
1 parent 7a9d5d3 commit 3d2bf27

File tree

84 files changed

+3039
-102
lines changed

Some content is hidden

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

84 files changed

+3039
-102
lines changed

Sources/MongoSwift/APM.swift

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ public enum CommandEvent: Publishable {
7676
self.event.commandName
7777
}
7878

79-
/// The driver generated request id.
79+
/// The driver generated request ID.
8080
public var requestID: Int64 {
8181
self.event.requestID
8282
}
8383

84-
/// The driver generated operation id. This is used to link events together such
84+
/// The driver generated operation ID. This is used to link events together such
8585
/// as bulk write operations.
8686
public var operationID: Int64 {
8787
self.event.operationID
@@ -91,22 +91,30 @@ public enum CommandEvent: Publishable {
9191
public var serverAddress: ServerAddress {
9292
self.event.serverAddress
9393
}
94+
95+
/// The service ID for the command, if the driver is in load balancer mode.
96+
public var serviceID: BSONObjectID? {
97+
self.event.serviceID
98+
}
9499
}
95100

96101
/// A protocol for command monitoring events to implement, specifying the command name and other shared fields.
97102
private protocol CommandEventProtocol {
98103
/// The command name.
99104
var commandName: String { get }
100105

101-
/// The driver generated request id.
106+
/// The driver generated request ID.
102107
var requestID: Int64 { get }
103108

104-
/// The driver generated operation id. This is used to link events together such
109+
/// The driver generated operation ID. This is used to link events together such
105110
/// as bulk write operations.
106111
var operationID: Int64 { get }
107112

108113
/// The address of the server the command was run against.
109114
var serverAddress: ServerAddress { get }
115+
116+
/// The service ID for the command, if the driver is in load balancer mode.
117+
var serviceID: BSONObjectID? { get }
110118
}
111119

112120
/// An event published when a command starts.
@@ -145,6 +153,9 @@ public struct CommandStartedEvent: MongoSwiftEvent, CommandEventProtocol {
145153
/// The address of the server the command was run against.
146154
public let serverAddress: ServerAddress
147155

156+
/// The service ID for the command, if the driver is in load balancer mode.
157+
public let serviceID: BSONObjectID?
158+
148159
fileprivate init(mongocEvent: MongocCommandStartedEvent) {
149160
// we have to copy because libmongoc owns the pointer.
150161
self.command = BSONDocument(copying: mongoc_apm_command_started_get_command(mongocEvent.ptr))
@@ -153,6 +164,11 @@ public struct CommandStartedEvent: MongoSwiftEvent, CommandEventProtocol {
153164
self.requestID = mongoc_apm_command_started_get_request_id(mongocEvent.ptr)
154165
self.operationID = mongoc_apm_command_started_get_operation_id(mongocEvent.ptr)
155166
self.serverAddress = ServerAddress(mongoc_apm_command_started_get_host(mongocEvent.ptr))
167+
if let serviceID = mongoc_apm_command_started_get_service_id(mongocEvent.ptr) {
168+
self.serviceID = BSONObjectID(bsonOid: serviceID.pointee)
169+
} else {
170+
self.serviceID = nil
171+
}
156172
}
157173

158174
fileprivate func toPublishable() -> CommandEvent {
@@ -196,6 +212,9 @@ public struct CommandSucceededEvent: MongoSwiftEvent, CommandEventProtocol {
196212
/// The address of the server the command was run against.
197213
public let serverAddress: ServerAddress
198214

215+
/// The service ID for the command, if the driver is in load balancer mode.
216+
public let serviceID: BSONObjectID?
217+
199218
fileprivate init(mongocEvent: MongocCommandSucceededEvent) {
200219
// TODO: SWIFT-349 add logging to check and warn of unlikely int size issues
201220
self.duration = Int(mongoc_apm_command_succeeded_get_duration(mongocEvent.ptr))
@@ -205,6 +224,11 @@ public struct CommandSucceededEvent: MongoSwiftEvent, CommandEventProtocol {
205224
self.requestID = mongoc_apm_command_succeeded_get_request_id(mongocEvent.ptr)
206225
self.operationID = mongoc_apm_command_succeeded_get_operation_id(mongocEvent.ptr)
207226
self.serverAddress = ServerAddress(mongoc_apm_command_succeeded_get_host(mongocEvent.ptr))
227+
if let serviceID = mongoc_apm_command_succeeded_get_service_id(mongocEvent.ptr) {
228+
self.serviceID = BSONObjectID(bsonOid: serviceID.pointee)
229+
} else {
230+
self.serviceID = nil
231+
}
208232
}
209233

210234
fileprivate func toPublishable() -> CommandEvent {
@@ -248,6 +272,9 @@ public struct CommandFailedEvent: MongoSwiftEvent, CommandEventProtocol {
248272
/// The connection id for the command.
249273
public let serverAddress: ServerAddress
250274

275+
/// The service ID for the command, if the driver is in load balancer mode.
276+
public let serviceID: BSONObjectID?
277+
251278
fileprivate init(mongocEvent: MongocCommandFailedEvent) {
252279
self.duration = Int(mongoc_apm_command_failed_get_duration(mongocEvent.ptr))
253280
self.commandName = String(cString: mongoc_apm_command_failed_get_command_name(mongocEvent.ptr))
@@ -258,6 +285,11 @@ public struct CommandFailedEvent: MongoSwiftEvent, CommandEventProtocol {
258285
self.requestID = mongoc_apm_command_failed_get_request_id(mongocEvent.ptr)
259286
self.operationID = mongoc_apm_command_failed_get_operation_id(mongocEvent.ptr)
260287
self.serverAddress = ServerAddress(mongoc_apm_command_failed_get_host(mongocEvent.ptr))
288+
if let serviceID = mongoc_apm_command_failed_get_service_id(mongocEvent.ptr) {
289+
self.serviceID = BSONObjectID(bsonOid: serviceID.pointee)
290+
} else {
291+
self.serviceID = nil
292+
}
261293
}
262294

263295
fileprivate func toPublishable() -> CommandEvent {

Sources/TestsCommon/CommonTestUtils.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ public enum UnmetRequirement {
224224
case topology(actual: TestTopologyConfiguration, required: [TestTopologyConfiguration])
225225
case serverParameter(name: String, actual: BSON?, required: BSON)
226226
case serverless(required: TestRequirement.ServerlessRequirement)
227+
case auth(actual: Bool, required: Bool)
227228
}
228229

229230
/// Struct representing conditions that a deployment must meet in order for a test file to be run.
@@ -255,6 +256,7 @@ public struct TestRequirement: Decodable {
255256
private let topologies: [TestTopologyConfiguration]?
256257
private let serverParameters: BSONDocument?
257258
private let serverless: ServerlessRequirement?
259+
private let auth: Bool?
258260

259261
public static let failCommandSupport: [TestRequirement] = [
260262
TestRequirement(
@@ -278,13 +280,15 @@ public struct TestRequirement: Decodable {
278280
maxServerVersion: ServerVersion? = nil,
279281
acceptableTopologies: [TestTopologyConfiguration]? = nil,
280282
serverlessRequirement: ServerlessRequirement? = nil,
281-
serverParameters: BSONDocument? = nil
283+
serverParameters: BSONDocument? = nil,
284+
auth: Bool? = nil
282285
) {
283286
self.minServerVersion = minServerVersion
284287
self.maxServerVersion = maxServerVersion
285288
self.topologies = acceptableTopologies
286289
self.serverParameters = serverParameters
287290
self.serverless = serverlessRequirement
291+
self.auth = auth
288292
}
289293

290294
/// Determines if the given deployment meets this requirement.
@@ -340,11 +344,17 @@ public struct TestRequirement: Decodable {
340344
}
341345
}
342346

347+
if let auth = self.auth {
348+
guard auth == MongoSwiftTestCase.auth else {
349+
return .auth(actual: MongoSwiftTestCase.auth, required: auth)
350+
}
351+
}
352+
343353
return nil
344354
}
345355

346356
private enum CodingKeys: String, CodingKey {
347-
case minServerVersion, maxServerVersion, topology, topologies, serverless, serverParameters
357+
case minServerVersion, maxServerVersion, topology, topologies, serverless, serverParameters, auth
348358
}
349359

350360
public init(from decoder: Decoder) throws {
@@ -361,6 +371,7 @@ public struct TestRequirement: Decodable {
361371
}
362372
self.serverParameters = try container.decodeIfPresent(BSONDocument.self, forKey: .serverParameters)
363373
self.serverless = try container.decodeIfPresent(ServerlessRequirement.self, forKey: .serverless)
374+
self.auth = try container.decodeIfPresent(Bool.self, forKey: .auth)
364375
}
365376
}
366377

@@ -471,6 +482,8 @@ public func printSkipMessage(
471482
case .require:
472483
reason = "this test must be run against a Serverless instance"
473484
}
485+
case let .auth(actual, required):
486+
reason = "Test requires auth is \(required ? "on" : "off") but auth is \(actual ? "on" : "off")"
474487
}
475488
printSkipMessage(testName: testName, reason: reason)
476489
}

Tests/MongoSwiftSyncTests/UnifiedTestRunner/EntityDescription.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ enum Entity {
214214
case session(ClientSession)
215215
case changeStream(ChangeStream<BSONDocument>)
216216
case bson(BSON)
217+
case findCursor(MongoCursor<BSONDocument>)
217218

218219
func asTestClient() throws -> UnifiedTestClient {
219220
guard case let .client(client) = self else {

Tests/MongoSwiftSyncTests/UnifiedTestRunner/ExpectedEventsForClient.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ enum ExpectedEvent: Decodable {
5151

5252
/// Name of the database the command is run against.
5353
let databaseName: String?
54+
55+
/// Specifies whether the serviceId field of the event is set.
56+
let hasServiceId: Bool?
5457
}
5558

5659
/// Represents expectations for a CommandSucceededEvent.
@@ -60,11 +63,17 @@ enum ExpectedEvent: Decodable {
6063

6164
/// Name of the command.
6265
let commandName: String?
66+
67+
/// Specifies whether the serviceId field of the event is set.
68+
let hasServiceId: Bool?
6369
}
6470

6571
/// Represents expectations for a CommandStartedEvent.
6672
struct CommandFailedExpectation: Decodable {
6773
/// Name of the command.
6874
let commandName: String?
75+
76+
/// Specifies whether the serviceId field of the event is set.
77+
let hasServiceId: Bool?
6978
}
7079
}

Tests/MongoSwiftSyncTests/UnifiedTestRunner/Matching.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ extension UnifiedOperationResult {
3535
actual = .none
3636
case let .changeStream(cs):
3737
throw NonMatchingError(expected: expected, actual: cs, context: context)
38+
case let .findCursor(c):
39+
throw NonMatchingError(expected: expected, actual: c, context: context)
3840
}
3941

4042
try actual.matches(expected, context: context)
@@ -337,6 +339,13 @@ func matchesEvents(expected: [ExpectedEvent], actual: [CommandEvent], context: C
337339
try equals(expected: expectedDb, actual: actualStarted.databaseName, context: context)
338340
}
339341
}
342+
343+
if let hasServiceId = expectedStarted.hasServiceId {
344+
try context.withPushedElt("hasServiceId") {
345+
try equals(expected: hasServiceId, actual: actualStarted.serviceID != nil, context: context)
346+
}
347+
}
348+
340349
case let (.commandSucceeded(expectedSucceeded), .succeeded(actualSucceeded)):
341350
if let expectedName = expectedSucceeded.commandName {
342351
try context.withPushedElt("commandName") {
@@ -350,12 +359,26 @@ func matchesEvents(expected: [ExpectedEvent], actual: [CommandEvent], context: C
350359
try actual.matches(.document(expectedReply), context: context)
351360
}
352361
}
362+
363+
if let hasServiceId = expectedSucceeded.hasServiceId {
364+
try context.withPushedElt("hasServiceId") {
365+
try equals(expected: hasServiceId, actual: actualSucceeded.serviceID != nil, context: context)
366+
}
367+
}
368+
353369
case let (.commandFailed(expectedFailed), .failed(actualFailed)):
354370
if let expectedName = expectedFailed.commandName {
355371
try context.withPushedElt("commandName") {
356372
try equals(expected: expectedName, actual: actualFailed.commandName, context: context)
357373
}
358374
}
375+
376+
if let hasServiceId = expectedFailed.hasServiceId {
377+
try context.withPushedElt("hasServiceId") {
378+
try equals(expected: hasServiceId, actual: actualFailed.serviceID != nil, context: context)
379+
}
380+
}
381+
359382
default:
360383
throw NonMatchingError(expected: expectedEvent, actual: actualEvent, context: context)
361384
}

Tests/MongoSwiftSyncTests/UnifiedTestRunner/UnifiedCollectionOperations.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,41 @@ struct UnifiedFind: UnifiedOperationProtocol {
152152
}
153153
}
154154

155+
struct UnifiedCreateFindCursor: UnifiedOperationProtocol {
156+
/// Filter to use for the operation.
157+
let filter: BSONDocument
158+
159+
/// Options to use for the operation.
160+
let options: FindOptions
161+
162+
/// Optional identifier for a session entity to use.
163+
let session: String?
164+
165+
private enum CodingKeys: String, CodingKey, CaseIterable {
166+
case session, filter
167+
}
168+
169+
static var knownArguments: Set<String> {
170+
Set(
171+
CodingKeys.allCases.map { $0.rawValue } +
172+
FindOptions.CodingKeys.allCases.map { $0.rawValue }
173+
)
174+
}
175+
176+
init(from decoder: Decoder) throws {
177+
self.options = try decoder.singleValueContainer().decode(FindOptions.self)
178+
let container = try decoder.container(keyedBy: CodingKeys.self)
179+
self.session = try container.decodeIfPresent(String.self, forKey: .session)
180+
self.filter = try container.decode(BSONDocument.self, forKey: .filter)
181+
}
182+
183+
func execute(on object: UnifiedOperation.Object, context: Context) throws -> UnifiedOperationResult {
184+
let collection = try context.entities.getEntity(from: object).asCollection()
185+
let session = try context.entities.resolveSession(id: self.session)
186+
return .findCursor(try collection.find(self.filter, options: self.options, session: session))
187+
}
188+
}
189+
155190
struct UnifiedFindOneAndReplace: UnifiedOperationProtocol {
156191
/// Filter to use for the operation.
157192
let filter: BSONDocument

Tests/MongoSwiftSyncTests/UnifiedTestRunner/UnifiedChangeStreamOperations.swift renamed to Tests/MongoSwiftSyncTests/UnifiedTestRunner/UnifiedCursorAndChangeStreamOperations.swift

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,38 @@ struct IterateUntilDocumentOrError: UnifiedOperationProtocol {
6868
static var knownArguments: Set<String> { [] }
6969

7070
func execute(on object: UnifiedOperation.Object, context: Context) throws -> UnifiedOperationResult {
71-
let cs = try context.entities.getEntity(from: object).asChangeStream()
72-
guard let next = cs.next() else {
73-
throw TestError(message: "Change stream unexpectedly exhausted")
71+
let entity = try context.entities.getEntity(from: object)
72+
switch entity {
73+
case let .changeStream(cs):
74+
guard let next = cs.next() else {
75+
throw TestError(message: "Change stream unexpectedly exhausted")
76+
}
77+
return .rootDocument(try next.get())
78+
case let .findCursor(c):
79+
guard let next = c.next() else {
80+
throw TestError(message: "Cursor unexpectedly exhausted")
81+
}
82+
return .rootDocument(try next.get())
83+
default:
84+
throw TestError(message: "Unsupported entity type \(entity) for IterateUntilDocumentOrError operation")
85+
}
86+
}
87+
}
88+
89+
struct UnifiedCloseCursor: UnifiedOperationProtocol {
90+
static var knownArguments: Set<String> { [] }
91+
92+
func execute(on object: UnifiedOperation.Object, context: Context) throws -> UnifiedOperationResult {
93+
let entity = try context.entities.getEntity(from: object)
94+
switch entity {
95+
case let .changeStream(cs):
96+
cs.kill()
97+
case let .findCursor(c):
98+
c.kill()
99+
default:
100+
throw TestError(message: "Unsupported entity type \(entity) for close operation")
74101
}
75-
return .rootDocument(try next.get())
102+
103+
return .none
76104
}
77105
}

0 commit comments

Comments
 (0)