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