Skip to content

Commit

Permalink
Check for cancellation (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
reddavis authored Jun 10, 2022
1 parent 08e1ee0 commit 8ed33bc
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 50 deletions.
4 changes: 3 additions & 1 deletion Asynchrone/Source/Extensions/AsyncSequence+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,10 @@ extension AsyncSequence {
do {
for try await element in self {
await receiveValue(element)
try Task.checkCancellation()
}


try Task.checkCancellation()
await receiveCompletion(.finished)
} catch {
await receiveCompletion(.failure(error))
Expand Down
110 changes: 65 additions & 45 deletions AsynchroneTests/Supporting Files/Assertion.swift
Original file line number Diff line number Diff line change
@@ -1,74 +1,94 @@
import XCTest

/// Assert two async expressions are eventually equal.
/// Assert two expressions are eventually equal.
/// - Parameters:
/// - expressionA: Expression A
/// - expressionB: Expression B
/// - timeout: Time to wait for store state changes. Defaults to `5`
/// - file: The file where this assertion is being called. Defaults to `#filePath`.
/// - line: The line in the file where this assertion is being called. Defaults to `#line`.
public func XCTAssertEventuallyEqual<T: Equatable>(
func XCTAssertEventuallyEqual<T: Equatable>(
_ expressionA: @escaping @autoclosure () -> T?,
_ expressionB: @escaping @autoclosure () -> T?,
timeout: TimeInterval = 5.0,
file: StaticString = #filePath,
line: UInt = #line
) {
Task.detached(priority: .low) {
let timeoutDate = Date(timeIntervalSinceNow: timeout)

while true {
let resultA = expressionA()
let resultB = expressionB()

switch resultA == resultB {
// All good!
case true:
return
// False and timed out.
case false where Date().compare(timeoutDate) == .orderedDescending:
let error = XCTAssertEventuallyEqualError(
resultA: resultA,
resultB: resultB
)

XCTFail(
error.message,
file: file,
line: line
)
return
// False but still within timeout limit.
case false: ()
}

try? await Task.sleep(nanoseconds: 50000000)
await Task.yield()
) async {
let timeoutDate = Date(timeIntervalSinceNow: timeout)

while true {
let resultA = expressionA()
let resultB = expressionB()

switch resultA == resultB {
// All good!
case true:
return
// False and timed out.
case false where Date().compare(timeoutDate) == .orderedDescending:
let error = XCTAssertEventuallyEqualError(
resultA: resultA,
resultB: resultB
)

XCTFail(
error.message,
file: file,
line: line
)
return
// False but still within timeout limit.
case false:
try? await Task.sleep(nanoseconds: 10000000)
}
}
}

/// Assert a value is eventually true.
/// Assert two async expressions are eventually equal.
/// - Parameters:
/// - expression: The value to assert eventually is true.
/// - expressionA: Expression A
/// - expressionB: Expression B
/// - timeout: Time to wait for store state changes. Defaults to `5`
/// - file: The file where this assertion is being called. Defaults to `#filePath`.
/// - line: The line in the file where this assertion is being called. Defaults to `#line`.
public func XCTAssertEventuallyTrue(
_ expression: @escaping @autoclosure () -> Bool,
func XCTAsyncAssertEventuallyEqual<T: Equatable>(
_ expressionA: @escaping () async -> T?,
_ expressionB: @escaping () async -> T?,
timeout: TimeInterval = 5.0,
file: StaticString = #filePath,
line: UInt = #line
) {
XCTAssertEventuallyEqual(
expression(),
true,
timeout: timeout,
file: file,
line: line
)
) async {
let timeoutDate = Date(timeIntervalSinceNow: timeout)

while true {
let resultA = await expressionA()
let resultB = await expressionB()

switch resultA == resultB {
// All good!
case true:
return
// False and timed out.
case false where Date().compare(timeoutDate) == .orderedDescending:
let error = XCTAssertEventuallyEqualError(
resultA: resultA,
resultB: resultB
)

XCTFail(
error.message,
file: file,
line: line
)
return
// False but still within timeout limit.
case false:
try? await Task.sleep(nanoseconds: 10000000)
}
}
}


/// Assert an async closure thorws an error.
/// - Parameters:
/// - closure: The closure.
Expand Down
34 changes: 30 additions & 4 deletions AsynchroneTests/Tests/Extensions/AsyncSequenceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class AsyncSequenceTests: XCTestCase {

func testAssign() async {
self.sequence.assign(to: \.assignableValue, on: self)
XCTAssertEventuallyEqual(self.assignableValue, 3)
await XCTAssertEventuallyEqual(self.assignableValue, 3)
}

// MARK: First
Expand Down Expand Up @@ -50,7 +50,7 @@ final class AsyncSequenceTests: XCTestCase {
var values: [Int] = []
self.sequence.sink { values.append($0) }

XCTAssertEventuallyEqual(values, [1, 2, 3])
await XCTAssertEventuallyEqual(values, [1, 2, 3])
}

func testSinkWithFinishedCompletion() async {
Expand All @@ -66,7 +66,7 @@ final class AsyncSequenceTests: XCTestCase {
}
)

XCTAssertEventuallyEqual(values, [1, 2, 3])
await XCTAssertEventuallyEqual(values, [1, 2, 3])
}

func testSinkWithFailedCompletion() async {
Expand All @@ -92,6 +92,32 @@ final class AsyncSequenceTests: XCTestCase {
)

await self.waitForExpectations(timeout: 5.0, handler: nil)
XCTAssertEventuallyEqual(values, [1, 2, 3])
await XCTAssertEventuallyEqual(values, [1, 2, 3])
}

func testSinkWithCancellation() async {
let completionExpectation = self.expectation(description: "Completion called")
let sequence = AsyncThrowingStream<Int, Error> { continuation in
continuation.yield(1)
}

var values: [Int] = []
let task = sequence.sink(
receiveValue: { values.append($0) },
receiveCompletion: {
switch $0 {
case .failure(let error) where error is CancellationError:
completionExpectation.fulfill()
case .failure(let error):
XCTFail("Invalid failure error: \(error)")
case .finished:
XCTFail("Invalid completion case: Finished")
}
}
)

task.cancel()
await self.waitForExpectations(timeout: 5.0, handler: nil)
await XCTAssertEventuallyEqual(values, [1])
}
}

0 comments on commit 8ed33bc

Please sign in to comment.