24
24
/// - seealso: `XCTMain`
25
25
public typealias XCTestCaseEntry = ( testCaseClass: XCTestCase . Type , allTests: [ ( String , XCTestCase throws -> Void ) ] )
26
26
27
+ // A global pointer to the currently running test case. This is required in
28
+ // order for XCTAssert functions to report failures.
29
+ internal var XCTCurrentTestCase : XCTestCase ?
30
+
27
31
public class XCTestCase : XCTest {
32
+ private let testClosure : XCTestCase throws -> Void
28
33
29
34
/// The name of the test case, consisting of its class name and the method
30
35
/// name it will run.
@@ -35,11 +40,79 @@ public class XCTestCase: XCTest {
35
40
/// - Note: FIXME: This property should be readonly, but currently has to
36
41
/// be publicly settable due to a Swift compiler bug on Linux. To ensure
37
42
/// compatibility of tests between swift-corelibs-xctest and Apple XCTest,
38
- /// this property should not be modified.
43
+ /// this property should not be modified. See
44
+ /// https://bugs.swift.org/browse/SR-1129 for details.
39
45
public var _name : String
40
46
41
- public required override init ( ) {
42
- _name = " \( self . dynamicType) .<unknown> "
47
+ public override var testRunClass : AnyClass {
48
+ return XCTestCaseRun . self
49
+ }
50
+
51
+ public override func performTest( run: XCTestRun ) {
52
+ guard let testRun = run as? XCTestCaseRun else {
53
+ fatalError ( " Wrong XCTestRun class. " )
54
+ }
55
+
56
+ XCTCurrentTestCase = self
57
+ testRun. start ( )
58
+ invokeTest ( )
59
+ failIfExpectationsNotWaitedFor ( XCTAllExpectations)
60
+ XCTAllExpectations = [ ]
61
+ testRun. stop ( )
62
+ XCTCurrentTestCase = nil
63
+ }
64
+
65
+ /// The designated initializer for SwiftXCTest's XCTestCase.
66
+ /// - Note: Like the designated initializer for Apple XCTest's XCTestCase,
67
+ /// `-[XCTestCase initWithInvocation:]`, it's rare for anyone outside of
68
+ /// XCTest itself to call this initializer.
69
+ public required init ( name: String , testClosure: XCTestCase throws -> Void ) {
70
+ _name = " \( self . dynamicType) . \( name) "
71
+ self . testClosure = testClosure
72
+ }
73
+
74
+ /// Invoking a test performs its setUp, invocation, and tearDown. In
75
+ /// general this should not be called directly.
76
+ public func invokeTest( ) {
77
+ setUp ( )
78
+ do {
79
+ try testClosure ( self )
80
+ } catch {
81
+ recordFailureWithDescription (
82
+ " threw error \" \( error) \" " ,
83
+ inFile: " <EXPR> " ,
84
+ atLine: 0 ,
85
+ expected: false )
86
+ }
87
+ tearDown ( )
88
+ }
89
+
90
+ /// Records a failure in the execution of the test and is used by all test
91
+ /// assertions.
92
+ /// - Parameter description: The description of the failure being reported.
93
+ /// - Parameter filePath: The file path to the source file where the failure
94
+ /// being reported was encountered.
95
+ /// - Parameter lineNumber: The line number in the source file at filePath
96
+ /// where the failure being reported was encountered.
97
+ /// - Parameter expected: `true` if the failure being reported was the
98
+ /// result of a failed assertion, `false` if it was the result of an
99
+ /// uncaught exception.
100
+ public func recordFailureWithDescription( description: String , inFile filePath: String , atLine lineNumber: UInt , expected: Bool ) {
101
+ testRun? . recordFailureWithDescription (
102
+ " \( name) : \( description) " ,
103
+ inFile: filePath,
104
+ atLine: lineNumber,
105
+ expected: expected)
106
+
107
+ // FIXME: Apple XCTest does not throw a fatal error and crash the test
108
+ // process, it merely prevents the remainder of a testClosure
109
+ // from execting after it's been determined that it has already
110
+ // failed. The following behavior is incorrect.
111
+ // FIXME: No regression tests exist for this feature. We may break it
112
+ // without ever realizing.
113
+ if !continueAfterFailure {
114
+ fatalError ( " Terminating execution due to test failure " )
115
+ }
43
116
}
44
117
}
45
118
@@ -81,90 +154,15 @@ extension XCTestCase {
81
154
}
82
155
}
83
156
84
- internal static func invokeTests( tests: [ ( String , XCTestCase throws -> Void ) ] ) {
85
- let observationCenter = XCTestObservationCenter . shared ( )
86
-
87
- var totalDuration = 0.0
88
- var totalFailures = 0
89
- var unexpectedFailures = 0
90
- let overallDuration = measureTimeExecutingBlock {
91
- for (name, test) in tests {
92
- let testCase = self . init ( )
93
- testCase. _name = " \( testCase. dynamicType) . \( name) "
94
-
95
- var failures = [ XCTFailure] ( )
96
- XCTFailureHandler = { failure in
97
- observationCenter. testCase ( testCase,
98
- didFailWithDescription: failure. failureMessage,
99
- inFile: String ( failure. file) ,
100
- atLine: failure. line)
101
-
102
- if !testCase. continueAfterFailure {
103
- failure. emit ( testCase. name)
104
- fatalError ( " Terminating execution due to test failure " , file: failure. file, line: failure. line)
105
- } else {
106
- failures. append ( failure)
107
- }
108
- }
109
-
110
- XCTPrint ( " Test Case ' \( testCase. name) ' started. " )
111
-
112
- observationCenter. testCaseWillStart ( testCase)
113
-
114
- testCase. setUp ( )
115
-
116
- let duration = measureTimeExecutingBlock {
117
- do {
118
- try test ( testCase)
119
- } catch {
120
- let unexpectedFailure = XCTFailure ( message: " " , failureDescription: " threw error \" \( error) \" " , expected: false , file: " <EXPR> " , line: 0 )
121
- XCTFailureHandler!( unexpectedFailure)
122
- }
123
- }
124
-
125
- testCase. tearDown ( )
126
- testCase. failIfExpectationsNotWaitedFor ( XCTAllExpectations)
127
- XCTAllExpectations = [ ]
128
-
129
- observationCenter. testCaseDidFinish ( testCase)
130
-
131
- totalDuration += duration
132
-
133
- var result = " passed "
134
- for failure in failures {
135
- failure. emit ( testCase. name)
136
- totalFailures += 1
137
- if !failure. expected {
138
- unexpectedFailures += 1
139
- }
140
- result = failures. count > 0 ? " failed " : " passed "
141
- }
142
-
143
- XCTPrint ( " Test Case ' \( testCase. name) ' \( result) ( \( printableStringForTimeInterval ( duration) ) seconds). " )
144
- XCTAllRuns . append ( XCTRun ( duration: duration, method: testCase. name, passed: failures. count == 0 , failures: failures) )
145
- XCTFailureHandler = nil
146
- }
147
- }
148
-
149
- let testCountSuffix = ( tests. count == 1 ) ? " " : " s "
150
- let failureSuffix = ( totalFailures == 1 ) ? " " : " s "
151
-
152
- XCTPrint ( " Executed \( tests. count) test \( testCountSuffix) , with \( totalFailures) failure \( failureSuffix) ( \( unexpectedFailures) unexpected) in \( printableStringForTimeInterval ( totalDuration) ) ( \( printableStringForTimeInterval ( overallDuration) ) ) seconds " )
153
- }
154
-
155
157
/// It is an API violation to create expectations but not wait for them to
156
158
/// be completed. Notify the user of a mistake via a test failure.
157
159
private func failIfExpectationsNotWaitedFor( expectations: [ XCTestExpectation ] ) {
158
160
if expectations. count > 0 {
159
- let failure = XCTFailure (
160
- message: " Failed due to unwaited expectations. " ,
161
- failureDescription: " " ,
162
- expected: false ,
163
- file: expectations. last!. file,
164
- line: expectations. last!. line)
165
- if let failureHandler = XCTFailureHandler {
166
- failureHandler ( failure)
167
- }
161
+ recordFailureWithDescription (
162
+ " Failed due to unwaited expectations. " ,
163
+ inFile: String ( expectations. last!. file) ,
164
+ atLine: expectations. last!. line,
165
+ expected: false )
168
166
}
169
167
}
170
168
@@ -232,15 +230,11 @@ extension XCTestCase {
232
230
// and executes the rest of the test. This discrepancy should be
233
231
// fixed.
234
232
if XCTAllExpectations . count == 0 {
235
- let failure = XCTFailure (
236
- message: " call made to wait without any expectations having been set. " ,
237
- failureDescription: " API violation " ,
238
- expected: false ,
239
- file: file,
240
- line: line)
241
- if let failureHandler = XCTFailureHandler {
242
- failureHandler ( failure)
243
- }
233
+ recordFailureWithDescription (
234
+ " API violation - call made to wait without any expectations having been set. " ,
235
+ inFile: String ( file) ,
236
+ atLine: line,
237
+ expected: false )
244
238
return
245
239
}
246
240
@@ -280,15 +274,11 @@ extension XCTestCase {
280
274
// Not all expectations were fulfilled. Append a failure
281
275
// to the array of expectation-based failures.
282
276
let descriptions = unfulfilledDescriptions. joined ( separator: " , " )
283
- let failure = XCTFailure (
284
- message: " Exceeded timeout of \( timeout) seconds, with unfulfilled expectations: \( descriptions) " ,
285
- failureDescription: " Asynchronous wait failed " ,
286
- expected: true ,
287
- file: file,
288
- line: line)
289
- if let failureHandler = XCTFailureHandler {
290
- failureHandler ( failure)
291
- }
277
+ recordFailureWithDescription (
278
+ " Asynchronous wait failed - Exceeded timeout of \( timeout) seconds, with unfulfilled expectations: \( descriptions) " ,
279
+ inFile: String ( file) ,
280
+ atLine: line,
281
+ expected: true )
292
282
}
293
283
294
284
// We've recorded all the failures; clear the expectations that
0 commit comments