diff --git a/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift b/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift index 2388c282365..4ef333934c1 100644 --- a/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift +++ b/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import NIOCore +import NIOEmbedded import NIOPosix import XCTest @@ -23,100 +24,138 @@ final class SuperNotSendable { @available(*, unavailable) extension SuperNotSendable: Sendable {} -final class EventLoopFutureIsolatedTest: XCTestCase { - func testCompletingPromiseWithNonSendableValue() throws { - let group = MultiThreadedEventLoopGroup.singleton - let loop = group.next() +private protocol RunnableEventLoop: EventLoop { + func runForTests() + func advanceTimeForTests(by: TimeAmount) +} + +extension EmbeddedEventLoop: RunnableEventLoop { + fileprivate func runForTests() { + self.run() + } + + fileprivate func advanceTimeForTests(by amount: TimeAmount) { + self.advanceTime(by: amount) + } +} + +extension NIOAsyncTestingEventLoop: RunnableEventLoop { + fileprivate func runForTests() { + // This is horrible, but it's the only general-purpose implementation. + let promise = self.makePromise(of: Void.self) + promise.completeWithTask { + await self.run() + } + try! promise.futureResult.wait() + } + + fileprivate func advanceTimeForTests(by amount: TimeAmount) { + // This is horrible, but it's the only general-purpose implementation. + let promise = self.makePromise(of: Void.self) + promise.completeWithTask { + await self.advanceTime(by: amount) + } + try! promise.futureResult.wait() + } +} - try loop.flatSubmit { +final class EventLoopFutureIsolatedTest: XCTestCase { + func _completingPromiseWithNonSendableValue(loop: any EventLoop) throws { + let f = loop.flatSubmit { let promise = loop.makePromise(of: SuperNotSendable.self) let value = SuperNotSendable() promise.assumeIsolated().succeed(value) return promise.futureResult.assumeIsolated().map { val in XCTAssertIdentical(val, value) }.nonisolated() - }.wait() + } + if let runnable = loop as? RunnableEventLoop { + runnable.runForTests() + } + try f.wait() } - func testCompletingPromiseWithNonSendableResult() throws { - let group = MultiThreadedEventLoopGroup.singleton - let loop = group.next() - - try loop.flatSubmit { + func _completingPromiseWithNonSendableResult(loop: any EventLoop) throws { + let f = loop.flatSubmit { let promise = loop.makePromise(of: SuperNotSendable.self) let value = SuperNotSendable() promise.assumeIsolated().completeWith(.success(value)) return promise.futureResult.assumeIsolated().map { val in XCTAssertIdentical(val, value) }.nonisolated() - }.wait() + } + if let runnable = loop as? RunnableEventLoop { + runnable.runForTests() + } + try f.wait() } - func testCompletingPromiseWithNonSendableValueUnchecked() throws { - let group = MultiThreadedEventLoopGroup.singleton - let loop = group.next() - - try loop.flatSubmit { + func _completingPromiseWithNonSendableValueUnchecked(loop: any EventLoop) throws { + let f = loop.flatSubmit { let promise = loop.makePromise(of: SuperNotSendable.self) let value = SuperNotSendable() promise.assumeIsolatedUnsafeUnchecked().succeed(value) return promise.futureResult.assumeIsolatedUnsafeUnchecked().map { val in XCTAssertIdentical(val, value) }.nonisolated() - }.wait() + } + if let runnable = loop as? RunnableEventLoop { + runnable.runForTests() + } + try f.wait() } - func testCompletingPromiseWithNonSendableResultUnchecked() throws { - let group = MultiThreadedEventLoopGroup.singleton - let loop = group.next() - - try loop.flatSubmit { + func _completingPromiseWithNonSendableResultUnchecked(loop: any EventLoop) throws { + let f = loop.flatSubmit { let promise = loop.makePromise(of: SuperNotSendable.self) let value = SuperNotSendable() promise.assumeIsolatedUnsafeUnchecked().completeWith(.success(value)) return promise.futureResult.assumeIsolatedUnsafeUnchecked().map { val in XCTAssertIdentical(val, value) }.nonisolated() - }.wait() + } + if let runnable = loop as? RunnableEventLoop { + runnable.runForTests() + } + try f.wait() } - func testBackAndForthUnwrapping() throws { - let group = MultiThreadedEventLoopGroup.singleton - let loop = group.next() - - try loop.submit { + func _backAndForthUnwrapping(loop: any EventLoop) throws { + let f = loop.submit { let promise = loop.makePromise(of: SuperNotSendable.self) let future = promise.futureResult XCTAssertEqual(promise.assumeIsolated().nonisolated(), promise) XCTAssertEqual(future.assumeIsolated().nonisolated(), future) promise.assumeIsolated().succeed(SuperNotSendable()) - }.wait() + } + if let runnable = loop as? RunnableEventLoop { + runnable.runForTests() + } + try f.wait() } - func testBackAndForthUnwrappingUnchecked() throws { - let group = MultiThreadedEventLoopGroup.singleton - let loop = group.next() - - try loop.submit { + func _backAndForthUnwrappingUnchecked(loop: any EventLoop) throws { + let f = loop.submit { let promise = loop.makePromise(of: SuperNotSendable.self) let future = promise.futureResult XCTAssertEqual(promise.assumeIsolatedUnsafeUnchecked().nonisolated(), promise) XCTAssertEqual(future.assumeIsolatedUnsafeUnchecked().nonisolated(), future) promise.assumeIsolated().succeed(SuperNotSendable()) - }.wait() + } + if let runnable = loop as? RunnableEventLoop { + runnable.runForTests() + } + try f.wait() } - func testFutureChaining() throws { + func _futureChaining(loop: any EventLoop) throws { enum TestError: Error { case error } - let group = MultiThreadedEventLoopGroup.singleton - let loop = group.next() - - try loop.flatSubmit { + let f = loop.flatSubmit { let promise = loop.makePromise(of: SuperNotSendable.self) let future = promise.futureResult.assumeIsolated() let originalValue = SuperNotSendable() @@ -217,14 +256,15 @@ final class EventLoopFutureIsolatedTest: XCTestCase { promise.assumeIsolated().succeed(originalValue) return newFuture.map { _ in }.nonisolated() - }.wait() + } + if let runnable = loop as? RunnableEventLoop { + runnable.runForTests() + } + try f.wait() } - func testEventLoopIsolated() throws { - let group = MultiThreadedEventLoopGroup.singleton - let loop = group.next() - - let result: Int = try loop.flatSubmit { + func _eventLoopIsolated(loop: any EventLoop) throws { + let f = loop.flatSubmit { let value = SuperNotSendable() // Again, all of these need to close over value. In addition, @@ -240,7 +280,7 @@ final class EventLoopFutureIsolatedTest: XCTestCase { return val }.map { $0.x } - let secondFuture = isolated.scheduleTask(deadline: .now() + .milliseconds(50)) { + let secondFuture = isolated.scheduleTask(deadline: loop.now + .milliseconds(50)) { let val = SuperNotSendable() val.x = value.x + 1 return val @@ -252,7 +292,7 @@ final class EventLoopFutureIsolatedTest: XCTestCase { return val }.futureResult.map { $0.x } - let fourthFuture = isolated.flatScheduleTask(deadline: .now() + .milliseconds(50)) { + let fourthFuture = isolated.flatScheduleTask(deadline: loop.now + .milliseconds(50)) { let promise = loop.makePromise(of: Int.self) promise.succeed(value.x + 1) return promise.futureResult @@ -263,16 +303,17 @@ final class EventLoopFutureIsolatedTest: XCTestCase { [firstFuture, secondFuture, thirdFuture, fourthFuture], on: loop ) { $0 += $1 } - }.wait() + } + if let runnable = loop as? RunnableEventLoop { + runnable.advanceTimeForTests(by: .milliseconds(51)) + } + let result = try f.wait() XCTAssertEqual(result, 6 * 4) } - func testEventLoopIsolatedUnchecked() throws { - let group = MultiThreadedEventLoopGroup.singleton - let loop = group.next() - - let result: Int = try loop.flatSubmit { + func _eventLoopIsolatedUnchecked(loop: any EventLoop) throws { + let f = loop.flatSubmit { let value = SuperNotSendable() // Again, all of these need to close over value. In addition, @@ -288,7 +329,7 @@ final class EventLoopFutureIsolatedTest: XCTestCase { return val }.map { $0.x } - let secondFuture = isolated.scheduleTask(deadline: .now() + .milliseconds(50)) { + let secondFuture = isolated.scheduleTask(deadline: loop.now + .milliseconds(50)) { let val = SuperNotSendable() val.x = value.x + 1 return val @@ -300,7 +341,7 @@ final class EventLoopFutureIsolatedTest: XCTestCase { return val }.futureResult.map { $0.x } - let fourthFuture = isolated.flatScheduleTask(deadline: .now() + .milliseconds(50)) { + let fourthFuture = isolated.flatScheduleTask(deadline: loop.now + .milliseconds(50)) { let promise = loop.makePromise(of: Int.self) promise.succeed(value.x + 1) return promise.futureResult @@ -311,8 +352,150 @@ final class EventLoopFutureIsolatedTest: XCTestCase { [firstFuture, secondFuture, thirdFuture, fourthFuture], on: loop ) { $0 += $1 } - }.wait() + } + if let runnable = loop as? RunnableEventLoop { + runnable.advanceTimeForTests(by: .milliseconds(50)) + } + let result = try f.wait() XCTAssertEqual(result, 6 * 4) } + + // MARK: SelectableEL + func testCompletingPromiseWithNonSendableValue_SelectableEL() throws { + let loop = MultiThreadedEventLoopGroup.singleton.next() + try self._completingPromiseWithNonSendableValue(loop: loop) + } + + func testCompletingPromiseWithNonSendableResult_SelectableEL() throws { + let loop = MultiThreadedEventLoopGroup.singleton.next() + try self._completingPromiseWithNonSendableResult(loop: loop) + } + + func testCompletingPromiseWithNonSendableValueUnchecked_SelectableEL() throws { + let loop = MultiThreadedEventLoopGroup.singleton.next() + try self._completingPromiseWithNonSendableValueUnchecked(loop: loop) + } + + func testCompletingPromiseWithNonSendableResultUnchecked_SelectableEL() throws { + let loop = MultiThreadedEventLoopGroup.singleton.next() + try self._completingPromiseWithNonSendableResultUnchecked(loop: loop) + } + + func testBackAndForthUnwrapping_SelectableEL() throws { + let loop = MultiThreadedEventLoopGroup.singleton.next() + try self._backAndForthUnwrapping(loop: loop) + } + + func testBackAndForthUnwrappingUnchecked_SelectableEL() throws { + let loop = MultiThreadedEventLoopGroup.singleton.next() + try self._backAndForthUnwrappingUnchecked(loop: loop) + } + + func testFutureChaining_SelectableEL() throws { + let loop = MultiThreadedEventLoopGroup.singleton.next() + try self._futureChaining(loop: loop) + } + + func testEventLoopIsolated_SelectableEL() throws { + let loop = MultiThreadedEventLoopGroup.singleton.next() + try self._eventLoopIsolated(loop: loop) + } + + func testEventLoopIsolatedUnchecked_SelectableEL() throws { + let loop = MultiThreadedEventLoopGroup.singleton.next() + try self._eventLoopIsolatedUnchecked(loop: loop) + } + + // MARK: EmbeddedEL + func testCompletingPromiseWithNonSendableValue_EmbeddedEL() throws { + let loop = EmbeddedEventLoop() + try self._completingPromiseWithNonSendableValue(loop: loop) + } + + func testCompletingPromiseWithNonSendableResult_EmbeddedEL() throws { + let loop = EmbeddedEventLoop() + try self._completingPromiseWithNonSendableResult(loop: loop) + } + + func testCompletingPromiseWithNonSendableValueUnchecked_EmbeddedEL() throws { + let loop = EmbeddedEventLoop() + try self._completingPromiseWithNonSendableValueUnchecked(loop: loop) + } + + func testCompletingPromiseWithNonSendableResultUnchecked_EmbeddedEL() throws { + let loop = EmbeddedEventLoop() + try self._completingPromiseWithNonSendableResultUnchecked(loop: loop) + } + + func testBackAndForthUnwrapping_EmbeddedEL() throws { + let loop = EmbeddedEventLoop() + try self._backAndForthUnwrapping(loop: loop) + } + + func testBackAndForthUnwrappingUnchecked_EmbeddedEL() throws { + let loop = EmbeddedEventLoop() + try self._backAndForthUnwrappingUnchecked(loop: loop) + } + + func testFutureChaining_EmbeddedEL() throws { + let loop = EmbeddedEventLoop() + try self._futureChaining(loop: loop) + } + + func testEventLoopIsolated_EmbeddedEL() throws { + let loop = EmbeddedEventLoop() + try self._eventLoopIsolated(loop: loop) + } + + func testEventLoopIsolatedUnchecked_EmbeddedEL() throws { + let loop = EmbeddedEventLoop() + try self._eventLoopIsolatedUnchecked(loop: loop) + } + + // MARK: AsyncTestingEL + func testCompletingPromiseWithNonSendableValue_AsyncTestingEL() throws { + let loop = NIOAsyncTestingEventLoop() + try self._completingPromiseWithNonSendableValue(loop: loop) + } + + func testCompletingPromiseWithNonSendableResult_AsyncTestingEL() throws { + let loop = NIOAsyncTestingEventLoop() + try self._completingPromiseWithNonSendableResult(loop: loop) + } + + func testCompletingPromiseWithNonSendableValueUnchecked_AsyncTestingEL() throws { + let loop = NIOAsyncTestingEventLoop() + try self._completingPromiseWithNonSendableValueUnchecked(loop: loop) + } + + func testCompletingPromiseWithNonSendableResultUnchecked_AsyncTestingEL() throws { + let loop = NIOAsyncTestingEventLoop() + try self._completingPromiseWithNonSendableResultUnchecked(loop: loop) + } + + func testBackAndForthUnwrapping_AsyncTestingEL() throws { + let loop = NIOAsyncTestingEventLoop() + try self._backAndForthUnwrapping(loop: loop) + } + + func testBackAndForthUnwrappingUnchecked_AsyncTestingEL() throws { + let loop = NIOAsyncTestingEventLoop() + try self._backAndForthUnwrappingUnchecked(loop: loop) + } + + func testFutureChaining_AsyncTestingEL() throws { + let loop = NIOAsyncTestingEventLoop() + try self._futureChaining(loop: loop) + } + + func testEventLoopIsolated_AsyncTestingEL() throws { + let loop = NIOAsyncTestingEventLoop() + try self._eventLoopIsolated(loop: loop) + } + + func testEventLoopIsolatedUnchecked_AsyncTestingEL() throws { + let loop = NIOAsyncTestingEventLoop() + try self._eventLoopIsolatedUnchecked(loop: loop) + } }