diff --git a/Tests/AwaitingTests.swift b/Tests/AwaitingTests.swift index eab0f8f..2b095e7 100644 --- a/Tests/AwaitingTests.swift +++ b/Tests/AwaitingTests.swift @@ -29,18 +29,21 @@ // SOFTWARE. // +#if canImport(Testing) @testable import Awaiting -import XCTest +import Testing -final class AwaitingTests: XCTestCase { +struct AwaitingTests { - func testWrappedValueUpdates() { + @Test + func wrappedValueUpdates() { let mock = Mock("") mock.property = "Shrimp" - XCTAssertEqual(mock.property, "Shrimp") + #expect(mock.property == "Shrimp") } - func testInitializesWithValue() async throws { + @Test + func initializesWithValue() async throws { // given let mock = Mock(10) @@ -48,10 +51,11 @@ final class AwaitingTests: XCTestCase { let value = try await mock.$property.some() // then - XCTAssertEqual(value, 10) + #expect(value == 10) } - func testLateTask_ReceivesTheWrappedValue() async throws { + @Test + func lateTask_ReceivesTheWrappedValue() async throws { // given let mock = Mock("Fish") mock.property = "Chips" @@ -60,10 +64,11 @@ final class AwaitingTests: XCTestCase { let value = try await mock.$property.some() // then - XCTAssertEqual(value, "Chips") + #expect(value == "Chips") } - func testMultipleTasks_ReceiveTheWrappedValue() async throws { + @Test + func multipleTasks_ReceiveTheWrappedValue() async throws { // given let mock = Mock(nil) @@ -75,17 +80,13 @@ final class AwaitingTests: XCTestCase { Task { mock.property = "Chips" } // then - let v1 = try await value1 - XCTAssertEqual(v1, "Chips") - - let v2 = try await value2 - XCTAssertEqual(v2, "Chips") - - let v3 = try await value3 - XCTAssertEqual(v3, "Chips") + #expect(try await value1 == "Chips") + #expect(try await value2 == "Chips") + #expect(try await value3 == "Chips") } - func testWaitersAreRemoved_WhenComplete() async throws { + @Test + func waitersAreRemoved_WhenComplete() async throws { // given let mock = Mock(nil) async let value1 = mock.$property.some() @@ -95,10 +96,11 @@ final class AwaitingTests: XCTestCase { _ = try await value1 // then - XCTAssertTrue(mock.isWaitingEmpty) + #expect(mock.isWaitingEmpty) } - func testNil_MakesTaskWait() async throws { + @Test + func nil_MakesTaskWait() async throws { // given let mock = Mock("Fish") mock.property = "Fish" @@ -109,11 +111,11 @@ final class AwaitingTests: XCTestCase { mock.property = "Chips" // then - let v1 = try await value1 - XCTAssertEqual(v1, "Chips") + #expect(try await value1 == "Chips") } - func testCancellingTask_ThrowsCancellationError() async { + @Test + func cancellingTask_ThrowsCancellationError() async { // given let mock = Mock(nil) let task = Task { @@ -124,15 +126,13 @@ final class AwaitingTests: XCTestCase { task.cancel() // then - do { + await #expect(throws: CancellationError.self) { _ = try await task.value - XCTFail("Expected Error") - } catch { - XCTAssertTrue(error is CancellationError) } } - func testCollectionWaiter_WaitsForMinimumElements() async throws { + @Test + func collectionWaiter_WaitsForMinimumElements() async throws { let mock = Mock("") async let value = mock.$property.first(withAtLeast: 5) @@ -144,11 +144,11 @@ final class AwaitingTests: XCTestCase { } // then - let v1 = try await value - XCTAssertEqual(v1, "Kracken") + #expect(try await value == "Kracken") } - func testOptionalWaiter_WaitsForPredicate() async throws { + @Test + func optionalWaiter_WaitsForPredicate() async throws { let mock = Mock(nil) async let value = mock.$property.some(where: { $0 > 5 }) @@ -160,11 +160,11 @@ final class AwaitingTests: XCTestCase { } // then - let v1 = try await value - XCTAssertEqual(v1, 10) + #expect(try await value == 10) } - func testCollectionWaiter_WaitsForValueAtIndex() async throws { + @Test + func collectionWaiter_WaitsForValueAtIndex() async throws { let mock = Mock(Array()) async let value = mock.$property.value(at: 2) @@ -176,11 +176,11 @@ final class AwaitingTests: XCTestCase { } // then - let v1 = try await value - XCTAssertEqual(v1, 30) + #expect(try await value == 30) } - func testCollectionWaiter_WaitsForElementAtIndex() async throws { + @Test + func collectionWaiter_WaitsForElementAtIndex() async throws { let mock = Mock(Array()) async let value = mock.$property.element(at: 2) @@ -192,11 +192,11 @@ final class AwaitingTests: XCTestCase { } // then - let v1 = try await value - XCTAssertEqual(v1, 30) + #expect(try await value == 30) } - func testCollectionWaiter_WaitsForElementThatMatchesPredicate() async throws { + @Test + func collectionWaiter_WaitsForElementThatMatchesPredicate() async throws { let mock = Mock(Array()) async let value = mock.$property.element(where: { $0.isMultiple(of: 7) }) @@ -208,11 +208,11 @@ final class AwaitingTests: XCTestCase { } // then - let v1 = try await value - XCTAssertEqual(v1, 21) + #expect(try await value == 21) } - func testEquatableWaiter_WaitsForElement() async throws { + @Test + func equatableWaiter_WaitsForElement() async throws { let mock = Mock(0) async let value = mock.$property.equals(30) @@ -224,11 +224,11 @@ final class AwaitingTests: XCTestCase { } // then - let v1 = try await value - XCTAssertEqual(v1, 30) + #expect(try await value == 30) } - func testEquatableWaiter_WaitsForOptionalElement() async throws { + @Test + func equatableWaiter_WaitsForOptionalElement() async throws { let mock = Mock(Optional.none) async let value = mock.$property.equals("Fish") @@ -240,11 +240,11 @@ final class AwaitingTests: XCTestCase { } // then - let v1 = try await value - XCTAssertEqual(v1, "Fish") + #expect(try await value == "Fish") } - func testModify_TriggersWaiter() async throws { + @Test + func modify_TriggersWaiter() async throws { // given let mock = Mock(nil) async let value = mock.$property.some() @@ -253,8 +253,7 @@ final class AwaitingTests: XCTestCase { mock.modify { $0 = 200 } // then - let v1 = try await value - XCTAssertEqual(v1, 200) + #expect(try await value == 200) } } @@ -274,3 +273,4 @@ final class Mock: @unchecked Sendable { try _property.modify(transform) } } +#endif diff --git a/Tests/AwaitingXCTests.swift b/Tests/AwaitingXCTests.swift new file mode 100644 index 0000000..839e68f --- /dev/null +++ b/Tests/AwaitingXCTests.swift @@ -0,0 +1,278 @@ +// +// AwaitingTests.swift +// Awaiting +// +// Created by Simon Whitty on 26/01/2022. +// Copyright © 2022 Simon Whitty. All rights reserved. +// +// Distributed under the permissive MIT license +// Get the latest version from here: +// +// https://github.com/swhitty/Awaiting +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#if !canImport(Testing) +@testable import Awaiting +import XCTest + +final class AwaitingTests: XCTestCase { + + func testWrappedValueUpdates() { + let mock = Mock("") + mock.property = "Shrimp" + XCTAssertEqual(mock.property, "Shrimp") + } + + func testInitializesWithValue() async throws { + // given + let mock = Mock(10) + + // when + let value = try await mock.$property.some() + + // then + XCTAssertEqual(value, 10) + } + + func testLateTask_ReceivesTheWrappedValue() async throws { + // given + let mock = Mock("Fish") + mock.property = "Chips" + + // when + let value = try await mock.$property.some() + + // then + XCTAssertEqual(value, "Chips") + } + + func testMultipleTasks_ReceiveTheWrappedValue() async throws { + // given + let mock = Mock(nil) + + async let value1 = mock.$property.some() + async let value2 = mock.$property.some() + async let value3 = mock.$property.some() + + // when + Task { mock.property = "Chips" } + + // then + let v1 = try await value1 + XCTAssertEqual(v1, "Chips") + + let v2 = try await value2 + XCTAssertEqual(v2, "Chips") + + let v3 = try await value3 + XCTAssertEqual(v3, "Chips") + } + + func testWaitersAreRemoved_WhenComplete() async throws { + // given + let mock = Mock(nil) + async let value1 = mock.$property.some() + + // when + mock.property = "Chips" + _ = try await value1 + + // then + XCTAssertTrue(mock.isWaitingEmpty) + } + + func testNil_MakesTaskWait() async throws { + // given + let mock = Mock("Fish") + mock.property = "Fish" + mock.property = nil + async let value1 = mock.$property.some() + + // when + mock.property = "Chips" + + // then + let v1 = try await value1 + XCTAssertEqual(v1, "Chips") + } + + func testCancellingTask_ThrowsCancellationError() async { + // given + let mock = Mock(nil) + let task = Task { + try await mock.$property.some() + } + + // when + task.cancel() + + // then + do { + _ = try await task.value + XCTFail("Expected Error") + } catch { + XCTAssertTrue(error is CancellationError) + } + } + + func testCollectionWaiter_WaitsForMinimumElements() async throws { + let mock = Mock("") + async let value = mock.$property.first(withAtLeast: 5) + + // when + Task { + mock.property = "Fish" + mock.property = "Kracken" + mock.property = "Chips" + } + + // then + let v1 = try await value + XCTAssertEqual(v1, "Kracken") + } + + func testOptionalWaiter_WaitsForPredicate() async throws { + let mock = Mock(nil) + async let value = mock.$property.some(where: { $0 > 5 }) + + // when + Task { + mock.property = 3 + mock.property = nil + mock.property = 10 + } + + // then + let v1 = try await value + XCTAssertEqual(v1, 10) + } + + func testCollectionWaiter_WaitsForValueAtIndex() async throws { + let mock = Mock(Array()) + async let value = mock.$property.value(at: 2) + + // when + Task { + mock.property.append(10) + mock.property.append(20) + mock.property.append(30) + } + + // then + let v1 = try await value + XCTAssertEqual(v1, 30) + } + + func testCollectionWaiter_WaitsForElementAtIndex() async throws { + let mock = Mock(Array()) + async let value = mock.$property.element(at: 2) + + // when + Task { + mock.property.append(10) + mock.property.append(20) + mock.property.append(30) + } + + // then + let v1 = try await value + XCTAssertEqual(v1, 30) + } + + func testCollectionWaiter_WaitsForElementThatMatchesPredicate() async throws { + let mock = Mock(Array()) + async let value = mock.$property.element(where: { $0.isMultiple(of: 7) }) + + // when + Task { + mock.property.append(10) + mock.property.append(20) + mock.property.append(21) + } + + // then + let v1 = try await value + XCTAssertEqual(v1, 21) + } + + func testEquatableWaiter_WaitsForElement() async throws { + let mock = Mock(0) + async let value = mock.$property.equals(30) + + // when + Task { + mock.property = 10 + mock.property = 20 + mock.property = 30 + } + + // then + let v1 = try await value + XCTAssertEqual(v1, 30) + } + + func testEquatableWaiter_WaitsForOptionalElement() async throws { + let mock = Mock(Optional.none) + async let value = mock.$property.equals("Fish") + + // when + Task { + mock.property = "chips" + mock.property = "fish" + mock.property = "Fish" + } + + // then + let v1 = try await value + XCTAssertEqual(v1, "Fish") + } + + func testModify_TriggersWaiter() async throws { + // given + let mock = Mock(nil) + async let value = mock.$property.some() + + // when + mock.modify { $0 = 200 } + + // then + let v1 = try await value + XCTAssertEqual(v1, 200) + } +} + +final class Mock: @unchecked Sendable { + @Awaiting var property: T + + init(_ initial: T) { + self.property = initial + } + + var isWaitingEmpty: Bool { + _property.isWaitingEmpty + } + + @discardableResult + func modify(_ transform: (inout T) throws -> U) rethrows -> U { + try _property.modify(transform) + } +} +#endif diff --git a/Tests/MutexTests.swift b/Tests/MutexTests.swift new file mode 100644 index 0000000..3bc517c --- /dev/null +++ b/Tests/MutexTests.swift @@ -0,0 +1,76 @@ +// +// MutexTests.swift +// swift-mutex +// +// Created by Simon Whitty on 07/09/2024. +// Copyright 2024 Simon Whitty +// +// Distributed under the permissive MIT license +// Get the latest version from here: +// +// https://github.com/swhitty/swift-mutex +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#if canImport(Testing) +@testable import Awaiting +import Testing + +struct MutexTests { + + @Test + func withLock_ReturnsValue() { + let mutex = Mutex("fish") + let val = mutex.withLock { + $0 + " & chips" + } + #expect(val == "fish & chips") + } + + @Test + func withLock_ThrowsError() { + let mutex = Mutex("fish") + #expect(throws: CancellationError.self) { + try mutex.withLock { _ -> Void in throw CancellationError() } + } + } + + @Test + func lockIfAvailable_ReturnsValue() { + let mutex = Mutex("fish") + mutex.storage.lock() + #expect( + mutex.withLockIfAvailable { _ in "chips" } == nil + ) + mutex.storage.unlock() + #expect( + mutex.withLockIfAvailable { _ in "chips" } == "chips" + ) + } + + @Test + func withLockIfAvailable_ThrowsError() { + let mutex = Mutex("fish") + #expect(throws: CancellationError.self) { + try mutex.withLockIfAvailable { _ -> Void in throw CancellationError() } + } + } +} +#endif diff --git a/Tests/MutexXCTests.swift b/Tests/MutexXCTests.swift new file mode 100644 index 0000000..9a416b5 --- /dev/null +++ b/Tests/MutexXCTests.swift @@ -0,0 +1,73 @@ +// +// MutexXCTests.swift +// swift-mutex +// +// Created by Simon Whitty on 07/09/2024. +// Copyright 2024 Simon Whitty +// +// Distributed under the permissive MIT license +// Get the latest version from here: +// +// https://github.com/swhitty/swift-mutex +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#if !canImport(Testing) +@testable import Awaiting +import XCTest + +final class MutexXCTests: XCTestCase { + + func testWithLock_ReturnsValue() { + let mutex = Mutex("fish") + let val = mutex.withLock { + $0 + " & chips" + } + XCTAssertEqual(val, "fish & chips") + } + + func testWithLock_ThrowsError() { + let mutex = Mutex("fish") + XCTAssertThrowsError(try mutex.withLock { _ -> Void in throw CancellationError() }) { + _ = $0 is CancellationError + } + } + + func testLockIfAvailable_ReturnsValue() { + let mutex = Mutex("fish") + mutex.storage.lock() + XCTAssertNil( + mutex.withLockIfAvailable { _ in "chips" } + ) + mutex.storage.unlock() + XCTAssertEqual( + mutex.withLockIfAvailable { _ in "chips" }, + "chips" + ) + } + + func testWithLockIfAvailable_ThrowsError() { + let mutex = Mutex("fish") + XCTAssertThrowsError(try mutex.withLockIfAvailable { _ -> Void in throw CancellationError() }) { + _ = $0 is CancellationError + } + } +} +#endif