Skip to content

Commit 01765b9

Browse files
committed
Changes to allow source-compatible tests and a new instance for each run
* XCTestCase protocol -> XCTestCaseType * Add XCTestCase class so `setUp` and `tearDown` can be marked `override` * Change `invokeTest` to be static and create new instances for each test * Change allTests to be a static property * Add a generic function adapting static method references on a type to the required signature.
1 parent 7f80b20 commit 01765b9

File tree

7 files changed

+132
-106
lines changed

7 files changed

+132
-106
lines changed

Sources/XCTest/XCTAssert.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private func _XCTEvaluateAssertion(assertion: _XCTAssertion, @autoclosure messag
104104
/// to it evaluates to false.
105105
///
106106
/// - Requires: This and all other XCTAssert* functions must be called from
107-
/// within a test method, as indicated by `XCTestCaseProvider.allTests`.
107+
/// within a test method, as indicated by `XCTestCaseType.allTests`.
108108
/// Assertion failures that occur outside of a test method will *not* be
109109
/// reported as failures.
110110
///

Sources/XCTest/XCTestCase.swift

Lines changed: 6 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -8,94 +8,19 @@
88
//
99
//
1010
// XCTestCase.swift
11-
// Base protocol (and extension with default methods) for test cases
11+
// Optional base class for test cases facilitating source code compatibility with
12+
// the Objective-C XCTest framework
1213
//
1314

14-
public protocol XCTestCase : XCTestCaseProvider {
15-
func setUp()
16-
func tearDown()
17-
}
18-
19-
extension XCTestCase {
20-
21-
public var continueAfterFailure: Bool {
22-
get {
23-
return true
24-
}
25-
set {
26-
// TODO: When using the Objective-C runtime, XCTest is able to throw an exception from an assert and then catch it at the frame above the test method. This enables the framework to effectively stop all execution in the current test. There is no such facility in Swift. Until we figure out how to get a compatible behavior, we have decided to hard-code the value of 'true' for continue after failure.
27-
}
28-
}
29-
30-
public func invokeTest() {
31-
let tests = self.allTests
32-
var totalDuration = 0.0
33-
var totalFailures = 0
34-
var unexpectedFailures = 0
35-
let overallDuration = measureTimeExecutingBlock {
36-
for (name, test) in tests {
37-
let method = "\(self.dynamicType).\(name)"
38-
39-
var failures = [XCTFailure]()
40-
XCTFailureHandler = { failure in
41-
if !self.continueAfterFailure {
42-
failure.emit(method)
43-
fatalError("Terminating execution due to test failure", file: failure.file, line: failure.line)
44-
} else {
45-
failures.append(failure)
46-
}
47-
}
48-
49-
print("Test Case '\(method)' started.")
50-
51-
setUp()
52-
53-
let duration = measureTimeExecutingBlock {
54-
do {
55-
try test()
56-
} catch {
57-
let unexpectedFailure = XCTFailure(message: "", failureDescription: "threw error \"\(error)\"", expected: false, file: "<EXPR>", line: 0)
58-
XCTFailureHandler!(unexpectedFailure)
59-
}
60-
}
6115

62-
tearDown()
16+
public class XCTestCase {
6317

64-
totalDuration += duration
65-
66-
var result = "passed"
67-
for failure in failures {
68-
failure.emit(method)
69-
totalFailures += 1
70-
if !failure.expected {
71-
unexpectedFailures += 1
72-
}
73-
result = failures.count > 0 ? "failed" : "passed"
74-
}
75-
76-
print("Test Case '\(method)' \(result) (\(printableStringForTimeInterval(duration)) seconds).")
77-
XCTAllRuns.append(XCTRun(duration: duration, method: method, passed: failures.count == 0, failures: failures))
78-
XCTFailureHandler = nil
79-
}
80-
}
81-
82-
var testCountSuffix = "s"
83-
if tests.count == 1 {
84-
testCountSuffix = ""
85-
}
86-
var failureSuffix = "s"
87-
if totalFailures == 1 {
88-
failureSuffix = ""
89-
}
90-
91-
print("Executed \(tests.count) test\(testCountSuffix), with \(totalFailures) failure\(failureSuffix) (\(unexpectedFailures) unexpected) in \(printableStringForTimeInterval(totalDuration)) (\(printableStringForTimeInterval(overallDuration))) seconds")
18+
public required init() {
9219
}
93-
20+
9421
public func setUp() {
95-
9622
}
97-
23+
9824
public func tearDown() {
99-
10025
}
10126
}

Sources/XCTest/XCTestCaseProvider.swift

Lines changed: 0 additions & 18 deletions
This file was deleted.

Sources/XCTest/XCTestCaseType.swift

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// This source file is part of the Swift.org open source project
2+
//
3+
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
4+
// Licensed under Apache License v2.0 with Runtime Library Exception
5+
//
6+
// See http://swift.org/LICENSE.txt for license information
7+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8+
//
9+
//
10+
// XCTestCaseType.swift
11+
// Base protocol (and extension with default methods) for test cases
12+
//
13+
14+
public typealias TestMethod = XCTestCaseType throws -> Void
15+
16+
public protocol XCTestCaseType {
17+
init()
18+
19+
func setUp()
20+
func tearDown()
21+
22+
// In the Objective-C version of XCTest, it is possible to discover all tests when the test is executed by asking the runtime for all methods and looking for the string "test". In Swift, we ask test providers to tell us the list of tests by implementing this property.
23+
static var allTests: [(String, TestMethod)] { get }
24+
}
25+
26+
/// Wrapper function allowing a static reference to a test method to fit
27+
/// the signature required by XCTestCaseType.allTests
28+
public func test<T: XCTestCaseType>(testFunc: T -> () throws -> Void) -> TestMethod {
29+
return { testCaseType in
30+
guard let testCase: T = testCaseType as? T else {
31+
fatalError("Attempt to invoke test on class \(T.self) with incompatible instance type \(testCaseType.dynamicType)")
32+
}
33+
34+
try testFunc(testCase)()
35+
}
36+
}
37+
38+
extension XCTestCaseType {
39+
40+
public var continueAfterFailure: Bool {
41+
get {
42+
return true
43+
}
44+
set {
45+
// TODO: When using the Objective-C runtime, XCTest is able to throw an exception from an assert and then catch it at the frame above the test method. This enables the framework to effectively stop all execution in the current test. There is no such facility in Swift. Until we figure out how to get a compatible behavior, we have decided to hard-code the value of 'true' for continue after failure.
46+
}
47+
}
48+
49+
public static func invokeTest() {
50+
let tests = self.allTests
51+
var totalDuration = 0.0
52+
var totalFailures = 0
53+
var unexpectedFailures = 0
54+
let overallDuration = measureTimeExecutingBlock {
55+
for (name, test) in tests {
56+
let testCase = self.init()
57+
let fullName = "\(testCase.dynamicType).\(name)"
58+
59+
var failures = [XCTFailure]()
60+
XCTFailureHandler = { failure in
61+
if !testCase.continueAfterFailure {
62+
failure.emit(fullName)
63+
fatalError("Terminating execution due to test failure", file: failure.file, line: failure.line)
64+
} else {
65+
failures.append(failure)
66+
}
67+
}
68+
69+
print("Test Case '\(fullName)' started.")
70+
71+
testCase.setUp()
72+
73+
let duration = measureTimeExecutingBlock {
74+
do {
75+
try test(testCase)
76+
} catch {
77+
let unexpectedFailure = XCTFailure(message: "", failureDescription: "threw error \"\(error)\"", expected: false, file: "<EXPR>", line: 0)
78+
XCTFailureHandler!(unexpectedFailure)
79+
}
80+
}
81+
82+
testCase.tearDown()
83+
84+
totalDuration += duration
85+
86+
var result = "passed"
87+
for failure in failures {
88+
failure.emit(fullName)
89+
totalFailures += 1
90+
if !failure.expected {
91+
unexpectedFailures += 1
92+
}
93+
result = failures.count > 0 ? "failed" : "passed"
94+
}
95+
96+
print("Test Case '\(fullName)' \(result) (\(printableStringForTimeInterval(duration)) seconds).")
97+
XCTAllRuns.append(XCTRun(duration: duration, method: fullName, passed: failures.count == 0, failures: failures))
98+
XCTFailureHandler = nil
99+
}
100+
}
101+
102+
var testCountSuffix = "s"
103+
if tests.count == 1 {
104+
testCountSuffix = ""
105+
}
106+
var failureSuffix = "s"
107+
if totalFailures == 1 {
108+
failureSuffix = ""
109+
}
110+
111+
print("Executed \(tests.count) test\(testCountSuffix), with \(totalFailures) failure\(failureSuffix) (\(unexpectedFailures) unexpected) in \(printableStringForTimeInterval(totalDuration)) (\(printableStringForTimeInterval(overallDuration))) seconds")
112+
}
113+
114+
public func setUp() {
115+
}
116+
117+
public func tearDown() {
118+
}
119+
}

Sources/XCTest/XCTestMain.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ internal struct XCTRun {
4444
///
4545
/// This function will not return. If the test cases pass, then it will call `exit(0)`. If there is a failure, then it will call `exit(1)`.
4646
/// - Parameter testCases: An array of test cases to run.
47-
@noreturn public func XCTMain(testCases: [XCTestCase]) {
47+
@noreturn public func XCTMain(testCases: [XCTestCaseType.Type]) {
4848
let overallDuration = measureTimeExecutingBlock {
4949
for testCase in testCases {
5050
testCase.invokeTest()

XCTest.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
/* Begin PBXBuildFile section */
1010
C265F66F1C3AEB6A00520CF9 /* XCTAssert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C265F6691C3AEB6A00520CF9 /* XCTAssert.swift */; };
1111
C265F6701C3AEB6A00520CF9 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C265F66A1C3AEB6A00520CF9 /* XCTestCase.swift */; };
12-
C265F6711C3AEB6A00520CF9 /* XCTestCaseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C265F66B1C3AEB6A00520CF9 /* XCTestCaseProvider.swift */; };
12+
C265F6711C3AEB6A00520CF9 /* XCTestCaseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C265F66B1C3AEB6A00520CF9 /* XCTestCaseType.swift */; };
1313
C265F6721C3AEB6A00520CF9 /* XCTestMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = C265F66C1C3AEB6A00520CF9 /* XCTestMain.swift */; };
1414
C265F6731C3AEB6A00520CF9 /* XCTimeUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C265F66D1C3AEB6A00520CF9 /* XCTimeUtilities.swift */; };
1515
/* End PBXBuildFile section */
@@ -34,7 +34,7 @@
3434
B1384A431C1B3E8700EDF031 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
3535
C265F6691C3AEB6A00520CF9 /* XCTAssert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTAssert.swift; sourceTree = "<group>"; };
3636
C265F66A1C3AEB6A00520CF9 /* XCTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestCase.swift; sourceTree = "<group>"; };
37-
C265F66B1C3AEB6A00520CF9 /* XCTestCaseProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestCaseProvider.swift; sourceTree = "<group>"; };
37+
C265F66B1C3AEB6A00520CF9 /* XCTestCaseType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestCaseType.swift; sourceTree = "<group>"; };
3838
C265F66C1C3AEB6A00520CF9 /* XCTestMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestMain.swift; sourceTree = "<group>"; };
3939
C265F66D1C3AEB6A00520CF9 /* XCTimeUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTimeUtilities.swift; sourceTree = "<group>"; };
4040
DA78F7E91C4039410082E15B /* lit.cfg */ = {isa = PBXFileReference; lastKnownFileType = text; path = lit.cfg; sourceTree = "<group>"; };
@@ -131,7 +131,7 @@
131131
children = (
132132
C265F6691C3AEB6A00520CF9 /* XCTAssert.swift */,
133133
C265F66A1C3AEB6A00520CF9 /* XCTestCase.swift */,
134-
C265F66B1C3AEB6A00520CF9 /* XCTestCaseProvider.swift */,
134+
C265F66B1C3AEB6A00520CF9 /* XCTestCaseType.swift */,
135135
C265F66C1C3AEB6A00520CF9 /* XCTestMain.swift */,
136136
C265F66D1C3AEB6A00520CF9 /* XCTimeUtilities.swift */,
137137
);
@@ -327,7 +327,7 @@
327327
files = (
328328
C265F6731C3AEB6A00520CF9 /* XCTimeUtilities.swift in Sources */,
329329
C265F6701C3AEB6A00520CF9 /* XCTestCase.swift in Sources */,
330-
C265F6711C3AEB6A00520CF9 /* XCTestCaseProvider.swift in Sources */,
330+
C265F6711C3AEB6A00520CF9 /* XCTestCaseType.swift in Sources */,
331331
C265F66F1C3AEB6A00520CF9 /* XCTAssert.swift in Sources */,
332332
C265F6721C3AEB6A00520CF9 /* XCTestMain.swift in Sources */,
333333
);

build_script.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def main():
9292

9393
sourceFiles = [
9494
"XCTAssert.swift",
95-
"XCTestCaseProvider.swift",
95+
"XCTestCaseType.swift",
9696
"XCTestCase.swift",
9797
"XCTimeUtilities.swift",
9898
"XCTestMain.swift",

0 commit comments

Comments
 (0)