@@ -3,309 +3,24 @@ import Nimble
3
3
import TestsCommon
4
4
import XCTest
5
5
6
- /// The entity on which to start a change stream.
7
- internal enum ChangeStreamTarget : String , Decodable {
8
- /// Indicates the change stream will be opened to watch a client.
9
- case client
10
-
11
- /// Indicates the change stream will be opened to watch a database.
12
- case database
13
-
14
- /// Indicates the change stream will be opened to watch a collection.
15
- case collection
16
-
17
- /// Open a change stream against this target. An error will be thrown if the necessary namespace information is not
18
- /// provided.
19
- internal func watch(
20
- _ client: MongoClient ,
21
- _ database: String ? ,
22
- _ collection: String ? ,
23
- _ pipeline: [ BSONDocument ] ,
24
- _ options: ChangeStreamOptions
25
- ) throws -> ChangeStream < BSONDocument > {
26
- switch self {
27
- case . client:
28
- return try client. watch ( pipeline, options: options, withEventType: BSONDocument . self)
29
- case . database:
30
- guard let database = database else {
31
- throw TestError ( message: " missing db in watch " )
32
- }
33
- return try client. db ( database) . watch ( pipeline, options: options, withEventType: BSONDocument . self)
34
- case . collection:
35
- guard let collection = collection, let database = database else {
36
- throw TestError ( message: " missing db or collection in watch " )
37
- }
38
- return try client. db ( database)
39
- . collection ( collection)
40
- . watch ( pipeline, options: options, withEventType: BSONDocument . self)
41
- }
42
- }
43
- }
44
-
45
- /// An operation performed as part of a `ChangeStreamTest` (e.g. a CRUD operation, an drop, etc.)
46
- /// This struct includes the namespace against which it should be run.
47
- internal struct ChangeStreamTestOperation : Decodable {
48
- /// The operation itself to run.
49
- private let operation : AnyTestOperation
50
-
51
- /// The database to run the operation against.
52
- private let database : String
53
-
54
- /// The collection to run the operation against.
55
- private let collection : String
56
-
57
- private enum CodingKeys : String , CodingKey {
58
- case database, collection
59
- }
60
-
61
- public init ( from decoder: Decoder ) throws {
62
- let container = try decoder. container ( keyedBy: CodingKeys . self)
63
- self . database = try container. decode ( String . self, forKey: . database)
64
- self . collection = try container. decode ( String . self, forKey: . collection)
65
- self . operation = try AnyTestOperation ( from: decoder)
66
- }
67
-
68
- /// Run the operation against the namespace associated with this operation.
69
- internal func execute( using client: MongoClient ) throws -> TestOperationResult ? {
70
- let db = client. db ( self . database)
71
- let coll = db. collection ( self . collection)
72
- return try self . operation. op. execute ( on: coll, sessions: [ : ] )
73
- }
74
- }
75
-
76
- /// The outcome of a given `ChangeStreamTest`.
77
- internal enum ChangeStreamTestResult : Decodable {
78
- /// Describes an error received during the test
79
- case error( code: Int , labels: [ String ] ? )
80
-
81
- /// An array of event documents expected to be received from the change stream without error during the test.
82
- case success( [ BSONDocument ] )
83
-
84
- /// Top-level coding keys. Used for determining whether this result is a success or failure.
85
- internal enum CodingKeys : CodingKey {
86
- case error, success
87
- }
88
-
89
- /// Coding keys used specifically for decoding the `.error` case.
90
- internal enum ErrorCodingKeys : CodingKey {
91
- case code, errorLabels
92
- }
93
-
94
- /// Asserts that the given error matches the one expected by this result.
95
- internal func assertMatchesError( error: Error , description: String ) {
96
- guard case let . error( code, labels) = self else {
97
- fail ( " \( description) failed: got error but result success " )
98
- return
99
- }
100
- guard let seenError = error as? MongoError . CommandError else {
101
- fail ( " \( description) failed: didn't get command error " )
102
- return
103
- }
104
-
105
- expect ( seenError. code) . to ( equal ( code) , description: description)
106
- if let labels = labels {
107
- expect ( seenError. errorLabels) . toNot ( beNil ( ) , description: description)
108
- expect ( seenError. errorLabels) . to ( equal ( labels) , description: description)
109
- }
110
- }
111
-
112
- public init ( from decoder: Decoder ) throws {
113
- let container = try decoder. container ( keyedBy: CodingKeys . self)
114
- if container. contains ( . success) {
115
- self = . success( try container. decode ( [ BSONDocument ] . self, forKey: . success) )
116
- } else {
117
- let nested = try container. nestedContainer ( keyedBy: ErrorCodingKeys . self, forKey: . error)
118
- let code = try nested. decode ( Int . self, forKey: . code)
119
- let labels = try nested. decodeIfPresent ( [ String ] . self, forKey: . errorLabels)
120
- self = . error( code: code, labels: labels)
121
- }
122
- }
123
- }
124
-
125
- /// Struct representing a single test within a spec test JSON file.
126
- internal struct ChangeStreamTest : Decodable , FailPointConfigured {
127
- /// The title of this test.
128
- let description : String
129
-
130
- /// The minimum server version that this test can be run against.
131
- let minServerVersion : ServerVersion
132
-
133
- /// The maximum server version that this test can be run against.
134
- let maxServerVersion : ServerVersion ?
135
-
136
- /// The fail point that should be set prior to running this test.
137
- let failPoint : FailPoint ?
138
-
139
- /// The entity on which to run the change stream.
140
- let target : ChangeStreamTarget
141
-
142
- /// An array of server topologies against which to run the test.
143
- let topology : [ TestTopologyConfiguration ]
144
-
145
- /// An array of additional aggregation pipeline stages to pass to the `watch` used to create the change stream for
146
- /// this test.
147
- let changeStreamPipeline : [ BSONDocument ]
148
-
149
- /// Additional options to pass to the `watch` used to create the change stream for this test.
150
- let changeStreamOptions : ChangeStreamOptions
151
-
152
- /// An array of documents, each describing an operation that should be run as part of this test.
153
- let operations : [ ChangeStreamTestOperation ]
154
-
155
- /// A list of command-started events that are expected to have been emitted by the client that starts the change
156
- /// stream for this test.
157
- let expectations : [ TestCommandStartedEvent ] ?
158
-
159
- // The expected result of running this test.
160
- let result : ChangeStreamTestResult
161
-
162
- var activeFailPoint : FailPoint ?
163
- var targetedHost : ServerAddress ?
164
-
165
- internal mutating func run( globalClient: MongoClient , database: String , collection: String ) throws {
166
- let client = try MongoClient . makeTestClient ( )
167
- let monitor = client. addCommandMonitor ( )
168
-
169
- if let failPoint = self . failPoint {
170
- try failPoint. enable ( using: globalClient)
171
- }
172
- defer { self . failPoint? . disable ( using: globalClient) }
173
-
174
- monitor. captureEvents {
175
- do {
176
- let changeStream = try self . target. watch (
177
- client,
178
- database,
179
- collection,
180
- self . changeStreamPipeline,
181
- self . changeStreamOptions
182
- )
183
- for operation in self . operations {
184
- _ = try operation. execute ( using: globalClient)
185
- }
186
-
187
- switch self . result {
188
- case . error:
189
- _ = try changeStream. nextWithTimeout ( )
190
- fail ( " \( self . description) failed: expected error but got none while iterating " )
191
- case let . success( events) :
192
- var seenEvents : [ BSONDocument ] = [ ]
193
- for _ in 0 ..< events. count {
194
- guard let event = try changeStream. nextWithTimeout ( ) else {
195
- XCTFail ( " Unexpectedly got no event from change stream in test: \( self . description) " )
196
- return
197
- }
198
- seenEvents. append ( event)
199
- }
200
- expect ( seenEvents) . to ( match ( events) , description: self . description)
201
- }
202
- } catch {
203
- self . result. assertMatchesError ( error: error, description: self . description)
204
- }
205
- }
206
-
207
- if let expectations = self . expectations {
208
- let commandEvents = monitor. commandStartedEvents ( )
209
- . filter { ![ LEGACY_HELLO, " hello " , " killCursors " ] . contains ( $0. commandName) }
210
- . map { TestCommandStartedEvent ( from: $0) }
211
- expect ( commandEvents) . to ( match ( expectations) , description: self . description)
212
- }
213
- }
214
- }
215
-
216
- /// Struct representing a single change-streams spec test JSON file.
217
- private struct ChangeStreamTestFile : Decodable {
218
- private enum CodingKeys : String , CodingKey {
219
- case databaseName = " database_name " ,
220
- collectionName = " collection_name " ,
221
- database2Name = " database2_name " ,
222
- collection2Name = " collection2_name " ,
223
- tests
224
- }
225
-
226
- /// The default database.
227
- let databaseName : String
228
-
229
- /// The default collection.
230
- let collectionName : String
231
-
232
- /// Secondary database.
233
- let database2Name : String ?
234
-
235
- // Secondary collection.
236
- let collection2Name : String ?
237
-
238
- /// An array of tests that are to be run independently of each other.
239
- let tests : [ ChangeStreamTest ]
240
- }
241
-
242
- /// Class covering the JSON spec tests associated with change streams.
243
- final class ChangeStreamSpecTests : MongoSwiftTestCase {
244
- func testChangeStreamSpec( ) throws {
245
- let tests = try retrieveSpecTestFiles (
246
- specName: " change-streams " ,
247
- subdirectory: " legacy " ,
248
- asType: ChangeStreamTestFile . self
249
- )
250
-
251
- let globalClient = try MongoClient . makeTestClient ( )
252
-
253
- for (testName, testFile) in tests {
254
- let db1 = globalClient. db ( testFile. databaseName)
255
- // only some test files use a second database.
256
- let db2 : MongoDatabase ?
257
- if let db2Name = testFile. database2Name {
258
- db2 = globalClient. db ( db2Name)
259
- } else {
260
- db2 = nil
261
- }
262
- defer {
263
- try ? db1. drop ( )
264
- try ? db2? . drop ( )
265
- }
266
- print ( " \n ------------ \n Executing tests from file \( testName) ... \n " )
267
- for var test in testFile. tests {
268
- let testRequirements = TestRequirement (
269
- minServerVersion: test. minServerVersion,
270
- maxServerVersion: test. maxServerVersion,
271
- acceptableTopologies: test. topology
272
- )
273
-
274
- let unmetRequirement = try globalClient. getUnmetRequirement ( testRequirements)
275
- guard unmetRequirement == nil else {
276
- printSkipMessage ( testName: test. description, unmetRequirement: unmetRequirement!)
277
- continue
278
- }
279
-
280
- print ( " Executing test: \( test. description) " )
281
-
282
- try db1. drop ( )
283
- try db2? . drop ( )
284
- _ = try db1. createCollection ( testFile. collectionName)
285
- _ = try db2? . createCollection ( testFile. collection2Name ?? " foo " )
286
-
287
- try test. run (
288
- globalClient: globalClient,
289
- database: testFile. databaseName,
290
- collection: testFile. collectionName
291
- )
292
- }
293
- }
294
- }
295
-
6
+ final class SyncChangeStreamTests : MongoSwiftTestCase {
7
+ let excludeFiles = [
8
+ // TODO: SWIFT-1458 Unskip.
9
+ " change-streams-showExpandedEvents.json " ,
10
+ // TODO: SWIFT-1472 Unskip.
11
+ " change-streams-pre_and_post_images.json "
12
+ ]
296
13
func testChangeStreamSpecUnified( ) throws {
297
14
let tests = try retrieveSpecTestFiles (
298
15
specName: " change-streams " ,
299
16
subdirectory: " unified " ,
17
+ excludeFiles: excludeFiles,
300
18
asType: UnifiedTestFile . self
301
19
) . map { $0. 1 }
302
20
let testRunner = try UnifiedTestRunner ( )
303
21
try testRunner. runFiles ( tests)
304
22
}
305
- }
306
23
307
- /// Class for spec prose tests and other integration tests associated with change streams.
308
- final class SyncChangeStreamTests : MongoSwiftTestCase {
309
24
/// How long in total a change stream should poll for an event or error before returning.
310
25
/// Used as a default value for `ChangeStream.nextWithTimeout`
311
26
public static let TIMEOUT : TimeInterval = 15
0 commit comments