From afd55a66ca85e50deedd07b8eede34a373619d8d Mon Sep 17 00:00:00 2001 From: Thibault Wittemberg Date: Mon, 18 Jul 2022 09:54:54 +0200 Subject: [PATCH 1/2] adjacentPairs: improve unit tests --- .../TestAdjacentPairs.swift | 113 ++++++++++++------ 1 file changed, 75 insertions(+), 38 deletions(-) diff --git a/Tests/AsyncAlgorithmsTests/TestAdjacentPairs.swift b/Tests/AsyncAlgorithmsTests/TestAdjacentPairs.swift index ade9c8f4..3bcd73cf 100644 --- a/Tests/AsyncAlgorithmsTests/TestAdjacentPairs.swift +++ b/Tests/AsyncAlgorithmsTests/TestAdjacentPairs.swift @@ -13,48 +13,85 @@ import AsyncAlgorithms final class TestAdjacentPairs: XCTestCase { - func test_adjacentPairs() async { - let source = 1...5 - let expected = [(1,2), (2,3), (3,4), (4,5)] - let sequence = source.async.adjacentPairs() - var actual: [(Int, Int)] = [] - for await item in sequence { - actual.append(item) - } - XCTAssertEqual(expected, actual) + func test_adjacentPairs_produces_tuples_of_adjacent_values_of_original_element() async { + let source = 1...5 + let expected = Array(zip(source, source.dropFirst())) + + let sequence = source.async.adjacentPairs() + var actual: [(Int, Int)] = [] + for await item in sequence { + actual.append(item) } - func test_empty() async { - let source = 0..<1 - let expected: [(Int, Int)] = [] - let sequence = source.async.adjacentPairs() - var actual: [(Int, Int)] = [] - for await item in sequence { - actual.append(item) - } - XCTAssertEqual(expected, actual) + XCTAssertEqual(expected, actual) + } + + func test_adjacentPairs_forwards_termination_from_source_when_iteration_is_finished() async { + let source = 1...5 + + var iterator = source.async.adjacentPairs().makeAsyncIterator() + while let _ = await iterator.next() {} + + let pastEnd = await iterator.next() + XCTAssertNil(pastEnd) + } + + func test_adjacentPairs_produces_empty_sequence_when_source_sequence_is_empty() async { + let source = 0..<1 + let expected: [(Int, Int)] = [] + + let sequence = source.async.adjacentPairs() + var actual: [(Int, Int)] = [] + for await item in sequence { + actual.append(item) } - func test_cancellation() async { - let source = Indefinite(value: 0) - let sequence = source.async.adjacentPairs() - let finished = expectation(description: "finished") - let iterated = expectation(description: "iterated") - let task = Task { - var firstIteration = false - for await _ in sequence { - if !firstIteration { - firstIteration = true - iterated.fulfill() - } - } - finished.fulfill() + XCTAssertEqual(expected, actual) + } + + func test_adjacentPairs_throws_when_source_sequence_throws() async throws { + let source = 1...5 + let expected = [(1, 2), (2, 3)] + + let sequence = source.async.map { try throwOn(4, $0) }.adjacentPairs() + var iterator = sequence.makeAsyncIterator() + var actual = [(Int, Int)]() + do { + while let value = try await iterator.next() { + actual.append(value) + } + XCTFail(".adjacentPairs should throw when the source sequence throws") + } catch { + XCTAssertEqual(error as? Failure, Failure()) + } + + XCTAssertEqual(actual, expected) + let pastEnd = try await iterator.next() + XCTAssertNil(pastEnd) + } + + func test_adjacentPairs_finishes_when_iteration_task_is_cancelled() async { + let source = Indefinite(value: 0) + let sequence = source.async.adjacentPairs() + let finished = expectation(description: "finished") + let iterated = expectation(description: "iterated") + + let task = Task { + var firstIteration = false + for await _ in sequence { + if !firstIteration { + firstIteration = true + iterated.fulfill() } - // ensure the other task actually starts - wait(for: [iterated], timeout: 1.0) - // cancellation should ensure the loop finishes - // without regards to the remaining underlying sequence - task.cancel() - wait(for: [finished], timeout: 1.0) + } + finished.fulfill() } + + // ensure the other task actually starts + wait(for: [iterated], timeout: 1.0) + // cancellation should ensure the loop finishes + // without regards to the remaining underlying sequence + task.cancel() + wait(for: [finished], timeout: 1.0) + } } From 5aafcbb2c94af39db21cac56cb4493b456c5299a Mon Sep 17 00:00:00 2001 From: Thibault Wittemberg Date: Mon, 18 Jul 2022 09:58:31 +0200 Subject: [PATCH 2/2] adjacentPairs: conform indentation to project standards --- .../AsyncAdjacentPairsSequence.swift | 108 +++++++++--------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/Sources/AsyncAlgorithms/AsyncAdjacentPairsSequence.swift b/Sources/AsyncAlgorithms/AsyncAdjacentPairsSequence.swift index 2186e364..7a69bf35 100644 --- a/Sources/AsyncAlgorithms/AsyncAdjacentPairsSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncAdjacentPairsSequence.swift @@ -13,74 +13,74 @@ /// `AsyncSequence`. @frozen public struct AsyncAdjacentPairsSequence: AsyncSequence { - public typealias Element = (Base.Element, Base.Element) + public typealias Element = (Base.Element, Base.Element) - @usableFromInline - let base: Base + @usableFromInline + let base: Base - @inlinable - init(_ base: Base) { - self.base = base - } + @inlinable + init(_ base: Base) { + self.base = base + } - /// The iterator for an `AsyncAdjacentPairsSequence` instance. - @frozen - public struct Iterator: AsyncIteratorProtocol { - public typealias Element = (Base.Element, Base.Element) + /// The iterator for an `AsyncAdjacentPairsSequence` instance. + @frozen + public struct Iterator: AsyncIteratorProtocol { + public typealias Element = (Base.Element, Base.Element) - @usableFromInline - var base: Base.AsyncIterator + @usableFromInline + var base: Base.AsyncIterator - @usableFromInline - internal var previousElement: Base.Element? + @usableFromInline + internal var previousElement: Base.Element? - @inlinable - init(_ base: Base.AsyncIterator) { - self.base = base - } + @inlinable + init(_ base: Base.AsyncIterator) { + self.base = base + } - @inlinable - public mutating func next() async rethrows -> (Base.Element, Base.Element)? { - if previousElement == nil { - previousElement = try await base.next() - } + @inlinable + public mutating func next() async rethrows -> (Base.Element, Base.Element)? { + if previousElement == nil { + previousElement = try await base.next() + } - guard let previous = previousElement, let next = try await base.next() else { - return nil - } + guard let previous = previousElement, let next = try await base.next() else { + return nil + } - previousElement = next - return (previous, next) - } + previousElement = next + return (previous, next) } + } - @inlinable - public func makeAsyncIterator() -> Iterator { - Iterator(base.makeAsyncIterator()) - } + @inlinable + public func makeAsyncIterator() -> Iterator { + Iterator(base.makeAsyncIterator()) + } } extension AsyncSequence { - /// An `AsyncSequence` that iterates over the adjacent pairs of the original - /// original `AsyncSequence`. - /// - /// ``` - /// for await (first, second) in (1...5).async.adjacentPairs() { - /// print("First: \(first), Second: \(second)") - /// } - /// - /// // First: 1, Second: 2 - /// // First: 2, Second: 3 - /// // First: 3, Second: 4 - /// // First: 4, Second: 5 - /// ``` - /// - /// - Returns: An `AsyncSequence` where the element is a tuple of two adjacent elements - /// or the original `AsyncSequence`. - @inlinable - public func adjacentPairs() -> AsyncAdjacentPairsSequence { - AsyncAdjacentPairsSequence(self) - } + /// An `AsyncSequence` that iterates over the adjacent pairs of the original + /// original `AsyncSequence`. + /// + /// ``` + /// for await (first, second) in (1...5).async.adjacentPairs() { + /// print("First: \(first), Second: \(second)") + /// } + /// + /// // First: 1, Second: 2 + /// // First: 2, Second: 3 + /// // First: 3, Second: 4 + /// // First: 4, Second: 5 + /// ``` + /// + /// - Returns: An `AsyncSequence` where the element is a tuple of two adjacent elements + /// or the original `AsyncSequence`. + @inlinable + public func adjacentPairs() -> AsyncAdjacentPairsSequence { + AsyncAdjacentPairsSequence(self) + } } extension AsyncAdjacentPairsSequence: Sendable where Base: Sendable, Base.Element: Sendable, Base.AsyncIterator: Sendable { }