Skip to content

Improve adjacent pairs unit tests #179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 54 additions & 54 deletions Sources/AsyncAlgorithms/AsyncAdjacentPairsSequence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,74 +13,74 @@
/// `AsyncSequence`.
@frozen
public struct AsyncAdjacentPairsSequence<Base: AsyncSequence>: 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<Self> {
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<Self> {
AsyncAdjacentPairsSequence(self)
}
}

extension AsyncAdjacentPairsSequence: Sendable where Base: Sendable, Base.Element: Sendable, Base.AsyncIterator: Sendable { }
Expand Down
113 changes: 75 additions & 38 deletions Tests/AsyncAlgorithmsTests/TestAdjacentPairs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}