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