Skip to content

Commit f12ca52

Browse files
authored
Basic swift testing support (#1154)
* Provide basic integration with Swift Testing This is not reliable, in fact, failure reporting is flaky. * Spike utilizing my changes to Swift Testing to allow direct recording of issues * link to my branch of swift testing enabling public issues * Support the current version of Swift Testing * Fix broken tests * Fix a broken test helper * Correct a long-broken test because expectFailureMessageRegex has been a no-op for ages
1 parent 3e40449 commit f12ca52

21 files changed

+448
-198
lines changed

Nimble.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@
135135
8923E60D2B47CE7E00F3961A /* Map.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8923E60C2B47CE7E00F3961A /* Map.swift */; };
136136
8923E6102B47D08300F3961A /* MapTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8923E60E2B47D06E00F3961A /* MapTest.swift */; };
137137
892FDF1329D3EA7700523A80 /* AsyncExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892FDF1229D3EA7700523A80 /* AsyncExpression.swift */; };
138+
895644DD2C1B63910006EC12 /* NimbleSwiftTestingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 895644DC2C1B63910006EC12 /* NimbleSwiftTestingHandler.swift */; };
139+
895644DF2C1B71DE0006EC12 /* SwiftTestingSupportTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 895644DE2C1B71DE0006EC12 /* SwiftTestingSupportTest.swift */; };
138140
896962412A5FABD000A7929D /* AsyncAllPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896962402A5FABD000A7929D /* AsyncAllPass.swift */; };
139141
8969624A2A5FAD5F00A7929D /* AsyncAllPassTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896962452A5FAD4500A7929D /* AsyncAllPassTest.swift */; };
140142
898F28B025D9F4C30052B8D0 /* AlwaysFailMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898F28AF25D9F4C30052B8D0 /* AlwaysFailMatcher.swift */; };
@@ -324,6 +326,8 @@
324326
8923E60E2B47D06E00F3961A /* MapTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTest.swift; sourceTree = "<group>"; };
325327
892FDF1229D3EA7700523A80 /* AsyncExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncExpression.swift; sourceTree = "<group>"; };
326328
8952ADDC2B4F159400D9305F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
329+
895644DC2C1B63910006EC12 /* NimbleSwiftTestingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbleSwiftTestingHandler.swift; sourceTree = "<group>"; };
330+
895644DE2C1B71DE0006EC12 /* SwiftTestingSupportTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTestingSupportTest.swift; sourceTree = "<group>"; };
327331
896962402A5FABD000A7929D /* AsyncAllPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAllPass.swift; sourceTree = "<group>"; };
328332
896962452A5FAD4500A7929D /* AsyncAllPassTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAllPassTest.swift; sourceTree = "<group>"; };
329333
898F28AF25D9F4C30052B8D0 /* AlwaysFailMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlwaysFailMatcher.swift; sourceTree = "<group>"; };
@@ -490,6 +494,7 @@
490494
89C297CB2A911CDA002A143F /* AsyncTimerSequenceTest.swift */,
491495
89C297CD2A92AB34002A143F /* AsyncPromiseTest.swift */,
492496
965B0D0B1B62C06D0005AE66 /* UserDescriptionTest.swift */,
497+
895644DE2C1B71DE0006EC12 /* SwiftTestingSupportTest.swift */,
493498
6CAEDD091CAEA86F003F1584 /* LinuxSupport.swift */,
494499
1F14FB61194180A7009F2A08 /* Helpers */,
495500
1F925EE3195C11B000ED456B /* Matchers */,
@@ -561,6 +566,7 @@
561566
89F5E090290B9D5C001F9377 /* AssertionRecorder+Async.swift */,
562567
1FC494A91C29CBA40010975C /* NimbleEnvironment.swift */,
563568
1FD8CD071968AB07008ED995 /* NimbleXCTestHandler.swift */,
569+
895644DC2C1B63910006EC12 /* NimbleSwiftTestingHandler.swift */,
564570
1F1871BA1CA89E2500A34BF2 /* NonObjectiveC */,
565571
1F1871C21CA89EDB00A34BF2 /* NMBExpectation.swift */,
566572
);
@@ -872,6 +878,7 @@
872878
CDF5C57B2647B89B0036532C /* Equal+Tuple.swift in Sources */,
873879
857D1849253610A900D8693A /* BeWithin.swift in Sources */,
874880
1FD8CD4D1968AB07008ED995 /* BeLessThan.swift in Sources */,
881+
895644DD2C1B63910006EC12 /* NimbleSwiftTestingHandler.swift in Sources */,
875882
1FD8CD471968AB07008ED995 /* BeGreaterThan.swift in Sources */,
876883
F8A1BE301CB3710900031679 /* XCTestObservationCenter+Register.m in Sources */,
877884
1FD8CD311968AB07008ED995 /* AdapterProtocols.swift in Sources */,
@@ -974,6 +981,7 @@
974981
1F4A568C1A3B3407009E1637 /* ObjCBeTrueTest.m in Sources */,
975982
DDEFAEB51A93CBE6005CA37A /* ObjCAllPassTest.m in Sources */,
976983
1F4A56801A3B333F009E1637 /* ObjCBeLessThanTest.m in Sources */,
984+
895644DF2C1B71DE0006EC12 /* SwiftTestingSupportTest.swift in Sources */,
977985
857D184F2536124400D8693A /* BeWithinTest.swift in Sources */,
978986
8922828F2B283956002DA355 /* AsyncAwaitTest+Require.swift in Sources */,
979987
1F0648CD19639F5A001F9C46 /* ObjectWithLazyProperty.swift in Sources */,

Sources/Nimble/Adapters/AdapterProtocols.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ public protocol AssertionHandler {
44
}
55

66
/// Global backing interface for assertions that Nimble creates.
7-
/// Defaults to a private test handler that passes through to XCTest.
7+
/// Defaults to a private test handler that passes through to Swift Testing or XCTest.
88
///
9-
/// If XCTest is not available, you must assign your own assertion handler
9+
/// If neither Swift Testing or XCTest is available, you must assign your own assertion handler
1010
/// before using any matchers, otherwise Nimble will abort the program.
1111
///
1212
/// @see AssertionHandler
1313
public var NimbleAssertionHandler: AssertionHandler = { () -> AssertionHandler in
1414
// swiftlint:disable:previous identifier_name
15-
return isXCTestAvailable() ? NimbleXCTestHandler() : NimbleXCTestUnavailableHandler()
15+
if isSwiftTestingAvailable() || isXCTestAvailable() {
16+
return NimbleTestingHandler()
17+
}
18+
19+
return NimbleTestingUnavailableHandler()
1620
}()

Sources/Nimble/Adapters/AssertionRecorder+Async.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
///
99
/// @see AssertionHandler
1010
public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
11-
file: FileString = #file,
11+
fileID: String = #fileID,
12+
file: FileString = #filePath,
1213
line: UInt = #line,
14+
column: UInt = #column,
1315
closure: () async throws -> Void) async {
1416
let environment = NimbleEnvironment.activeInstance
1517
let oldRecorder = environment.assertionHandler
@@ -23,7 +25,7 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
2325
} catch {
2426
let failureMessage = FailureMessage()
2527
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
26-
let location = SourceLocation(file: file, line: line)
28+
let location = SourceLocation(fileID: fileID, filePath: file, line: line, column: column)
2729
tempAssertionHandler.assert(false, message: failureMessage, location: location)
2830
}
2931
}

Sources/Nimble/Adapters/AssertionRecorder.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,10 @@ extension NMBExceptionCapture {
6363
///
6464
/// @see AssertionHandler
6565
public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
66-
file: FileString = #file,
66+
fileID: String = #fileID,
67+
file: FileString = #filePath,
6768
line: UInt = #line,
69+
column: UInt = #column,
6870
closure: () throws -> Void) {
6971
let environment = NimbleEnvironment.activeInstance
7072
let oldRecorder = environment.assertionHandler
@@ -80,7 +82,11 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
8082
} catch {
8183
let failureMessage = FailureMessage()
8284
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
83-
let location = SourceLocation(file: file, line: line)
85+
let location = SourceLocation(
86+
fileID: fileID,
87+
filePath: file,
88+
line: line, column: column
89+
)
8490
tempAssertionHandler.assert(false, message: failureMessage, location: location)
8591
}
8692
}

Sources/Nimble/Adapters/NMBExpectation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ public class NMBExpectation: NSObject {
174174
}
175175

176176
@objc public class func failWithMessage(_ message: String, file: FileString, line: UInt) {
177-
fail(message, location: SourceLocation(file: file, line: line))
177+
fail(message, location: SourceLocation(fileID: "Unknown/\(file)", filePath: file, line: line, column: 0))
178178
}
179179
}
180180

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Foundation
2+
#if canImport(Testing)
3+
import Testing
4+
#endif
5+
6+
public class NimbleSwiftTestingHandler: AssertionHandler {
7+
public func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
8+
if !assertion {
9+
recordTestingFailure("\(message.stringValue)\n", location: location)
10+
}
11+
}
12+
}
13+
14+
func isSwiftTestingAvailable() -> Bool {
15+
#if canImport(Testing)
16+
true
17+
#else
18+
false
19+
#endif
20+
}
21+
22+
func isRunningSwiftTest() -> Bool {
23+
#if canImport(Testing)
24+
Test.current != nil
25+
#else
26+
false
27+
#endif
28+
}
29+
30+
public func recordTestingFailure(_ message: String, location: SourceLocation) {
31+
#if canImport(Testing)
32+
let testingLocation = Testing.SourceLocation(
33+
fileID: location.fileID,
34+
filePath: "\(location.filePath)",
35+
line: Int(location.line),
36+
column: Int(location.column)
37+
)
38+
39+
Testing.Issue.record(
40+
"\(message)",
41+
sourceLocation: testingLocation
42+
)
43+
#endif
44+
}

Sources/Nimble/Adapters/NimbleXCTestHandler.swift

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import Foundation
22
import XCTest
33

4-
/// Default handler for Nimble. This assertion handler passes failures along to
5-
/// XCTest.
4+
/// Default handler for Nimble. This assertion handler passes on to Swift Testing or XCTest.
5+
public class NimbleTestingHandler: AssertionHandler {
6+
public func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
7+
if isRunningSwiftTest() {
8+
NimbleSwiftTestingHandler().assert(assertion, message: message, location: location)
9+
} else {
10+
NimbleXCTestHandler().assert(assertion, message: message, location: location)
11+
}
12+
}
13+
}
14+
15+
/// This assertion handler passes failures along to XCTest.
616
public class NimbleXCTestHandler: AssertionHandler {
717
public func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
818
if !assertion {
@@ -27,11 +37,11 @@ public class NimbleShortXCTestHandler: AssertionHandler {
2737
}
2838
}
2939

30-
/// Fallback handler in case XCTest is unavailable. This assertion handler will abort
40+
/// Fallback handler in case XCTest/Swift Testing is unavailable. This assertion handler will abort
3141
/// the program if it is invoked.
32-
class NimbleXCTestUnavailableHandler: AssertionHandler {
42+
class NimbleTestingUnavailableHandler: AssertionHandler {
3343
func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
34-
fatalError("XCTest is not available and no custom assertion handler was configured. Aborting.")
44+
fatalError("XCTest and Swift Testing are not available and no custom assertion handler was configured. Aborting.")
3545
}
3646
}
3747

@@ -78,15 +88,15 @@ public func recordFailure(_ message: String, location: SourceLocation) {
7888
#else
7989
if let testCase = CurrentTestCaseTracker.sharedInstance.currentTestCase {
8090
let line = Int(location.line)
81-
let location = XCTSourceCodeLocation(filePath: location.file, lineNumber: line)
91+
let location = XCTSourceCodeLocation(filePath: location.filePath, lineNumber: line)
8292
let sourceCodeContext = XCTSourceCodeContext(location: location)
8393
let issue = XCTIssue(type: .assertionFailure, compactDescription: message, sourceCodeContext: sourceCodeContext)
8494
testCase.record(issue)
8595
} else {
8696
let msg = """
8797
Attempted to report a test failure to XCTest while no test case was running. The failure was:
8898
\"\(message)\"
89-
It occurred at: \(location.file):\(location.line)
99+
It occurred at: \(location)
90100
"""
91101
NSException(name: .internalInconsistencyException, reason: msg, userInfo: nil).raise()
92102
}

0 commit comments

Comments
 (0)