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.
@@ -38,8 +43,75 @@ public class XCTestCase: XCTest {
38
43
/// this property should not be modified.
39
44
public var _name : String
40
45
41
- public required override init ( ) {
42
- _name = " \( self . dynamicType) .<unknown> "
46
+ public override var testRunClass : AnyClass {
47
+ return XCTestCaseRun . self
48
+ }
49
+
50
+ public override func performTest( run: XCTestRun ) {
51
+ guard let testRun = run as? XCTestCaseRun else {
52
+ fatalError ( " Wrong XCTestRun class. " )
53
+ }
54
+
55
+ XCTCurrentTestCase = self
56
+ testRun. start ( )
57
+ invokeTest ( )
58
+ failIfExpectationsNotWaitedFor ( XCTAllExpectations)
59
+ XCTAllExpectations = [ ]
60
+ testRun. stop ( )
61
+ XCTCurrentTestCase = nil
62
+ }
63
+
64
+ /// The designated initializer for SwiftXCTest's XCTestCase.
65
+ /// - Note: Like the designated initializer for Apple XCTest's XCTestCase,
66
+ /// `-[XCTestCase initWithInvocation:]`, it's rare for anyone outside of
67
+ /// XCTest itself to call this initializer.
68
+ public required init ( name: String , testClosure: XCTestCase throws -> Void ) {
69
+ _name = " \( self . dynamicType) . \( name) "
70
+ self . testClosure = testClosure
71
+ }
72
+
73
+ /// Invoking a test performs its setUp, invocation, and tearDown. In
74
+ /// general this should not be called directly.
75
+ public func invokeTest( ) {
76
+ setUp ( )
77
+ do {
78
+ try testClosure ( self )
79
+ } catch {
80
+ recordFailureWithDescription (
81
+ " threw error \" \( error) \" " ,
82
+ inFile: " <EXPR> " ,
83
+ atLine: 0 ,
84
+ expected: false )
85
+ }
86
+ tearDown ( )
87
+ }
88
+
89
+ /// Records a failure in the execution of the test and is used by all test
90
+ /// assertions.
91
+ /// - Parameter description: The description of the failure being reported.
92
+ /// - Parameter filePath: The file path to the source file where the failure
93
+ /// being reported was encountered.
94
+ /// - Parameter lineNumber: The line number in the source file at filePath
95
+ /// where the failure being reported was encountered.
96
+ /// - Parameter expected: `true` if the failure being reported was the
97
+ /// result of a failed assertion, `false` if it was the result of an
98
+ /// uncaught exception.
99
+ public func recordFailureWithDescription( description: String , inFile filePath: String , atLine lineNumber: UInt , expected: Bool ) {
100
+ testRun? . recordFailureWithDescription (
101
+ " \( name) : \( description) " ,
102
+ inFile: filePath,
103
+ atLine: lineNumber,
104
+ expected: expected)
105
+
106
+ // FIXME: Apple XCTest does not throw a fatal error and crash the test
107
+ // process, it merely prevents the remainder of a testClosure
108
+ // from execting after it's been determined that it has already
109
+ // failed. The following behavior is incorrect.
110
+ // FIXME: No regression tests exist for this feature. We may break it
111
+ // without ever realizing.
112
+ if !continueAfterFailure {
113
+ fatalError ( " Terminating execution due to test failure " )
114
+ }
43
115
}
44
116
}
45
117
@@ -81,90 +153,15 @@ extension XCTestCase {
81
153
}
82
154
}
83
155
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
156
/// It is an API violation to create expectations but not wait for them to
156
157
/// be completed. Notify the user of a mistake via a test failure.
157
158
private func failIfExpectationsNotWaitedFor( expectations: [ XCTestExpectation ] ) {
158
159
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
- }
160
+ recordFailureWithDescription (
161
+ " Failed due to unwaited expectations. " ,
162
+ inFile: String ( expectations. last!. file) ,
163
+ atLine: expectations. last!. line,
164
+ expected: false )
168
165
}
169
166
}
170
167
@@ -232,15 +229,11 @@ extension XCTestCase {
232
229
// and executes the rest of the test. This discrepancy should be
233
230
// fixed.
234
231
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
- }
232
+ recordFailureWithDescription (
233
+ " API violation - call made to wait without any expectations having been set. " ,
234
+ inFile: String ( file) ,
235
+ atLine: line,
236
+ expected: false )
244
237
return
245
238
}
246
239
@@ -280,15 +273,11 @@ extension XCTestCase {
280
273
// Not all expectations were fulfilled. Append a failure
281
274
// to the array of expectation-based failures.
282
275
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
- }
276
+ recordFailureWithDescription (
277
+ " Asynchronous wait failed - Exceeded timeout of \( timeout) seconds, with unfulfilled expectations: \( descriptions) " ,
278
+ inFile: String ( file) ,
279
+ atLine: line,
280
+ expected: true )
292
281
}
293
282
294
283
// We've recorded all the failures; clear the expectations that
0 commit comments