Skip to content

Commit

Permalink
Forward to Swift runtime assertions if nothing is injected (#8)
Browse files Browse the repository at this point in the history
Co-authored-by: Paul Schmiedmayer <[email protected]>
  • Loading branch information
Supereg and PSchmiedmayer authored Jul 19, 2023
1 parent 90d3291 commit 9226052
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 43 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ XCTRuntimeAssertions contributors
====================

* [Paul Schmiedmayer](https://github.com/PSchmiedmayer)
* [Andreas Bauer](https://github.com/Supereg)
2 changes: 1 addition & 1 deletion Sources/XCTRuntimeAssertions/Counter.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// This source file is part of the Stanford XCTRuntimeAssertions open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//
Expand Down
2 changes: 1 addition & 1 deletion Sources/XCTRuntimeAssertions/NeverReturn.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// This source file is part of the Stanford XCTRuntimeAssertions open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//
Expand Down
47 changes: 25 additions & 22 deletions Sources/XCTRuntimeAssertions/XCTRuntimeAssertion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ import Foundation

/// `XCTRuntimeAssertion` allows you to test assertions of types that use the `assert` and `assertionFailure` functions of the `XCTRuntimeAssertions` target.
/// - Parameters:
/// - validateRuntimeAssertion: An optional closure that can be used to furhter validate the messages passed to the
/// - validateRuntimeAssertion: An optional closure that can be used to further validate the messages passed to the
/// `assert` and `assertionFailure` functions of the `XCTRuntimeAssertions` target.
/// - expectedFulfillmentCount: The expected fulfillment count on how often the `assert` and `assertionFailure` functions of
/// the `XCTRuntimeAssertions` target are called. The defailt value is 1.
/// the `XCTRuntimeAssertions` target are called. The default value is 1. The value must be non-zero.
/// - message: A message that is posted on failure.
/// - file: The file where the failure occurs. The default is the filename of the test case where you call this function.
/// - line: The line number where the failure occurs. The default is the line number where you call this function.
/// - expression: The expression that is evaluated.
/// - Throws: Throws an `XCTFail` error if the expection does not trigger a runtime assertion with the parameters defined above.
/// - Throws: Throws an `XCTFail` error if the expression does not trigger a runtime assertion with the parameters defined above.
/// - Returns: The value of the function if it did not throw an error as it did not trigger a runtime assertion with the parameters defined above.
public func XCTRuntimeAssertion<T>(
validateRuntimeAssertion: ((String) -> Void)? = nil,
expectedFulfillmentCount: Int = 1,
expectedFulfillmentCount: UInt = 1,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line,
Expand All @@ -35,24 +35,24 @@ public func XCTRuntimeAssertion<T>(
fulfillmentCount: fulfillmentCount,
validateRuntimeAssertion: validateRuntimeAssertion
)

var result: Result<T, Error>
do {
result = .success(try expression())
} catch {
result = .failure(error)
}

XCTRuntimeAssertionInjector.removeRuntimeAssertionInjector(withId: xctRuntimeAssertionId)

try assertFulfillmentCount(
fulfillmentCount,
expectedFulfillmentCount: expectedFulfillmentCount,
message,
file: file,
line: line
)

switch result {
case let .success(returnValue):
return returnValue
Expand All @@ -63,19 +63,19 @@ public func XCTRuntimeAssertion<T>(

/// `XCTRuntimeAssertion` allows you to test async assertions of types that use the `assert` and `assertionFailure` functions of the `XCTRuntimeAssertions` target.
/// - Parameters:
/// - validateRuntimeAssertion: An optional closure that can be used to furhter validate the messages passed to the
/// - validateRuntimeAssertion: An optional closure that can be used to further validate the messages passed to the
/// `assert` and `assertionFailure` functions of the `XCTRuntimeAssertions` target.
/// - expectedFulfillmentCount: The expected fulfillment count on how often the `assert` and `assertionFailure` functions of
/// the `XCTRuntimeAssertions` target are called. The defailt value is 1.
/// the `XCTRuntimeAssertions` target are called. The default value is 1. The value must be non-zero.
/// - message: A message that is posted on failure.
/// - file: The file where the failure occurs. The default is the filename of the test case where you call this function.
/// - line: The line number where the failure occurs. The default is the line number where you call this function.
/// - expression: The async expression that is evaluated.
/// - Throws: Throws an `XCTFail` error if the expection does not trigger a runtime assertion with the parameters defined above.
/// - Throws: Throws an `XCTFail` error if the expression does not trigger a runtime assertion with the parameters defined above.
/// - Returns: The value of the function if it did not throw an error as it did not trigger a runtime assertion with the parameters defined above.
public func XCTRuntimeAssertion<T>(
validateRuntimeAssertion: ((String) -> Void)? = nil,
expectedFulfillmentCount: Int = 1,
expectedFulfillmentCount: UInt = 1,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line,
Expand All @@ -86,24 +86,24 @@ public func XCTRuntimeAssertion<T>(
fulfillmentCount: fulfillmentCount,
validateRuntimeAssertion: validateRuntimeAssertion
)

var result: Result<T, Error>
do {
result = .success(try await expression())
} catch {
result = .failure(error)
}

XCTRuntimeAssertionInjector.removeRuntimeAssertionInjector(withId: xctRuntimeAssertionId)

try assertFulfillmentCount(
fulfillmentCount,
expectedFulfillmentCount: expectedFulfillmentCount,
message,
file: file,
line: line
)

switch result {
case let .success(returnValue):
return returnValue
Expand All @@ -115,7 +115,7 @@ public func XCTRuntimeAssertion<T>(

private func setupXCTRuntimeAssertionInjector(fulfillmentCount: Counter, validateRuntimeAssertion: ((String) -> Void)? = nil) -> UUID {
let xctRuntimeAssertionId = UUID()

XCTRuntimeAssertionInjector.inject(
runtimeAssertionInjector: XCTRuntimeAssertionInjector(
id: xctRuntimeAssertionId,
Expand All @@ -133,23 +133,26 @@ private func setupXCTRuntimeAssertionInjector(fulfillmentCount: Counter, validat
}
)
)

return xctRuntimeAssertionId
}


private func assertFulfillmentCount(
_ fulfillmentCount: Counter,
expectedFulfillmentCount: Int,
expectedFulfillmentCount: UInt,
_ message: () -> String,
file: StaticString,
line: UInt
) throws {
Swift.precondition(expectedFulfillmentCount > 0, "expectedFulfillmentCount has to be non-zero!")

if fulfillmentCount.counter != expectedFulfillmentCount {
throw XCTFail(
message: """
Measured an fulfillment count of \(fulfillmentCount.counter), expected \(expectedFulfillmentCount).
\(message()) at \(file):\(line)
"""
Measured an fulfillment count of \(fulfillmentCount.counter), expected \(expectedFulfillmentCount).
\(message()) at \(file):\(line)
"""
)
}
}
Expand Down
24 changes: 16 additions & 8 deletions Sources/XCTRuntimeAssertions/XCTRuntimeAssertionInjector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ class XCTRuntimeAssertionInjector {
) {
self.id = id
self._assert = assert
self._precondition = { _, condition, messsage, file, line in
Swift.precondition(condition(), messsage(), file: file, line: line)
self._precondition = { _, condition, message, file, line in
Swift.precondition(condition(), message(), file: file, line: line)
}
}

Expand All @@ -45,8 +45,8 @@ class XCTRuntimeAssertionInjector {
precondition: @escaping (UUID, () -> Bool, () -> String, StaticString, UInt) -> Void
) {
self.id = id
self._assert = { _, condition, messsage, file, line in
Swift.assert(condition(), messsage(), file: file, line: line)
self._assert = { _, condition, message, file, line in
Swift.assert(condition(), message(), file: file, line: line)
}
self._precondition = precondition
}
Expand All @@ -55,11 +55,11 @@ class XCTRuntimeAssertionInjector {
id: UUID
) {
self.id = id
self._assert = { _, condition, messsage, file, line in
Swift.assert(condition(), messsage(), file: file, line: line)
self._assert = { _, condition, message, file, line in
Swift.assert(condition(), message(), file: file, line: line)
}
self._precondition = { _, condition, messsage, file, line in
Swift.precondition(condition(), messsage(), file: file, line: line)
self._precondition = { _, condition, message, file, line in
Swift.precondition(condition(), message(), file: file, line: line)
}
}

Expand All @@ -74,12 +74,20 @@ class XCTRuntimeAssertionInjector {


static func assert(_ condition: () -> Bool, message: () -> String, file: StaticString, line: UInt) {
if injected.isEmpty {
Swift.assert(condition(), message(), file: file, line: line)
}

for runtimeAssertionInjector in injected {
runtimeAssertionInjector._assert(runtimeAssertionInjector.id, condition, message, file, line)
}
}

static func precondition(_ condition: () -> Bool, message: () -> String, file: StaticString, line: UInt) {
if injected.isEmpty {
Swift.precondition(condition(), message(), file: file, line: line)
}

for runtimeAssertionInjector in injected {
runtimeAssertionInjector._precondition(runtimeAssertionInjector.id, condition, message, file, line)
}
Expand Down
16 changes: 8 additions & 8 deletions Sources/XCTRuntimeAssertions/XCTRuntimePrecondition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import Foundation

/// `XCTRuntimePrecondition` allows you to test assertions of types that use the `precondition` and `preconditionFailure` functions of the `XCTRuntimeAssertions` target.
/// - Parameters:
/// - validateRuntimeAssertion: An optional closure that can be used to furhter validate the messages passed to the
/// - validateRuntimeAssertion: An optional closure that can be used to further validate the messages passed to the
/// `precondition` and `preconditionFailure` functions of the `XCTRuntimeAssertions` target.
/// - timeout: A timeout defining how long to wait for the precondition to be triggered.
/// - message: A message that is posted on failure.
/// - file: The file where the failure occurs. The default is the filename of the test case where you call this function.
/// - line: The line number where the failure occurs. The default is the line number where you call this function.
/// - expression: The expression that is evaluated.
/// - Throws: Throws an `XCTFail` error if the expection does not trigger a runtime assertion with the parameters defined above.
/// - Throws: Throws an `XCTFail` error if the expression does not trigger a runtime assertion with the parameters defined above.
public func XCTRuntimePrecondition(
validateRuntimeAssertion: ((String) -> Void)? = nil,
timeout: Double = 0.01,
Expand Down Expand Up @@ -48,26 +48,26 @@ public func XCTRuntimePrecondition(
usleep(useconds_t(1_000_000 * timeout))
expressionWorkItem.cancel()

XCTRuntimeAssertionInjector.removeRuntimeAssertionInjector(withId: xctRuntimeAssertionId)

try assertFulfillmentCount(
fulfillmentCount,
message,
file: file,
line: line
)

XCTRuntimeAssertionInjector.removeRuntimeAssertionInjector(withId: xctRuntimeAssertionId)
}

/// `XCTRuntimePrecondition` allows you to test async assertions of types that use the `precondition` and `preconditionFailure` functions of the `XCTRuntimeAssertions` target.
/// - Parameters:
/// - validateRuntimeAssertion: An optional closure that can be used to furhter validate the messages passed to the
/// - validateRuntimeAssertion: An optional closure that can be used to further validate the messages passed to the
/// `precondition` and `preconditionFailure` functions of the `XCTRuntimeAssertions` target.
/// - timeout: A timeout defining how long to wait for the precondition to be triggered.
/// - message: A message that is posted on failure.
/// - file: The file where the failure occurs. The default is the filename of the test case where you call this function.
/// - line: The line number where the failure occurs. The default is the line number where you call this function.
/// - expression: The async expression that is evaluated.
/// - Throws: Throws an `XCTFail` error if the expection does not trigger a runtime assertion with the parameters defined above.
/// - Throws: Throws an `XCTFail` error if the expression does not trigger a runtime assertion with the parameters defined above.
public func XCTRuntimePrecondition(
validateRuntimeAssertion: ((String) -> Void)? = nil,
timeout: Double = 0.01,
Expand All @@ -92,14 +92,14 @@ public func XCTRuntimePrecondition(
usleep(useconds_t(1_000_000 * timeout))
task.cancel()

XCTRuntimeAssertionInjector.removeRuntimeAssertionInjector(withId: xctRuntimeAssertionId)

try assertFulfillmentCount(
fulfillmentCount,
message,
file: file,
line: line
)

XCTRuntimeAssertionInjector.removeRuntimeAssertionInjector(withId: xctRuntimeAssertionId)
}


Expand Down
13 changes: 12 additions & 1 deletion Tests/XCTRuntimeAssertionsTests/XCTRuntimeAssertionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ final class XCTRuntimeAssertionsTests: XCTestCase {
await fulfillment(of: [expectation], timeout: 0.1)
}

func testXCTRuntimeAssertationNotTriggered() throws {
func testXCTRuntimeAssertionNotTriggered() throws {
struct XCTRuntimeAssertionNotTriggeredError: Error {}

do {
Expand All @@ -95,4 +95,15 @@ final class XCTRuntimeAssertionsTests: XCTestCase {
XCTAssertTrue(error.description.contains("Measured an fulfillment count of 0, expected 1."))
}
}

func testCallHappensWithoutInjection() {
var called = false

assert({
called = true
return true
}(), "This could fail")

XCTAssertTrue(called, "assert was never called!")
}
}
15 changes: 13 additions & 2 deletions Tests/XCTRuntimeAssertionsTests/XCTRuntimePreconditionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class XCTRuntimePreconditionsTests: XCTestCase {
) {
precondition(number != 42, "preconditionFailure()")
}

wait(for: [expectation], timeout: 0.01)


Expand Down Expand Up @@ -78,7 +78,18 @@ final class XCTRuntimePreconditionsTests: XCTestCase {
}
} catch let error as XCTFail {
try? await Task.sleep(for: .seconds(0.5))
XCTAssertTrue(error.description.contains("The precondition was never called."))
XCTAssertTrue(error.description.contains("The precondition was called multiple times."))
}
}

func testCallHappensWithoutInjection() {
var called = false

precondition({
called = true
return true
}(), "This could fail")

XCTAssertTrue(called, "precondition was never called!")
}
}

0 comments on commit 9226052

Please sign in to comment.