diff --git a/.github/workflows/swiftlint.yml b/.github/workflows/swiftlint.yml deleted file mode 100644 index b4c5848..0000000 --- a/.github/workflows/swiftlint.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: SwiftLint - -on: - pull_request: - paths: - - '.github/workflows/swiftlint.yml' - - '.swiftlint.yml' - - '**/*.swift' - -jobs: - SwiftLint: - runs-on: ubuntu-latest - steps: - - name: Swiftlint - uses: norio-nomura/action-swiftlint@3.2.1 - with: - args: --strict - env: - WORKING_DIRECTORY: Asynchrone - DIFF_BASE: ${{ github.base_ref }} \ No newline at end of file diff --git a/.swiftlint.yml b/.swiftlint.yml deleted file mode 100644 index 67342e6..0000000 --- a/.swiftlint.yml +++ /dev/null @@ -1,34 +0,0 @@ -only_rules: - - closure_spacing - - colon - - empty_count - - fatal_error_message - - force_cast - - force_try - - force_unwrapping - - implicitly_unwrapped_optional - - legacy_cggeometry_functions - - legacy_constant - - legacy_constructor - - legacy_nsgeometry_functions - - operator_usage_whitespace - - redundant_string_enum_value - - return_arrow_whitespace - - trailing_newline - - type_name - - unused_optional_binding - - vertical_whitespace - - void_return - - custom_rules - -custom_rules: - no_direct_standard_out_logs: - name: "Writing log messages directly to standard out is disallowed" - regex: "(\\bprint|\\bdebugPrint|\\bdump|Swift\\.print|Swift\\.debugPrint|Swift\\.dump)\\s*\\(" - match_kinds: - - identifier - message: "Don't commit `print(…)`, `debugPrint(…)`, or `dump(…)` as they write to standard out in release. Either log to a dedicated logging system or silence this warning in debug-only scenarios explicitly using `// swiftlint:disable:next no_direct_standard_out_logs`" - severity: error - -vertical_whitespace: - max_empty_lines: 4 diff --git a/Asynchrone.xcodeproj/project.pbxproj b/Asynchrone.xcodeproj/project.pbxproj index d18158c..4e96f3b 100644 --- a/Asynchrone.xcodeproj/project.pbxproj +++ b/Asynchrone.xcodeproj/project.pbxproj @@ -399,6 +399,8 @@ dependencies = ( ); name = AsynchroneTests; + packageProductDependencies = ( + ); productName = AsynchroneTests; productReference = A4C361B4276A5EF200511525 /* AsynchroneTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -430,6 +432,8 @@ Base, ); mainGroup = A4C361A0276A5EF200511525; + packageReferences = ( + ); productRefGroup = A4C361AB276A5EF200511525 /* Products */; projectDirPath = ""; projectRoot = ""; diff --git a/AsynchroneTests/Supporting Files/Assertion.swift b/AsynchroneTests/Supporting Files/Assertion.swift index 7faaa1c..56b18b0 100644 --- a/AsynchroneTests/Supporting Files/Assertion.swift +++ b/AsynchroneTests/Supporting Files/Assertion.swift @@ -1,5 +1,27 @@ import XCTest +/// Asserts that an async expression is not `nil`, and returns its unwrapped value. +/// +/// Generates a failure when `expression == nil`. +/// +/// - Parameters: +/// - expression: An expression of type `T?` to compare against `nil`. +/// Its type will determine the type of the returned value. +/// - message: An optional description of the failure. +/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called. +/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called. +/// - Returns: A value of type `T`, the result of evaluating and unwrapping the given `expression`. +/// - Throws: An error when `expression == nil`. It will also rethrow any error thrown while evaluating the given expression. +func XCTAsyncUnwrap( + _ expression: () async throws -> T?, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) async throws -> T { + let value = try await expression() + return try XCTUnwrap(value, message(), file: file, line: line) +} + /// Assert two expressions are eventually equal. /// - Parameters: /// - expressionA: Expression A @@ -8,14 +30,14 @@ import XCTest /// - 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`. func XCTAssertEventuallyEqual( - _ expressionA: @escaping @autoclosure () -> T?, - _ expressionB: @escaping @autoclosure () -> T?, + _ expressionA: @autoclosure @escaping () -> T?, + _ expressionB: @autoclosure @escaping () -> T?, timeout: TimeInterval = 5.0, file: StaticString = #filePath, line: UInt = #line ) async { let timeoutDate = Date(timeIntervalSinceNow: timeout) - + while true { let resultA = expressionA() let resultB = expressionB() @@ -39,7 +61,7 @@ func XCTAssertEventuallyEqual( return // False but still within timeout limit. case false: - try? await Task.sleep(nanoseconds: 10000000) + try? await Task.sleep(nanoseconds: 1000000) } } } @@ -51,7 +73,7 @@ func XCTAssertEventuallyEqual( /// - 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`. -func XCTAsyncAssertEventuallyEqual( +func XCTAssertEventuallyEqual( _ expressionA: @escaping () async -> T?, _ expressionB: @escaping () async -> T?, timeout: TimeInterval = 5.0, @@ -59,7 +81,7 @@ func XCTAsyncAssertEventuallyEqual( line: UInt = #line ) async { let timeoutDate = Date(timeIntervalSinceNow: timeout) - + while true { let resultA = await expressionA() let resultB = await expressionB() @@ -83,18 +105,38 @@ func XCTAsyncAssertEventuallyEqual( return // False but still within timeout limit. case false: - try? await Task.sleep(nanoseconds: 10000000) + try? await Task.sleep(nanoseconds: 1000000) } } } +/// Assert a value is eventually true. +/// - Parameters: +/// - expression: The value to assert eventually is true. +/// - 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`. +func XCTAssertEventuallyTrue( + _ expression: @escaping @autoclosure () -> Bool, + timeout: TimeInterval = 5.0, + file: StaticString = #filePath, + line: UInt = #line +) async { + await XCTAssertEventuallyEqual( + expression(), + true, + timeout: timeout, + file: file, + line: line + ) +} /// Assert an async closure thorws an error. /// - Parameters: /// - closure: The closure. /// - 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 XCTAsyncAssertThrow( +func XCTAsyncAssertThrow( _ closure: () async throws -> T, file: StaticString = #filePath, line: UInt = #line @@ -114,7 +156,7 @@ public func XCTAsyncAssertThrow( /// - closure: The closure. /// - 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 XCTAsyncAssertNoThrow( +func XCTAsyncAssertNoThrow( _ closure: () async throws -> T, file: StaticString = #filePath, line: UInt = #line @@ -135,7 +177,7 @@ public func XCTAsyncAssertNoThrow( /// - closure: The closure. /// - 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 XCTAsyncAssertNil( +func XCTAsyncAssertNil( _ closure: () async -> T?, file: StaticString = #filePath, line: UInt = #line diff --git a/AsynchroneTests/Tests/Extensions/AsyncSequenceTests.swift b/AsynchroneTests/Tests/Extensions/AsyncSequenceTests.swift index 6e09a8e..ec7cbe7 100644 --- a/AsynchroneTests/Tests/Extensions/AsyncSequenceTests.swift +++ b/AsynchroneTests/Tests/Extensions/AsyncSequenceTests.swift @@ -61,6 +61,7 @@ final class AsyncSequenceTests: XCTestCase { } func testSinkWithFinishedCompletion() async { + let completionExpectation = self.expectation(description: "Completion called") var values: [Int] = [] self.sequence.sink( receiveValue: { values.append($0) }, @@ -68,12 +69,14 @@ final class AsyncSequenceTests: XCTestCase { switch $0 { case .failure(let error): XCTFail("Invalid completion case: Failure \(error)") - case .finished:() + case .finished: + completionExpectation.fulfill() } } ) - await XCTAssertEventuallyEqual(values, [1, 2, 3]) + await self.waitForExpectations(timeout: 5.0, handler: nil) + XCTAssertEqual(values, [1, 2, 3]) } func testSinkWithFailedCompletion() async { @@ -99,7 +102,7 @@ final class AsyncSequenceTests: XCTestCase { ) await self.waitForExpectations(timeout: 5.0, handler: nil) - await XCTAssertEventuallyEqual(values, [1, 2, 3]) + XCTAssertEqual(values, [1, 2, 3]) } func testSinkWithCancellation() async { @@ -125,6 +128,6 @@ final class AsyncSequenceTests: XCTestCase { task.cancel() await self.waitForExpectations(timeout: 5.0, handler: nil) - await XCTAssertEventuallyEqual(values, [1]) + XCTAssertEqual(values, [1]) } } diff --git a/AsynchroneTests/Tests/Sequences/DebounceAsyncSequenceTests.swift b/AsynchroneTests/Tests/Sequences/DebounceAsyncSequenceTests.swift index 27e9043..c2cebe7 100644 --- a/AsynchroneTests/Tests/Sequences/DebounceAsyncSequenceTests.swift +++ b/AsynchroneTests/Tests/Sequences/DebounceAsyncSequenceTests.swift @@ -9,14 +9,13 @@ final class DebounceAsyncSequenceTests: XCTestCase { override func setUpWithError() throws { self.stream = AsyncStream { continuation in continuation.yield(0) - try? await Task.sleep(seconds: 0.1) continuation.yield(1) - try? await Task.sleep(seconds: 0.1) + try? await Task.sleep(seconds: 0.2) continuation.yield(2) continuation.yield(3) continuation.yield(4) continuation.yield(5) - try? await Task.sleep(seconds: 0.1) + try? await Task.sleep(seconds: 0.2) continuation.finish() } } @@ -28,7 +27,7 @@ final class DebounceAsyncSequenceTests: XCTestCase { .debounce(for: 0.1) .collect() - XCTAssertEqual(values, [0, 1, 5]) + XCTAssertEqual(values, [1, 5]) } func testDebounceWithNoValues() async {