Skip to content

Commit

Permalink
Spike utilizing my changes to Swift Testing to allow direct recording…
Browse files Browse the repository at this point in the history
… of issues
  • Loading branch information
younata committed Sep 9, 2024
1 parent b16e122 commit d07409f
Show file tree
Hide file tree
Showing 16 changed files with 335 additions and 181 deletions.
6 changes: 4 additions & 2 deletions Sources/Nimble/Adapters/AssertionRecorder+Async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
///
/// @see AssertionHandler
public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
file: FileString = #file,
fileID: String = #fileID,
file: FileString = #filePath,
line: UInt = #line,
column: UInt = #column,
closure: () async throws -> Void) async {
let environment = NimbleEnvironment.activeInstance
let oldRecorder = environment.assertionHandler
Expand All @@ -23,7 +25,7 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
} catch {
let failureMessage = FailureMessage()
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
let location = SourceLocation(file: file, line: line)
let location = SourceLocation(fileID: fileID, filePath: file, line: line, column: column)
tempAssertionHandler.assert(false, message: failureMessage, location: location)
}
}
Expand Down
10 changes: 8 additions & 2 deletions Sources/Nimble/Adapters/AssertionRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ extension NMBExceptionCapture {
///
/// @see AssertionHandler
public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
file: FileString = #file,
fileID: String = #fileID,
file: FileString = #filePath,
line: UInt = #line,
column: UInt = #column,
closure: () throws -> Void) {
let environment = NimbleEnvironment.activeInstance
let oldRecorder = environment.assertionHandler
Expand All @@ -80,7 +82,11 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
} catch {
let failureMessage = FailureMessage()
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
let location = SourceLocation(file: file, line: line)
let location = SourceLocation(
fileID: fileID,
filePath: file,
line: line, column: column
)
tempAssertionHandler.assert(false, message: failureMessage, location: location)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Nimble/Adapters/NMBExpectation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ public class NMBExpectation: NSObject {
}

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

Expand Down
21 changes: 20 additions & 1 deletion Sources/Nimble/Adapters/NimbleSwiftTestingHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ func isRunningSwiftTest() -> Bool {

public func recordTestingFailure(_ message: String, location: SourceLocation) {
#if canImport(Testing)
Issue.record("\(message)", filePath: "\(location.file)", line: Int(location.line), column: 0)
let testingLocation = Testing.SourceLocation(
fileID: location.fileID,
filePath: "\(location.filePath)",
line: Int(location.line),
column: Int(location.column)
)

let issue = Testing.Issue(
kind: .expectationFailed(
Testing.Expectation(
mismatchedErrorDescription: message,
differenceDescription: nil,
isPassing: false,
isRequired: false,
sourceLocation: testingLocation
)
),
sourceContext: SourceContext(sourceLocation: testingLocation)
)
issue.record()
#endif
}
4 changes: 2 additions & 2 deletions Sources/Nimble/Adapters/NimbleXCTestHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@ public func recordFailure(_ message: String, location: SourceLocation) {
#else
if let testCase = CurrentTestCaseTracker.sharedInstance.currentTestCase {
let line = Int(location.line)
let location = XCTSourceCodeLocation(filePath: location.file, lineNumber: line)
let location = XCTSourceCodeLocation(filePath: location.filePath, lineNumber: line)
let sourceCodeContext = XCTSourceCodeContext(location: location)
let issue = XCTIssue(type: .assertionFailure, compactDescription: message, sourceCodeContext: sourceCodeContext)
testCase.record(issue)
} else {
let msg = """
Attempted to report a test failure to XCTest while no test case was running. The failure was:
\"\(message)\"
It occurred at: \(location.file):\(location.line)
It occurred at: \(location)
"""
NSException(name: .internalInconsistencyException, reason: msg, userInfo: nil).raise()
}
Expand Down
98 changes: 70 additions & 28 deletions Sources/Nimble/DSL+AsyncAwait.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,78 +3,78 @@ import Dispatch
#endif

/// Make an ``AsyncExpectation`` on a given actual value. The value given is lazily evaluated.
public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression: @escaping () async throws -> T?) -> AsyncExpectation<T> {
public func expect<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, _ expression: @escaping () async throws -> T?) -> AsyncExpectation<T> {
return AsyncExpectation(
expression: AsyncExpression(
expression: expression,
location: SourceLocation(file: file, line: line),
location: SourceLocation(fileID: fileID, filePath: file, line: line, column: column),
isClosure: true))
}

/// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked.
public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression: () -> (() async throws -> T)) -> AsyncExpectation<T> {
public func expect<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, _ expression: () -> (() async throws -> T)) -> AsyncExpectation<T> {
return AsyncExpectation(
expression: AsyncExpression(
expression: expression(),
location: SourceLocation(file: file, line: line),
location: SourceLocation(fileID: fileID, filePath: file, line: line, column: column),
isClosure: true))
}

/// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked.
public func expect<T>(file: FileString = #file, line: UInt = #line, _ expression: () -> (() async throws -> T?)) -> AsyncExpectation<T> {
public func expect<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, _ expression: () -> (() async throws -> T?)) -> AsyncExpectation<T> {
return AsyncExpectation(
expression: AsyncExpression(
expression: expression(),
location: SourceLocation(file: file, line: line),
location: SourceLocation(fileID: fileID, filePath: file, line: line, column: column),
isClosure: true))
}

/// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked.
public func expect(file: FileString = #file, line: UInt = #line, _ expression: () -> (() async throws -> Void)) -> AsyncExpectation<Void> {
public func expect(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, _ expression: () -> (() async throws -> Void)) -> AsyncExpectation<Void> {
return AsyncExpectation(
expression: AsyncExpression(
expression: expression(),
location: SourceLocation(file: file, line: line),
location: SourceLocation(fileID: fileID, filePath: file, line: line, column: column),
isClosure: true))
}

/// Make an ``AsyncExpectation`` on a given actual value. The value given is lazily evaluated.
/// This is provided to avoid confusion between `expect -> SyncExpectation` and `expect -> AsyncExpectation`.
public func expecta<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping () async throws -> T?) async -> AsyncExpectation<T> {
public func expecta<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, _ expression: @autoclosure @escaping () async throws -> T?) async -> AsyncExpectation<T> {
return AsyncExpectation(
expression: AsyncExpression(
expression: expression,
location: SourceLocation(file: file, line: line),
location: SourceLocation(fileID: fileID, filePath: file, line: line, column: column),
isClosure: true))
}

/// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked.
/// This is provided to avoid confusion between `expect -> SyncExpectation` and `expect -> AsyncExpectation`
public func expecta<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() async throws -> T)) async -> AsyncExpectation<T> {
public func expecta<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, _ expression: @autoclosure () -> (() async throws -> T)) async -> AsyncExpectation<T> {
return AsyncExpectation(
expression: AsyncExpression(
expression: expression(),
location: SourceLocation(file: file, line: line),
location: SourceLocation(fileID: fileID, filePath: file, line: line, column: column),
isClosure: true))
}

/// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked.
/// This is provided to avoid confusion between `expect -> SyncExpectation` and `expect -> AsyncExpectation`
public func expecta<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() async throws -> T?)) async -> AsyncExpectation<T> {
public func expecta<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, _ expression: @autoclosure () -> (() async throws -> T?)) async -> AsyncExpectation<T> {
return AsyncExpectation(
expression: AsyncExpression(
expression: expression(),
location: SourceLocation(file: file, line: line),
location: SourceLocation(fileID: fileID, filePath: file, line: line, column: column),
isClosure: true))
}

/// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked.
/// This is provided to avoid confusion between `expect -> SyncExpectation` and `expect -> AsyncExpectation`
public func expecta(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() async throws -> Void)) async -> AsyncExpectation<Void> {
public func expecta(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, _ expression: @autoclosure () -> (() async throws -> Void)) async -> AsyncExpectation<Void> {
return AsyncExpectation(
expression: AsyncExpression(
expression: expression(),
location: SourceLocation(file: file, line: line),
location: SourceLocation(fileID: fileID, filePath: file, line: line, column: column),
isClosure: true))
}

Expand All @@ -87,8 +87,18 @@ public func expecta(file: FileString = #file, line: UInt = #line, _ expression:
///
/// @warning
/// Unlike the synchronous version of this call, this does not support catching Objective-C exceptions.
public func waitUntil(timeout: NimbleTimeInterval = PollingDefaults.timeout, file: FileString = #file, line: UInt = #line, action: @escaping (@escaping () -> Void) async -> Void) async {
await throwableUntil(timeout: timeout) { done in
public func waitUntil(
timeout: NimbleTimeInterval = PollingDefaults.timeout,
fileID: String = #fileID,
file: FileString = #filePath,
line: UInt = #line,
column: UInt = #column,
action: @escaping (@escaping () -> Void) async -> Void
) async {
await throwableUntil(
timeout: timeout,
sourceLocation: SourceLocation(fileID: fileID, filePath: file, line: line, column: column)
) { done in
await action(done)
}
}
Expand All @@ -100,8 +110,18 @@ public func waitUntil(timeout: NimbleTimeInterval = PollingDefaults.timeout, fil
///
/// @warning
/// Unlike the synchronous version of this call, this does not support catching Objective-C exceptions.
public func waitUntil(timeout: NimbleTimeInterval = PollingDefaults.timeout, file: FileString = #file, line: UInt = #line, action: @escaping (@escaping () -> Void) -> Void) async {
await throwableUntil(timeout: timeout, file: file, line: line) { done in
public func waitUntil(
timeout: NimbleTimeInterval = PollingDefaults.timeout,
fileID: String = #fileID,
file: FileString = #filePath,
line: UInt = #line,
column: UInt = #column,
action: @escaping (@escaping () -> Void) -> Void
) async {
await throwableUntil(
timeout: timeout,
sourceLocation: SourceLocation(fileID: fileID, filePath: file, line: line, column: column)
) { done in
action(done)
}
}
Expand All @@ -113,14 +133,13 @@ private enum ErrorResult {

private func throwableUntil(
timeout: NimbleTimeInterval,
file: FileString = #file,
line: UInt = #line,
sourceLocation: SourceLocation,
action: @escaping (@escaping () -> Void) async throws -> Void) async {
let leeway = timeout.divided
let result = await performBlock(
timeoutInterval: timeout,
leeway: leeway,
file: file, line: line) { @MainActor (done: @escaping (ErrorResult) -> Void) async throws -> Void in
sourceLocation: sourceLocation) { @MainActor (done: @escaping (ErrorResult) -> Void) async throws -> Void in
do {
try await action {
done(.none)
Expand All @@ -133,14 +152,37 @@ private func throwableUntil(
switch result {
case .incomplete: internalError("Reached .incomplete state for waitUntil(...).")
case .blockedRunLoop:
fail(blockedRunLoopErrorMessageFor("-waitUntil()", leeway: leeway),
file: file, line: line)
fail(
blockedRunLoopErrorMessageFor("-waitUntil()", leeway: leeway),
fileID: sourceLocation.fileID,
file: sourceLocation.filePath,
line: sourceLocation.line,
column: sourceLocation.column
)
case .timedOut:
fail("Waited more than \(timeout.description)", file: file, line: line)
fail(
"Waited more than \(timeout.description)",
fileID: sourceLocation.fileID,
file: sourceLocation.filePath,
line: sourceLocation.line,
column: sourceLocation.column
)
case let .errorThrown(error):
fail("Unexpected error thrown: \(error)")
fail(
"Unexpected error thrown: \(error)",
fileID: sourceLocation.fileID,
file: sourceLocation.filePath,
line: sourceLocation.line,
column: sourceLocation.column
)
case .completed(.error(let error)):
fail("Unexpected error thrown: \(error)")
fail(
"Unexpected error thrown: \(error)",
fileID: sourceLocation.fileID,
file: sourceLocation.filePath,
line: sourceLocation.line,
column: sourceLocation.column
)
case .completed(.none): // success
break
}
Expand Down
Loading

0 comments on commit d07409f

Please sign in to comment.