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.
@@ -39,8 +44,75 @@ public class XCTestCase: XCTest {
39
44
/// https://bugs.swift.org/browse/SR-1129 for details.
40
45
public var _name : String
41
46
42
- public required override init ( ) {
43
- _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
+ }
44
116
}
45
117
}
46
118
@@ -82,90 +154,15 @@ extension XCTestCase {
82
154
}
83
155
}
84
156
85
- internal static func invokeTests( tests: [ ( String , XCTestCase throws -> Void ) ] ) {
86
- let observationCenter = XCTestObservationCenter . shared ( )
87
-
88
- var totalDuration = 0.0
89
- var totalFailures = 0
90
- var unexpectedFailures = 0
91
- let overallDuration = measureTimeExecutingBlock {
92
- for (name, test) in tests {
93
- let testCase = self . init ( )
94
- testCase. _name = " \( testCase. dynamicType) . \( name) "
95
-
96
- var failures = [ XCTFailure] ( )
97
- XCTFailureHandler = { failure in
98
- observationCenter. testCase ( testCase,
99
- didFailWithDescription: failure. failureMessage,
100
- inFile: String ( failure. file) ,
101
- atLine: failure. line)
102
-
103
- if !testCase. continueAfterFailure {
104
- failure. emit ( testCase. name)
105
- fatalError ( " Terminating execution due to test failure " , file: failure. file, line: failure. line)
106
- } else {
107
- failures. append ( failure)
108
- }
109
- }
110
-
111
- XCTPrint ( " Test Case ' \( testCase. name) ' started. " )
112
-
113
- observationCenter. testCaseWillStart ( testCase)
114
-
115
- testCase. setUp ( )
116
-
117
- let duration = measureTimeExecutingBlock {
118
- do {
119
- try test ( testCase)
120
- } catch {
121
- let unexpectedFailure = XCTFailure ( message: " " , failureDescription: " threw error \" \( error) \" " , expected: false , file: " <EXPR> " , line: 0 )
122
- XCTFailureHandler!( unexpectedFailure)
123
- }
124
- }
125
-
126
- testCase. tearDown ( )
127
- testCase. failIfExpectationsNotWaitedFor ( XCTAllExpectations)
128
- XCTAllExpectations = [ ]
129
-
130
- observationCenter. testCaseDidFinish ( testCase)
131
-
132
- totalDuration += duration
133
-
134
- var result = " passed "
135
- for failure in failures {
136
- failure. emit ( testCase. name)
137
- totalFailures += 1
138
- if !failure. expected {
139
- unexpectedFailures += 1
140
- }
141
- result = failures. count > 0 ? " failed " : " passed "
142
- }
143
-
144
- XCTPrint ( " Test Case ' \( testCase. name) ' \( result) ( \( printableStringForTimeInterval ( duration) ) seconds). " )
145
- XCTAllRuns . append ( XCTRun ( duration: duration, method: testCase. name, passed: failures. count == 0 , failures: failures) )
146
- XCTFailureHandler = nil
147
- }
148
- }
149
-
150
- let testCountSuffix = ( tests. count == 1 ) ? " " : " s "
151
- let failureSuffix = ( totalFailures == 1 ) ? " " : " s "
152
-
153
- XCTPrint ( " Executed \( tests. count) test \( testCountSuffix) , with \( totalFailures) failure \( failureSuffix) ( \( unexpectedFailures) unexpected) in \( printableStringForTimeInterval ( totalDuration) ) ( \( printableStringForTimeInterval ( overallDuration) ) ) seconds " )
154
- }
155
-
156
157
/// It is an API violation to create expectations but not wait for them to
157
158
/// be completed. Notify the user of a mistake via a test failure.
158
159
private func failIfExpectationsNotWaitedFor( expectations: [ XCTestExpectation ] ) {
159
160
if expectations. count > 0 {
160
- let failure = XCTFailure (
161
- message: " Failed due to unwaited expectations. " ,
162
- failureDescription: " " ,
163
- expected: false ,
164
- file: expectations. last!. file,
165
- line: expectations. last!. line)
166
- if let failureHandler = XCTFailureHandler {
167
- failureHandler ( failure)
168
- }
161
+ recordFailureWithDescription (
162
+ " Failed due to unwaited expectations. " ,
163
+ inFile: String ( expectations. last!. file) ,
164
+ atLine: expectations. last!. line,
165
+ expected: false )
169
166
}
170
167
}
171
168
@@ -233,15 +230,11 @@ extension XCTestCase {
233
230
// and executes the rest of the test. This discrepancy should be
234
231
// fixed.
235
232
if XCTAllExpectations . count == 0 {
236
- let failure = XCTFailure (
237
- message: " call made to wait without any expectations having been set. " ,
238
- failureDescription: " API violation " ,
239
- expected: false ,
240
- file: file,
241
- line: line)
242
- if let failureHandler = XCTFailureHandler {
243
- failureHandler ( failure)
244
- }
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 )
245
238
return
246
239
}
247
240
@@ -281,15 +274,11 @@ extension XCTestCase {
281
274
// Not all expectations were fulfilled. Append a failure
282
275
// to the array of expectation-based failures.
283
276
let descriptions = unfulfilledDescriptions. joined ( separator: " , " )
284
- let failure = XCTFailure (
285
- message: " Exceeded timeout of \( timeout) seconds, with unfulfilled expectations: \( descriptions) " ,
286
- failureDescription: " Asynchronous wait failed " ,
287
- expected: true ,
288
- file: file,
289
- line: line)
290
- if let failureHandler = XCTFailureHandler {
291
- failureHandler ( failure)
292
- }
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 )
293
282
}
294
283
295
284
// We've recorded all the failures; clear the expectations that
0 commit comments