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