@@ -33,9 +33,65 @@ public struct __ExpectationContext: ~Copyable {
33
33
/// will not be assigned a runtime value.
34
34
var runtimeValues : [ __ExpressionID : ( ) -> Expression . Value ? ]
35
35
36
- init ( sourceCode: [ __ExpressionID : String ] = [ : ] , runtimeValues: [ __ExpressionID : ( ) -> Expression . Value ? ] = [ : ] ) {
36
+ /// Computed differences between the operands or arguments of expressions.
37
+ ///
38
+ /// The values in this dictionary are gathered at runtime as subexpressions
39
+ /// are evaluated, much like ``runtimeValues``.
40
+ var differences : [ __ExpressionID : ( ) -> CollectionDifference < Any > ? ]
41
+
42
+ init (
43
+ sourceCode: [ __ExpressionID : String ] = [ : ] ,
44
+ runtimeValues: [ __ExpressionID : ( ) -> Expression . Value ? ] = [ : ] ,
45
+ differences: [ __ExpressionID : ( ) -> CollectionDifference < Any > ? ] = [ : ]
46
+ ) {
37
47
self . sourceCode = sourceCode
38
48
self . runtimeValues = runtimeValues
49
+ self . differences = differences
50
+ }
51
+
52
+ /// Convert an instance of `CollectionDifference` to one that is type-erased
53
+ /// over elements of type `Any`.
54
+ ///
55
+ /// - Parameters:
56
+ /// - difference: The difference to convert.
57
+ ///
58
+ /// - Returns: A type-erased copy of `difference`.
59
+ private static func _typeEraseCollectionDifference( _ difference: CollectionDifference < some Any > ) -> CollectionDifference < Any > {
60
+ CollectionDifference < Any > (
61
+ difference. lazy. map { change in
62
+ switch change {
63
+ case let . insert( offset, element, associatedWith) :
64
+ return . insert( offset: offset, element: element as Any , associatedWith: associatedWith)
65
+ case let . remove( offset, element, associatedWith) :
66
+ return . remove( offset: offset, element: element as Any , associatedWith: associatedWith)
67
+ }
68
+ }
69
+ ) !
70
+ }
71
+
72
+ /// Generate a description of a previously-computed collection difference.
73
+ ///
74
+ /// - Parameters:
75
+ /// - difference: The difference to describe.
76
+ ///
77
+ /// - Returns: A human-readable string describing `difference`.
78
+ private borrowing func _description( of difference: CollectionDifference < some Any > ) -> String {
79
+ let insertions : [ String ] = difference. insertions. lazy
80
+ . map ( \. element)
81
+ . map ( String . init ( describingForTest: ) )
82
+ let removals : [ String ] = difference. removals. lazy
83
+ . map ( \. element)
84
+ . map ( String . init ( describingForTest: ) )
85
+
86
+ var resultComponents = [ String] ( )
87
+ if !insertions. isEmpty {
88
+ resultComponents. append ( " inserted [ \( insertions. joined ( separator: " , " ) ) ] " )
89
+ }
90
+ if !removals. isEmpty {
91
+ resultComponents. append ( " removed [ \( removals. joined ( separator: " , " ) ) ] " )
92
+ }
93
+
94
+ return resultComponents. joined ( separator: " , " )
39
95
}
40
96
41
97
/// Collapse the given expression graph into one or more expressions with
@@ -102,6 +158,15 @@ public struct __ExpectationContext: ~Copyable {
102
158
expressionGraph [ keyPath] = expression
103
159
}
104
160
}
161
+
162
+ for (id, difference) in differences {
163
+ let keyPath = id. keyPath
164
+ if var expression = expressionGraph [ keyPath] , let difference = difference ( ) {
165
+ let differenceDescription = _description ( of: difference)
166
+ expression. differenceDescription = differenceDescription
167
+ expressionGraph [ keyPath] = expression
168
+ }
169
+ }
105
170
}
106
171
107
172
// Flatten the expression graph.
@@ -154,11 +219,12 @@ extension __ExpectationContext {
154
219
///
155
220
/// - Warning: This function is used to implement the `#expect()` and
156
221
/// `#require()` macros. Do not call it directly.
157
- public mutating func callAsFunction< T> ( _ value: T , _ id: __ExpressionID ) -> T where T : Copyable {
222
+ public mutating func callAsFunction< T> ( _ value: T , _ id: __ExpressionID ) -> T {
158
223
runtimeValues [ id] = { Expression . Value ( reflecting: value) }
159
224
return value
160
225
}
161
226
227
+ #if SWT_SUPPORTS_MOVE_ONLY_EXPRESSION_EXPANSION
162
228
/// Capture information about a value for use if the expectation currently
163
229
/// being evaluated fails.
164
230
///
@@ -176,7 +242,113 @@ extension __ExpectationContext {
176
242
// TODO: add support for borrowing non-copyable expressions (need @lifetime)
177
243
return value
178
244
}
245
+ #endif
246
+ }
247
+
248
+ // MARK: - Collection comparison
249
+
250
+ extension __ExpectationContext {
251
+ /// Compare two values using `==` or `!=`.
252
+ ///
253
+ /// - Parameters:
254
+ /// - lhs: The left-hand operand.
255
+ /// - lhsID: A value that uniquely identifies the expression represented by
256
+ /// `lhs` in the context of the expectation currently being evaluated.
257
+ /// - rhs: The left-hand operand.
258
+ /// - rhsID: A value that uniquely identifies the expression represented by
259
+ /// `rhs` in the context of the expectation currently being evaluated.
260
+ /// - op: A function that performs an operation on `lhs` and `rhs`.
261
+ /// - opID: A value that uniquely identifies the expression represented by
262
+ /// `op` in the context of the expectation currently being evaluated.
263
+ ///
264
+ /// - Returns: The result of calling `op(lhs, rhs)`.
265
+ ///
266
+ /// This overload of `__cmp()` serves as a catch-all for operands that are not
267
+ /// collections or otherwise are not interesting to the testing library.
268
+ ///
269
+ /// - Warning: This function is used to implement the `#expect()` and
270
+ /// `#require()` macros. Do not call it directly.
271
+ public mutating func __cmp< T, U, R> (
272
+ _ lhs: T ,
273
+ _ lhsID: __ExpressionID ,
274
+ _ rhs: U ,
275
+ _ rhsID: __ExpressionID ,
276
+ _ op: ( T , U ) throws -> R ,
277
+ _ opID: __ExpressionID
278
+ ) rethrows -> R {
279
+ try self ( op ( self ( lhs, lhsID) , self ( rhs, rhsID) ) , opID)
280
+ }
281
+
282
+ public mutating func __cmp< C> (
283
+ _ lhs: C ,
284
+ _ lhsID: __ExpressionID ,
285
+ _ rhs: C ,
286
+ _ rhsID: __ExpressionID ,
287
+ _ op: ( C , C ) -> Bool ,
288
+ _ opID: __ExpressionID
289
+ ) -> Bool where C: BidirectionalCollection , C. Element: Equatable {
290
+ let result = self ( op ( self ( lhs, lhsID) , self ( rhs, rhsID) ) , opID)
291
+
292
+ if !result {
293
+ differences [ opID] = { [ lhs, rhs] in
294
+ Self . _typeEraseCollectionDifference ( lhs. difference ( from: rhs) )
295
+ }
296
+ }
297
+
298
+ return result
299
+ }
300
+
301
+ public mutating func __cmp< R> (
302
+ _ lhs: R ,
303
+ _ lhsID: __ExpressionID ,
304
+ _ rhs: R ,
305
+ _ rhsID: __ExpressionID ,
306
+ _ op: ( R , R ) -> Bool ,
307
+ _ opID: __ExpressionID
308
+ ) -> Bool where R: RangeExpression & BidirectionalCollection , R. Element: Equatable {
309
+ self ( op ( self ( lhs, lhsID) , self ( rhs, rhsID) ) , opID)
310
+ }
311
+
312
+ public mutating func __cmp< S> (
313
+ _ lhs: S ,
314
+ _ lhsID: __ExpressionID ,
315
+ _ rhs: S ,
316
+ _ rhsID: __ExpressionID ,
317
+ _ op: ( S , S ) -> Bool ,
318
+ _ opID: __ExpressionID
319
+ ) -> Bool where S: StringProtocol {
320
+ let result = self ( op ( self ( lhs, lhsID) , self ( rhs, rhsID) ) , opID)
179
321
322
+ if !result {
323
+ differences [ opID] = { [ lhs, rhs] in
324
+ // Compare strings by line, not by character.
325
+ let lhsLines = String ( lhs) . split ( whereSeparator: \. isNewline)
326
+ let rhsLines = String ( rhs) . split ( whereSeparator: \. isNewline)
327
+
328
+ if lhsLines. count == 1 && rhsLines. count == 1 {
329
+ // There are no newlines in either string, so there's no meaningful
330
+ // per-line difference. Bail.
331
+ return nil
332
+ }
333
+
334
+ let diff = lhsLines. difference ( from: rhsLines)
335
+ if diff. isEmpty {
336
+ // The strings must have compared on a per-character basis, or this
337
+ // operator doesn't behave the way we expected. Bail.
338
+ return nil
339
+ }
340
+
341
+ return Self . _typeEraseCollectionDifference ( diff)
342
+ }
343
+ }
344
+
345
+ return result
346
+ }
347
+ }
348
+
349
+ // MARK: - Casting
350
+
351
+ extension __ExpectationContext {
180
352
/// Perform a conditional cast (`as?`) on a value.
181
353
///
182
354
/// - Parameters:
@@ -258,15 +430,15 @@ extension __ExpectationContext {
258
430
///
259
431
/// - Warning: This function is used to implement the `#expect()` and
260
432
/// `#require()` macros. Do not call it directly.
261
- public mutating func callAsFunction< T , U > ( _ value: T , _ id: __ExpressionID ) -> U where T : StringProtocol , U : _Pointer {
433
+ public mutating func callAsFunction< P > ( _ value: String , _ id: __ExpressionID ) -> P where P : _Pointer {
262
434
// Perform the normal value capture.
263
435
let result = self ( value, id)
264
436
265
437
// Create a C string copy of `value`.
266
438
#if os(Windows)
267
- let resultCString = _strdup ( String ( result) ) !
439
+ let resultCString = _strdup ( result) !
268
440
#else
269
- let resultCString = strdup ( String ( result) ) !
441
+ let resultCString = strdup ( result) !
270
442
#endif
271
443
272
444
// Store the C string pointer so we can free it later when this context is
@@ -277,7 +449,7 @@ extension __ExpectationContext {
277
449
_transformedCStrings. append ( resultCString)
278
450
279
451
// Return the C string as whatever pointer type the caller wants.
280
- return U ( bitPattern: Int ( bitPattern: resultCString) ) . unsafelyUnwrapped
452
+ return P ( bitPattern: Int ( bitPattern: resultCString) ) . unsafelyUnwrapped
281
453
}
282
454
}
283
455
#endif
0 commit comments