From 887ba6fc8d6aa40c1192ec7c0533b03ba4f9f008 Mon Sep 17 00:00:00 2001 From: sbertix Date: Wed, 26 May 2021 15:28:53 +0200 Subject: [PATCH] test: improve on testing --- .../Providers/Lock/LockProviderType.swift | 9 ++ .../Publishers/Pager/Pager.swift | 31 +++- .../ObservableTests.swift | 152 +++++------------- Tests/ComposableRequestTests/PagerTests.swift | 66 ++++++++ 4 files changed, 138 insertions(+), 120 deletions(-) diff --git a/Sources/ComposableRequest/Providers/Lock/LockProviderType.swift b/Sources/ComposableRequest/Providers/Lock/LockProviderType.swift index 44ea5318..aacb58d0 100644 --- a/Sources/ComposableRequest/Providers/Lock/LockProviderType.swift +++ b/Sources/ComposableRequest/Providers/Lock/LockProviderType.swift @@ -19,3 +19,12 @@ public extension LockProviderType { Self.generate(self, from: key) } } + +public extension LockProviderType where Input == Void { + /// Unlock. + /// + /// - returns: Some `Content`. + func unlock() -> Output { + Self.generate(self, from: ()) + } +} diff --git a/Sources/ComposableRequest/Publishers/Pager/Pager.swift b/Sources/ComposableRequest/Publishers/Pager/Pager.swift index 30b7f258..6443971a 100644 --- a/Sources/ComposableRequest/Publishers/Pager/Pager.swift +++ b/Sources/ComposableRequest/Publishers/Pager/Pager.swift @@ -42,6 +42,20 @@ public extension Publishers { self.generator = generator } + /// Init. + /// + /// - parameters: + /// - count: A valid `Int`. Defaults to `.max`. + /// - offset: A valid `Offset`. + /// - delay: A valid `Delay`. + /// - generator: A valid generator. + public init(_ count: Int = .max, + offset: Offset, + delay: Delay, + generator: @escaping (_ offset: Offset) -> Iteration) { + self.init(count, offset: offset, delay: { _ in delay }, generator: generator) + } + /// Init. /// /// - parameters: @@ -77,10 +91,19 @@ public extension Publishers { case .stop: return current.eraseToAnyPublisher() case .load(let next): - return current - .append(Deferred { Pager(count - 1, offset: next, delay: delay, generator: generator) } - .delay(for: count - 1 > 0 ? delay(next) : .zero, scheduler: RunLoop.main)) - .eraseToAnyPublisher() + switch count - 1 > 0 ? delay(next) : .zero { + case ...0: + return current + .append(Deferred { Pager(count - 1, offset: next, delay: delay, generator: generator) }) + .eraseToAnyPublisher() + case let delay: + return current + .append( + Deferred { Pager(count - 1, offset: next, delay: self.delay, generator: generator) } + .delay(for: delay, scheduler: RunLoop.main) + ) + .eraseToAnyPublisher() + } } } .subscribe(subscriber) diff --git a/Tests/ComposableRequestTests/ObservableTests.swift b/Tests/ComposableRequestTests/ObservableTests.swift index ec9f3549..2785c2db 100644 --- a/Tests/ComposableRequestTests/ObservableTests.swift +++ b/Tests/ComposableRequestTests/ObservableTests.swift @@ -95,59 +95,39 @@ internal final class ObservableTests: XCTestCase { // MARK: Pagination - // swiftlint:disable line_length /// Test a paginated fetch request. func testPagination() { - let languages = ["en", "it", "de", "fr"] - let expectations = [languages.map(XCTestExpectation.init), - [XCTestExpectation(description: "delay"), XCTestExpectation(description: "completion")]] - .reduce(into: []) { $0.append(contentsOf: $1) } - let offset = Reference(0) - let delayed = Reference(false) - // Prepare the provider. - PagerProvider { pages in - // Actually paginate futures. - Pager(pages) { offset in - LockProvider>>>>> { value, _, _, _, _ in - Just(value).map { _ in offset }.eraseToAnyPublisher() - } - .unlock(with: languages[offset]) - .unlock(with: 0) - .unlock(with: 0) - .unlock(with: 0) - .unlock(with: 0) - .assertBackgroundThread() - .iterateFirst(stoppingAt: offset) { - XCTAssert(!Thread.isMainThread) - return .load($0 + 1) + // swiftlint:disable line_length + typealias Lock = LockProvider>>>>> + // swiftlint:enable line_length + + let date = Date() + let expectations = ((0...4).map(String.init) + ["completion"]).map(XCTestExpectation.init) + PagerProvider { + Pager($0) { offset in + Lock { value, _, _, _, _ in + Just(value) } + .unlock(with: offset) + .unlock() + .unlock() + .unlock() + .unlock() + .iterateFirst { .load($0 + 1) } } } - .pages(languages.count, offset: 0, delay: .seconds(2)) - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .receive(on: RunLoop.main) - .assertMainThread() - .sink( - receiveCompletion: { - if case .failure(let error) = $0 { XCTFail(error.localizedDescription) } - XCTAssert(delayed.value, "Pagination did not wait.") - expectations.last?.fulfill() - }, - receiveValue: { - XCTAssert(offset.value == $0) - offset.value = $0 + 1 - expectations[$0].fulfill() - } - ) - .store(in: &bin) - // Make sure you delay before it's completed. - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { - delayed.value = true - expectations[4].fulfill() + .pages(5, offset: 0, delay: .seconds(1)) + .sink { + if case .failure(let error) = $0 { XCTFail(error.localizedDescription) } + expectations.last?.fulfill() + } receiveValue: { + expectations[$0].fulfill() } - wait(for: expectations, timeout: 30) + .store(in: &bin) + + wait(for: expectations, timeout: 10) + XCTAssertLessThan(date.timeIntervalSinceNow, -4) } - // swiftlint:enable line_length /// Test a pagination request using a ranked offset. func testRankedOffsetPagination() { @@ -159,55 +139,19 @@ internal final class ObservableTests: XCTestCase { Pager(pages) { Just(pages.rank[$0]).iterateFirst { .load($0 + 1) } } } .pages(values.count, offset: 0, rank: values) - .sink( - receiveCompletion: { - if case .failure(let error) = $0 { XCTFail(error.localizedDescription) } - expectations.last?.fulfill() - }, - receiveValue: { value in - offset.sync { - XCTAssert(value == $0) - expectations[value].fulfill() - $0 = value + 1 - } - } - ) - .store(in: &bin) - wait(for: expectations, timeout: 30) - } - - /// Test a remote paginated fetch request. - func testRemotePagination() { - let languages = ["en", "it", "de", "fr"] - let expectations = languages.map(XCTestExpectation.init) + [XCTestExpectation(description: "completion")] - let offset = Reference(0) - // Prepare the provider. - LockSessionPagerProvider { url, session, pages in // Additional tests. - // Actually paginate futures. - Pager(pages) { offset in - Request(url) - .query(appending: languages[offset], forKey: "l") - .publish(with: session) - .map { _ in offset } - .iterateLast { .load(($0 ?? -1) + 1) } - } + .sink { + if case .failure(let error) = $0 { XCTFail(error.localizedDescription) } + expectations.last?.fulfill() } - .unlock(with: url) - .session(.shared) - .pages(languages.count, offset: 0) - .sink( - receiveCompletion: { - if case .failure(let error) = $0 { XCTFail(error.localizedDescription) } - expectations.last?.fulfill() - }, - receiveValue: { - XCTAssert(offset.value == $0) - offset.value = $0 + 1 - expectations[$0].fulfill() + receiveValue: { value in + offset.sync { + XCTAssert(value == $0) + expectations[value].fulfill() + $0 = value + 1 } - ) + } .store(in: &bin) - wait(for: expectations, timeout: 30 * TimeInterval(languages.count)) + wait(for: expectations, timeout: 30) } // MARK: Cancellation @@ -230,28 +174,4 @@ internal final class ObservableTests: XCTestCase { } wait(for: expectations, timeout: 5) } - - /// Test paginated cancellation. - func testPaginatedCancellation() { - let expectations = ["output", "completion"].map(XCTestExpectation.init) - expectations[0].assertForOverFulfill = true - expectations[0].expectedFulfillmentCount = 1 - Pager(3) { - Request(url) - .publish(session: .shared) - .map(\.response) - } - .sink( - receiveCompletion: { _ in - XCTFail("This should not complete") - }, - receiveValue: { _ in - expectations.first?.fulfill() - self.bin.removeAll() - } - ) - .store(in: &bin) - DispatchQueue.main.asyncAfter(deadline: .now() + 25) { expectations.last?.fulfill() } - wait(for: expectations, timeout: 30) - } } diff --git a/Tests/ComposableRequestTests/PagerTests.swift b/Tests/ComposableRequestTests/PagerTests.swift index c4807a67..e27f3f24 100644 --- a/Tests/ComposableRequestTests/PagerTests.swift +++ b/Tests/ComposableRequestTests/PagerTests.swift @@ -75,4 +75,70 @@ internal class PagerTests: XCTestCase { } XCTAssertEqual(provider.pages(1, delay: .seconds(2)), 3) } + + /// Test iterations. + func testIterations() { + /// Compare equality. + /// + /// - parameters: + /// - instruction: A valid `Instruction`. + /// - value: Some value. + func compare(_ instruction: Instruction, _ value: T) { + switch instruction { + case .stop: + XCTFail("Iteration stopped.") + case .load(let next): + XCTAssertEqual(next, value) + } + } + + /// Compare to stop. + /// + /// - parameter instruction: A valid `Instruction`. + func compare(stop instruction: Instruction) { + switch instruction { + case .stop: + break + case .load: + XCTFail("Iteration should stop.") + } + } + + compare(Just(1).iterate { .load($0.reduce(into: 0) { $0 += $1 }+1) }.offset([0]), 1) + compare(stop: Just(1).iterate { $0 == 1 } with: { .load(($0.first ?? -1) + 1) }.offset([0])) + compare(stop: Just(1).iterate(stoppingAt: 1) { .load(($0.first ?? -1) + 1) }.offset([0])) + compare(Just(1).iterateFirst { .load(($0 ?? -1) + 1) }.offset([0]), 1) + compare(stop: Just(1).iterateFirst { $0 == 1 } with: { .load(($0 ?? -1) + 1) }.offset([0])) + compare(stop: Just(1).iterateFirst(stoppingAt: 1) { .load(($0 ?? -1) + 1) }.offset([0])) + compare(Just(1).iterateLast { .load(($0 ?? -1) + 1) }.offset([0]), 1) + compare(stop: Just(1).iterateLast { $0 == 1 } with: { .load(($0 ?? -1) + 1) }.offset([0])) + compare(stop: Just(1).iterateLast(stoppingAt: 1) { .load(($0 ?? -1) + 1) }.offset([0])) + compare(stop: Just(()).iterate { _ in false }.offset([()])) + + compare(Just(1).iterateFirst { .load($0 + 1) }.offset([0]), 1) + compare(stop: Just(1).iterateFirst { $0 == 1 } with: { .load($0 + 1) }.offset([0])) + compare(stop: Just(1).iterateFirst(stoppingAt: 1) { .load($0 + 1) }.offset([0])) + compare(Just(1).iterateLast { .load($0 + 1) }.offset([0]), 1) + compare(stop: Just(1).iterateLast { $0 == 1 } with: { .load($0 + 1) }.offset([0])) + compare(stop: Just(1).iterateLast(stoppingAt: 1) { .load($0 + 1) }.offset([0])) + + compare(Just(Page(offset: 1)).iterateFirst().offset([Page(offset: 0)]), 0) + compare(stop: Just(Page(offset: 1)).iterateFirst { $0 == 0 }.offset([Page(offset: 0)])) + compare(Just(Page(offset: 1)).iterateLast().offset([Page(offset: 0)]), 0) + compare(stop: Just(Page(offset: 1)).iterateFirst { $0 == 0 }.offset([Page(offset: 0)])) + + compare(stop: Just(Page(offset: 1)).iterateFirst(stoppingAt: 0).offset([Page(offset: 0)])) + compare(stop: Just(Page(offset: 1)).iterateLast(stoppingAt: 0).offset([Page(offset: 0)])) + } +} + +fileprivate extension PagerTests { + /// A `struct` defining a custom `Paginatable`. + struct Page: Paginatable { + let offset: Offset + } +} + +fileprivate extension PagerTests.Page { + typealias Offset = Int? }