This commit is contained in:
zeus
2025-01-22 16:22:33 +08:00
parent 09b9cec8ec
commit 738c373a77
2534 changed files with 0 additions and 486292 deletions
@@ -1,62 +0,0 @@
#if canImport(Combine)
import XCTest
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
extension PublisherExpectations {
/// A publisher expectation which waits for the timeout to expire, or
/// the recorded publisher to complete.
///
/// When waiting for this expectation, the publisher error is thrown if
/// the publisher fails before the expectation has expired.
///
/// Otherwise, an array of all elements published before the expectation
/// has expired is returned.
///
/// Unlike other expectations, `AvailableElements` does not make a test fail
/// on timeout expiration. It just returns the elements published so far.
///
/// For example:
///
/// // SUCCESS: no timeout, no error
/// func testTimerPublishesIncreasingDates() throws {
/// let publisher = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect()
/// let recorder = publisher.record()
/// let dates = try wait(for: recorder.availableElements, timeout: ...)
/// XCTAssertEqual(dates.sorted(), dates)
/// }
public struct AvailableElements<Input, Failure: Error>: PublisherExpectation {
let recorder: Recorder<Input, Failure>
public func _makeWaiter() -> XCTWaiter? { Waiter() }
public func _setup(_ expectation: XCTestExpectation) {
recorder.fulfillOnCompletion(expectation)
}
/// Returns all elements published so far, or throws an error if the
/// publisher has failed.
public func get() throws -> [Input] {
try recorder.value { (elements, completion, remainingElements, consume) in
if case let .failure(error) = completion {
throw error
}
consume(remainingElements.count)
return elements
}
}
/// A waiter that waits but never fails
private class Waiter: XCTWaiter, XCTWaiterDelegate {
init() {
super.init(delegate: nil)
delegate = self
}
func waiter(_ waiter: XCTWaiter, didTimeoutWithUnfulfilledExpectations unfulfilledExpectations: [XCTestExpectation]) { }
func waiter(_ waiter: XCTWaiter, fulfillmentDidViolateOrderingConstraintsFor expectation: XCTestExpectation, requiredExpectation: XCTestExpectation) { }
func waiter(_ waiter: XCTWaiter, didFulfillInvertedExpectation expectation: XCTestExpectation) { }
func nestedWaiter(_ waiter: XCTWaiter, wasInterruptedByTimedOutWaiter outerWaiter: XCTWaiter) { }
}
}
}
#endif
@@ -1,94 +0,0 @@
#if canImport(Combine)
import XCTest
// The Finished expectation waits for the publisher to complete, and throws an
// error if and only if the publisher fails with an error.
//
// It is not derived from the Recording expectation, because Finished does not
// throw RecordingError.notCompleted if the publisher does not complete on time.
// It only triggers a timeout test failure.
//
// This allows to write tests for publishers that should not complete:
//
// // SUCCESS: no timeout, no error
// func testPassthroughSubjectDoesNotFinish() throws {
// let publisher = PassthroughSubject<String, Never>()
// let recorder = publisher.record()
// try wait(for: recorder.finished.inverted, timeout: 1)
// }
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
extension PublisherExpectations {
/// A publisher expectation which waits for the recorded publisher
/// to complete.
///
/// When waiting for this expectation, the publisher error is thrown if the
/// publisher fails.
///
/// For example:
///
/// // SUCCESS: no timeout, no error
/// func testArrayPublisherFinishesWithoutError() throws {
/// let publisher = ["foo", "bar", "baz"].publisher
/// let recorder = publisher.record()
/// try wait(for: recorder.finished, timeout: 1)
/// }
///
/// This publisher expectation can be inverted:
///
/// // SUCCESS: no timeout, no error
/// func testPassthroughSubjectDoesNotFinish() throws {
/// let publisher = PassthroughSubject<String, Never>()
/// let recorder = publisher.record()
/// try wait(for: recorder.finished.inverted, timeout: 1)
/// }
public struct Finished<Input, Failure: Error>: PublisherExpectation {
let recorder: Recorder<Input, Failure>
public func _setup(_ expectation: XCTestExpectation) {
recorder.fulfillOnCompletion(expectation)
}
/// Returns the expected output, or throws an error if the
/// expectation fails.
///
/// For example:
///
/// // SUCCESS: no error
/// func testArrayPublisherSynchronouslyFinishesWithoutError() throws {
/// let publisher = ["foo", "bar", "baz"].publisher
/// let recorder = publisher.record()
/// try recorder.finished.get()
/// }
public func get() throws {
try recorder.value { (_, completion, remainingElements, consume) in
guard let completion else {
consume(remainingElements.count)
return
}
if case let .failure(error) = completion {
throw error
}
}
}
/// Returns an inverted publisher expectation which waits for a
/// publisher to complete successfully.
///
/// When waiting for this expectation, an error is thrown if the
/// publisher fails with an error.
///
/// For example:
///
/// // SUCCESS: no timeout, no error
/// func testPassthroughSubjectDoesNotFinish() throws {
/// let publisher = PassthroughSubject<String, Never>()
/// let recorder = publisher.record()
/// try wait(for: recorder.finished.inverted, timeout: 1)
/// }
public var inverted: Inverted<Self> {
return Inverted(base: self)
}
}
}
#endif
@@ -1,32 +0,0 @@
#if canImport(Combine)
import XCTest
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
extension PublisherExpectations {
/// A publisher expectation that fails if the base expectation is fulfilled.
///
/// When waiting for this expectation, you receive the same result and
/// eventual error as the base expectation.
///
/// For example:
///
/// // SUCCESS: no timeout, no error
/// func testPassthroughSubjectDoesNotFinish() throws {
/// let publisher = PassthroughSubject<String, Never>()
/// let recorder = publisher.record()
/// try wait(for: recorder.finished.inverted, timeout: 1)
/// }
public struct Inverted<Base: PublisherExpectation>: PublisherExpectation {
let base: Base
public func _setup(_ expectation: XCTestExpectation) {
base._setup(expectation)
expectation.isInverted.toggle()
}
public func get() throws -> Base.Output {
try base.get()
}
}
}
#endif
@@ -1,31 +0,0 @@
#if canImport(Combine)
import XCTest
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
extension PublisherExpectations {
/// A publisher expectation that transforms the value of a base expectation.
///
/// This expectation has no public initializer.
public struct Map<Base: PublisherExpectation, Output>: PublisherExpectation {
let base: Base
let transform: (Base.Output) throws -> Output
public func _setup(_ expectation: XCTestExpectation) {
base._setup(expectation)
}
public func get() throws -> Output {
try transform(base.get())
}
}
}
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
extension PublisherExpectation {
/// Returns a publisher expectation that transforms the value of the
/// base expectation.
func map<T>(_ transform: @escaping (Output) throws -> T) -> PublisherExpectations.Map<Self, T> {
PublisherExpectations.Map(base: self, transform: transform)
}
}
#endif
@@ -1,81 +0,0 @@
#if canImport(Combine)
import XCTest
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
extension PublisherExpectations {
/// A publisher expectation which waits for the recorded publisher to emit
/// `count` elements, or to complete.
///
/// When waiting for this expectation, a `RecordingError.notEnoughElements`
/// is thrown if the publisher does not publish `count` elements after last
/// waited expectation. The publisher error is thrown if the publisher fails
/// before publishing the next `count` elements.
///
/// Otherwise, an array of exactly `count` elements is returned.
///
/// For example:
///
/// // SUCCESS: no timeout, no error
/// func testArrayOfThreeElementsPublishesTwoThenOneElement() throws {
/// let publisher = ["foo", "bar", "baz"].publisher
/// let recorder = publisher.record()
///
/// var elements = try wait(for: recorder.next(2), timeout: 1)
/// XCTAssertEqual(elements, ["foo", "bar"])
///
/// elements = try wait(for: recorder.next(1), timeout: 1)
/// XCTAssertEqual(elements, ["baz"])
/// }
public struct Next<Input, Failure: Error>: PublisherExpectation {
let recorder: Recorder<Input, Failure>
let count: Int
init(recorder: Recorder<Input, Failure>, count: Int) {
precondition(count >= 0, "Can't take a prefix of negative length")
self.recorder = recorder
self.count = count
}
public func _setup(_ expectation: XCTestExpectation) {
if count == 0 {
// Such an expectation is immediately fulfilled, by essence.
expectation.expectedFulfillmentCount = 1
expectation.fulfill()
} else {
expectation.expectedFulfillmentCount = count
recorder.fulfillOnInput(expectation, includingConsumed: false)
}
}
/// Returns the expected output, or throws an error if the
/// expectation fails.
///
/// For example:
///
/// // SUCCESS: no error
/// func testArrayOfThreeElementsSynchronouslyPublishesTwoThenOneElement() throws {
/// let publisher = ["foo", "bar", "baz"].publisher
/// let recorder = publisher.record()
///
/// var elements = try recorder.next(2).get()
/// XCTAssertEqual(elements, ["foo", "bar"])
///
/// elements = try recorder.next(1).get()
/// XCTAssertEqual(elements, ["baz"])
/// }
public func get() throws -> [Input] {
try recorder.value { (_, completion, remainingElements, consume) in
if remainingElements.count >= count {
consume(count)
return Array(remainingElements.prefix(count))
}
if case let .failure(error) = completion {
throw error
} else {
throw RecordingError.notEnoughElements
}
}
}
}
}
#endif
@@ -1,123 +0,0 @@
#if canImport(Combine)
import XCTest
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
extension PublisherExpectations {
/// A publisher expectation which waits for the recorded publisher to emit
/// one element, or to complete.
///
/// When waiting for this expectation, a `RecordingError.notEnoughElements`
/// is thrown if the publisher does not publish one element after last
/// waited expectation. The publisher error is thrown if the publisher fails
/// before publishing the next element.
///
/// Otherwise, the next published element is returned.
///
/// For example:
///
/// // SUCCESS: no timeout, no error
/// func testArrayOfTwoElementsPublishesElementsInOrder() throws {
/// let publisher = ["foo", "bar"].publisher
/// let recorder = publisher.record()
///
/// var element = try wait(for: recorder.next(), timeout: 1)
/// XCTAssertEqual(element, "foo")
///
/// element = try wait(for: recorder.next(), timeout: 1)
/// XCTAssertEqual(element, "bar")
/// }
public struct NextOne<Input, Failure: Error>: PublisherExpectation {
let recorder: Recorder<Input, Failure>
public func _setup(_ expectation: XCTestExpectation) {
recorder.fulfillOnInput(expectation, includingConsumed: false)
}
/// Returns the expected output, or throws an error if the
/// expectation fails.
///
/// For example:
///
/// // SUCCESS: no error
/// func testArrayOfTwoElementsSynchronouslyPublishesElementsInOrder() throws {
/// let publisher = ["foo", "bar"].publisher
/// let recorder = publisher.record()
///
/// var element = try recorder.next().get()
/// XCTAssertEqual(element, "foo")
///
/// element = try recorder.next().get()
/// XCTAssertEqual(element, "bar")
/// }
public func get() throws -> Input {
try recorder.value { (_, completion, remainingElements, consume) in
if let next = remainingElements.first {
consume(1)
return next
}
if case let .failure(error) = completion {
throw error
} else {
throw RecordingError.notEnoughElements
}
}
}
/// Returns an inverted publisher expectation which waits for the
/// recorded publisher to emit one element, or to complete.
///
/// When waiting for this expectation, a RecordingError is thrown if the
/// publisher does not publish one element after last waited
/// expectation. The publisher error is thrown if the publisher fails
/// before publishing one element.
///
/// For example:
///
/// // SUCCESS: no timeout, no error
/// func testPassthroughSubjectDoesNotPublishAnyElement() throws {
/// let publisher = PassthroughSubject<String, Never>()
/// let recorder = publisher.record()
/// try wait(for: recorder.next().inverted, timeout: 1)
/// }
public var inverted: NextOneInverted<Input, Failure> {
return NextOneInverted(recorder: recorder)
}
}
/// An inverted publisher expectation which waits for the recorded publisher
/// to emit one element, or to complete.
///
/// When waiting for this expectation, a RecordingError is thrown if the
/// publisher does not publish one element after last waited expectation.
/// The publisher error is thrown if the publisher fails before
/// publishing one element.
///
/// For example:
///
/// // SUCCESS: no timeout, no error
/// func testPassthroughSubjectDoesNotPublishAnyElement() throws {
/// let publisher = PassthroughSubject<String, Never>()
/// let recorder = publisher.record()
/// try wait(for: recorder.next().inverted, timeout: 1)
/// }
public struct NextOneInverted<Input, Failure: Error>: PublisherExpectation {
let recorder: Recorder<Input, Failure>
public func _setup(_ expectation: XCTestExpectation) {
expectation.isInverted = true
recorder.fulfillOnInput(expectation, includingConsumed: false)
}
public func get() throws {
try recorder.value { (_, completion, remainingElements, consume) in
if remainingElements.isEmpty == false {
return
}
if case let .failure(error) = completion {
throw error
}
}
}
}
}
#endif
@@ -1,110 +0,0 @@
#if canImport(Combine)
import XCTest
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
extension PublisherExpectations {
/// A publisher expectation which waits for the recorded publisher to emit
/// `maxLength` elements, or to complete.
///
/// When waiting for this expectation, the publisher error is thrown if the
/// publisher fails before `maxLength` elements are published.
///
/// Otherwise, an array of received elements is returned, containing at
/// most `maxLength` elements, or less if the publisher completes early.
///
/// For example:
///
/// // SUCCESS: no timeout, no error
/// func testArrayOfThreeElementsPublishesTwoFirstElementsWithoutError() throws {
/// let publisher = ["foo", "bar", "baz"].publisher
/// let recorder = publisher.record()
/// let elements = try wait(for: recorder.prefix(2), timeout: 1)
/// XCTAssertEqual(elements, ["foo", "bar"])
/// }
///
/// This publisher expectation can be inverted:
///
/// // SUCCESS: no timeout, no error
/// func testPassthroughSubjectPublishesNoMoreThanSentValues() throws {
/// let publisher = PassthroughSubject<String, Never>()
/// let recorder = publisher.record()
/// publisher.send("foo")
/// publisher.send("bar")
/// let elements = try wait(for: recorder.prefix(3).inverted, timeout: 1)
/// XCTAssertEqual(elements, ["foo", "bar"])
/// }
public struct Prefix<Input, Failure: Error>: PublisherExpectation {
let recorder: Recorder<Input, Failure>
let maxLength: Int
init(recorder: Recorder<Input, Failure>, maxLength: Int) {
precondition(maxLength >= 0, "Can't take a prefix of negative length")
self.recorder = recorder
self.maxLength = maxLength
}
public func _setup(_ expectation: XCTestExpectation) {
if maxLength == 0 {
// Such an expectation is immediately fulfilled, by essence.
expectation.expectedFulfillmentCount = 1
expectation.fulfill()
} else {
expectation.expectedFulfillmentCount = maxLength
recorder.fulfillOnInput(expectation, includingConsumed: true)
}
}
/// Returns the expected output, or throws an error if the
/// expectation fails.
///
/// For example:
///
/// // SUCCESS: no error
/// func testArrayOfThreeElementsSynchronouslyPublishesTwoFirstElementsWithoutError() throws {
/// let publisher = ["foo", "bar", "baz"].publisher
/// let recorder = publisher.record()
/// let elements = try recorder.prefix(2).get()
/// XCTAssertEqual(elements, ["foo", "bar"])
/// }
public func get() throws -> [Input] {
try recorder.value { (elements, completion, remainingElements, consume) in
if elements.count >= maxLength {
let extraCount = max(maxLength + remainingElements.count - elements.count, 0)
consume(extraCount)
return Array(elements.prefix(maxLength))
}
if case let .failure(error) = completion {
throw error
}
consume(remainingElements.count)
return elements
}
}
/// Returns an inverted publisher expectation which waits for a
/// publisher to emit `maxLength` elements, or to complete.
///
/// When waiting for this expectation, the publisher error is thrown
/// if the publisher fails before `maxLength` elements are published.
///
/// Otherwise, an array of received elements is returned, containing at
/// most `maxLength` elements, or less if the publisher completes early.
///
/// For example:
///
/// // SUCCESS: no timeout, no error
/// func testPassthroughSubjectPublishesNoMoreThanSentValues() throws {
/// let publisher = PassthroughSubject<String, Never>()
/// let recorder = publisher.record()
/// publisher.send("foo")
/// publisher.send("bar")
/// let elements = try wait(for: recorder.prefix(3).inverted, timeout: 1)
/// XCTAssertEqual(elements, ["foo", "bar"])
/// }
public var inverted: Inverted<Self> {
return Inverted(base: self)
}
}
}
#endif
@@ -1,62 +0,0 @@
#if canImport(Combine)
import Combine
import XCTest
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
extension PublisherExpectations {
/// A publisher expectation which waits for the recorded publisher
/// to complete.
///
/// When waiting for this expectation, a RecordingError.notCompleted is
/// thrown if the publisher does not complete on time.
///
/// Otherwise, a [Record.Recording](https://developer.apple.com/documentation/combine/record/recording)
/// is returned.
///
/// For example:
///
/// // SUCCESS: no timeout, no error
/// func testArrayPublisherRecording() throws {
/// let publisher = ["foo", "bar", "baz"].publisher
/// let recorder = publisher.record()
/// let recording = try wait(for: recorder.recording, timeout: 1)
/// XCTAssertEqual(recording.output, ["foo", "bar", "baz"])
/// if case let .failure(error) = recording.completion {
/// XCTFail("Unexpected error \(error)")
/// }
/// }
public struct Recording<Input, Failure: Error>: PublisherExpectation {
let recorder: Recorder<Input, Failure>
public func _setup(_ expectation: XCTestExpectation) {
recorder.fulfillOnCompletion(expectation)
}
/// Returns the expected output, or throws an error if the
/// expectation fails.
///
/// For example:
///
/// // SUCCESS: no error
/// func testArrayPublisherSynchronousRecording() throws {
/// let publisher = ["foo", "bar", "baz"].publisher
/// let recorder = publisher.record()
/// let recording = try recorder.recording.get()
/// XCTAssertEqual(recording.output, ["foo", "bar", "baz"])
/// if case let .failure(error) = recording.completion {
/// XCTFail("Unexpected error \(error)")
/// }
/// }
public func get() throws -> Record<Input, Failure>.Recording {
try recorder.value { (elements, completion, remainingElements, consume) in
if let completion {
consume(remainingElements.count)
return Record<Input, Failure>.Recording(output: elements, completion: completion)
} else {
throw RecordingError.notCompleted
}
}
}
}
}
#endif