Skip to content

Commit ebbeb27

Browse files
committed
Initial pieces of XCTestObservation and XCTestObservationCenter
1 parent 6854fb3 commit ebbeb27

File tree

5 files changed

+220
-1
lines changed

5 files changed

+220
-1
lines changed

Sources/XCTest/ObjectWrapper.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// This source file is part of the Swift.org open source project
2+
//
3+
// Copyright (c) 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+
// ObjectWrapper.swift
11+
// Utility type for adapting implementors of a `class` protocol to Hashable
12+
//
13+
14+
/// A `Hashable` representation of an object and its ObjectIdentifier. This is
15+
/// useful because Swift classes aren't implicitly hashable based on identity.
16+
internal struct ObjectWrapper<T>: Hashable {
17+
let object: T
18+
let objectIdentifier: ObjectIdentifier
19+
20+
var hashValue: Int { return objectIdentifier.hashValue }
21+
}
22+
23+
internal func ==<T>(lhs: ObjectWrapper<T>, rhs: ObjectWrapper<T>) -> Bool {
24+
return lhs.objectIdentifier == rhs.objectIdentifier
25+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// This source file is part of the Swift.org open source project
2+
//
3+
// Copyright (c) 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+
// XCTestObservation.swift
11+
// Hooks for being notified about progress during a test run.
12+
//
13+
14+
/// `XCTestObservation` provides hooks for being notified about progress during a
15+
/// test run.
16+
/// - seealso: `XCTestObservationCenter`
17+
public protocol XCTestObservation: class {
18+
/// Called just before a test begins executing.
19+
/// - Parameter testCase: The test case that is about to start. Its `name`
20+
/// property can be used to identify it.
21+
func testCaseWillStart(testCase: XCTestCase)
22+
23+
/// Called when a test failure is reported.
24+
/// - Parameter testCase: The test case that failed. Its `name` property
25+
/// can be used to identify it.
26+
/// - Parameter description: Details about the cause of the test failure.
27+
/// - Parameter filePath: The path to the source file where the failure
28+
/// was reported, if available.
29+
/// - Parameter lineNumber: The line number in the source file where the
30+
/// failure was reported.
31+
func testCase(testCase: XCTestCase, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: UInt)
32+
33+
/// Called just after a test finishes executing.
34+
/// - Parameter testCase: The test case that finished. Its `name` property
35+
/// can be used to identify it.
36+
func testCaseDidFinish(testCase: XCTestCase)
37+
}
38+
39+
// All `XCTestObservation` methods are optional, so empty default implementations are provided
40+
public extension XCTestObservation {
41+
func testCaseWillStart(testCase: XCTestCase) {}
42+
func testCase(testCase: XCTestCase, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: UInt) {}
43+
func testCaseDidFinish(testCase: XCTestCase) {}
44+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// This source file is part of the Swift.org open source project
2+
//
3+
// Copyright (c) 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+
// XCTestObservationCenter.swift
11+
// Notification center for test run progress events.
12+
//
13+
14+
/// Provides a registry for objects wishing to be informed about progress
15+
/// during the course of a test run. Observers must implement the
16+
/// `XCTestObservation` protocol
17+
/// - seealso: `XCTestObservation`
18+
public class XCTestObservationCenter {
19+
20+
private static var center = XCTestObservationCenter()
21+
private var observers = Set<ObjectWrapper<XCTestObservation>>()
22+
23+
/// Registration should be performed on this shared instance
24+
public class func sharedTestObservationCenter() -> XCTestObservationCenter {
25+
return center
26+
}
27+
28+
/// Register an observer to receive future events during a test run. The order
29+
/// in which individual observers are notified about events is undefined.
30+
public func addTestObserver(testObserver: XCTestObservation) {
31+
observers.insert(testObserver.wrapper)
32+
}
33+
34+
/// Remove a previously-registered observer so that it will no longer receive
35+
/// event callbacks.
36+
public func removeTestObserver(testObserver: XCTestObservation) {
37+
observers.remove(testObserver.wrapper)
38+
}
39+
40+
41+
internal func testCaseWillStart(testCase: XCTestCase) {
42+
forEachObserver { $0.testCaseWillStart(testCase) }
43+
}
44+
45+
internal func testCase(testCase: XCTestCase, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: UInt) {
46+
forEachObserver { $0.testCase(testCase, didFailWithDescription: description, inFile: filePath, atLine: lineNumber) }
47+
}
48+
49+
internal func testCaseDidFinish(testCase: XCTestCase) {
50+
forEachObserver { $0.testCaseDidFinish(testCase) }
51+
}
52+
53+
private func forEachObserver(@noescape body: XCTestObservation -> Void) {
54+
for observer in observers {
55+
body(observer.object)
56+
}
57+
}
58+
}
59+
60+
private extension XCTestObservation {
61+
var wrapper: ObjectWrapper<XCTestObservation> {
62+
return ObjectWrapper(object: self, objectIdentifier: ObjectIdentifier(self))
63+
}
64+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// RUN: %{swiftc} %s -o %{built_tests_dir}/Observation
2+
// RUN: %{built_tests_dir}/Observation > %t || true
3+
// RUN: %{xctest_checker} %t %s
4+
5+
#if os(Linux) || os(FreeBSD)
6+
import XCTest
7+
#else
8+
import SwiftXCTest
9+
#endif
10+
11+
class Observer: XCTestObservation {
12+
var startedTestCaseNames = [String]()
13+
var failureDescriptions = [String]()
14+
var finishedTestCaseNames = [String]()
15+
16+
func testCaseWillStart(testCase: XCTestCase) {
17+
startedTestCaseNames.append(testCase.name)
18+
}
19+
20+
func testCase(testCase: XCTestCase, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: UInt) {
21+
failureDescriptions.append(description)
22+
}
23+
24+
func testCaseDidFinish(testCase: XCTestCase) {
25+
finishedTestCaseNames.append(testCase.name)
26+
}
27+
}
28+
29+
let observer = Observer()
30+
31+
class Observation: XCTestCase {
32+
static var allTests: [(String, Observation -> () throws -> Void)] {
33+
return [
34+
("test_one", test_one),
35+
("test_two", test_two),
36+
("test_three", test_three),
37+
]
38+
}
39+
40+
// CHECK: Test Case 'Observation.test_one' started.
41+
// CHECK: Test Case 'Observation.test_one' passed \(\d+\.\d+ seconds\).
42+
func test_one() {
43+
XCTAssertEqual(observer.startedTestCaseNames, [])
44+
XCTAssertEqual(observer.failureDescriptions, [])
45+
XCTAssertEqual(observer.finishedTestCaseNames, [])
46+
47+
XCTestObservationCenter.sharedTestObservationCenter().addTestObserver(observer)
48+
}
49+
50+
// CHECK: Test Case 'Observation.test_two' started.
51+
// CHECK: .*/Observation/main.swift:\d+: error: Observation.test_two : failed - fail!
52+
// CHECK: Test Case 'Observation.test_two' failed \(\d+\.\d+ seconds\).
53+
func test_two() {
54+
XCTAssertEqual(observer.startedTestCaseNames, ["Observation.test_two"])
55+
XCTAssertEqual(observer.finishedTestCaseNames,["Observation.test_one"])
56+
57+
XCTFail("fail!")
58+
XCTAssertEqual(observer.failureDescriptions, ["failed - fail!"])
59+
60+
XCTestObservationCenter.sharedTestObservationCenter().removeTestObserver(observer)
61+
}
62+
63+
// CHECK: Test Case 'Observation.test_three' started.
64+
// CHECK: Test Case 'Observation.test_three' passed \(\d+\.\d+ seconds\).
65+
func test_three() {
66+
XCTAssertEqual(observer.startedTestCaseNames, ["Observation.test_two"])
67+
XCTAssertEqual(observer.finishedTestCaseNames,["Observation.test_one"])
68+
}
69+
}
70+
71+
XCTMain([testCase(Observation.allTests)])
72+
73+
// CHECK: Executed 3 tests, with 1 failure \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
74+
// CHECK: Total executed 3 tests, with 1 failure \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds

XCTest.xcodeproj/project.pbxproj

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
AE7DD60C1C8F0513006FC722 /* XCTestObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE7DD60B1C8F0513006FC722 /* XCTestObservation.swift */; };
11+
AE9596DF1C96911F001A9EF0 /* ObjectWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE9596DE1C96911F001A9EF0 /* ObjectWrapper.swift */; };
12+
AE9596E11C9692B8001A9EF0 /* XCTestObservationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE9596E01C9692B8001A9EF0 /* XCTestObservationCenter.swift */; };
1013
C265F66F1C3AEB6A00520CF9 /* XCTAssert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C265F6691C3AEB6A00520CF9 /* XCTAssert.swift */; };
1114
C265F6701C3AEB6A00520CF9 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C265F66A1C3AEB6A00520CF9 /* XCTestCase.swift */; };
1215
C265F6721C3AEB6A00520CF9 /* XCTestMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = C265F66C1C3AEB6A00520CF9 /* XCTestMain.swift */; };
@@ -29,6 +32,9 @@
2932
/* Begin PBXFileReference section */
3033
5B5D86DB1BBC74AD00234F36 /* SwiftXCTest.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftXCTest.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3134
AE7DD6061C8DC6C0006FC722 /* Functional */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Functional; sourceTree = "<group>"; };
35+
AE7DD60B1C8F0513006FC722 /* XCTestObservation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestObservation.swift; sourceTree = "<group>"; };
36+
AE9596DE1C96911F001A9EF0 /* ObjectWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectWrapper.swift; sourceTree = "<group>"; };
37+
AE9596E01C9692B8001A9EF0 /* XCTestObservationCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestObservationCenter.swift; sourceTree = "<group>"; };
3238
B1384A411C1B3E8700EDF031 /* CONTRIBUTING.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = "<group>"; };
3339
B1384A421C1B3E8700EDF031 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
3440
B1384A431C1B3E8700EDF031 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@@ -97,10 +103,13 @@
97103
C265F6671C3AEB6A00520CF9 /* XCTest */ = {
98104
isa = PBXGroup;
99105
children = (
106+
AE9596DE1C96911F001A9EF0 /* ObjectWrapper.swift */,
100107
C265F6691C3AEB6A00520CF9 /* XCTAssert.swift */,
101108
C265F66A1C3AEB6A00520CF9 /* XCTestCase.swift */,
102-
C265F66C1C3AEB6A00520CF9 /* XCTestMain.swift */,
103109
DADB979B1C51BDA2005E68B6 /* XCTestExpectation.swift */,
110+
C265F66C1C3AEB6A00520CF9 /* XCTestMain.swift */,
111+
AE7DD60B1C8F0513006FC722 /* XCTestObservation.swift */,
112+
AE9596E01C9692B8001A9EF0 /* XCTestObservationCenter.swift */,
104113
C265F66D1C3AEB6A00520CF9 /* XCTimeUtilities.swift */,
105114
DACC94411C8B87B900EC85F5 /* XCWaitCompletionHandler.swift */,
106115
);
@@ -235,9 +244,12 @@
235244
files = (
236245
DACC94421C8B87B900EC85F5 /* XCWaitCompletionHandler.swift in Sources */,
237246
C265F6731C3AEB6A00520CF9 /* XCTimeUtilities.swift in Sources */,
247+
AE7DD60C1C8F0513006FC722 /* XCTestObservation.swift in Sources */,
238248
C265F6701C3AEB6A00520CF9 /* XCTestCase.swift in Sources */,
239249
DADB979C1C51BDA2005E68B6 /* XCTestExpectation.swift in Sources */,
250+
AE9596E11C9692B8001A9EF0 /* XCTestObservationCenter.swift in Sources */,
240251
C265F66F1C3AEB6A00520CF9 /* XCTAssert.swift in Sources */,
252+
AE9596DF1C96911F001A9EF0 /* ObjectWrapper.swift in Sources */,
241253
C265F6721C3AEB6A00520CF9 /* XCTestMain.swift in Sources */,
242254
);
243255
runOnlyForDeploymentPostprocessing = 0;

0 commit comments

Comments
 (0)