From eb18e0057897d084b9cf76f1014f8052c87017d0 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 17 Jan 2025 13:06:55 +0000 Subject: [PATCH 1/2] Ensure we test the isolated fallback methods Motivation I'm about to change all our main event loops to use our new customisation points. That's great, but it'll have the effect of removing all the testing of our default implementations, which is not a good idea. So let's add some more tests. Modifications Define a "fallback" event loop for testing the isolated methods. Result Reduced likelihood of regressions in future. --- .../EventLoopFutureIsolatedTests.swift | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift b/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift index 4ef333934c1..cb7464a476d 100644 --- a/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift +++ b/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import NIOConcurrencyHelpers import NIOCore import NIOEmbedded import NIOPosix @@ -24,6 +25,53 @@ final class SuperNotSendable { @available(*, unavailable) extension SuperNotSendable: Sendable {} +// A very stupid event loop that implements as little of the protocol as possible. +// +// We use this to confirm that the fallback path for the isolated views works, by not implementing +// their fast-paths. Instead, we forward to the underlying implementation. We use `AsyncTestingEventLoop` +// to provide the backing implementation. +fileprivate final class FallbackEventLoop: RunnableEventLoop { + private let base: NIOAsyncTestingEventLoop + + init() { + self.base = .init() + } + + var now: NIODeadline { + self.base.now + } + + var inEventLoop: Bool { + self.base.inEventLoop + } + + func execute(_ task: @escaping @Sendable () -> Void) { + self.base.execute(task) + } + + func scheduleTask( + deadline: NIOCore.NIODeadline, _ task: @escaping @Sendable () throws -> T + ) -> NIOCore.Scheduled { + self.base.scheduleTask(deadline: deadline, task) + } + + func scheduleTask(in delay: NIOCore.TimeAmount, _ task: @escaping @Sendable () throws -> T) -> NIOCore.Scheduled { + self.base.scheduleTask(in: delay, task) + } + + func shutdownGracefully(queue: DispatchQueue, _ callback: @escaping @Sendable ((any Error)?) -> Void) { + self.base.shutdownGracefully(queue: queue, callback) + } + + func runForTests() { + self.base.runForTests() + } + + func advanceTimeForTests(by amount: TimeAmount) { + self.base.advanceTimeForTests(by: amount) + } +} + private protocol RunnableEventLoop: EventLoop { func runForTests() func advanceTimeForTests(by: TimeAmount) @@ -498,4 +546,50 @@ final class EventLoopFutureIsolatedTest: XCTestCase { let loop = NIOAsyncTestingEventLoop() try self._eventLoopIsolatedUnchecked(loop: loop) } + + // MARK: Fallback + func testCompletingPromiseWithNonSendableValue_Fallback() throws { + let loop = FallbackEventLoop() + try self._completingPromiseWithNonSendableValue(loop: loop) + } + + func testCompletingPromiseWithNonSendableResult_() throws { + let loop = FallbackEventLoop() + try self._completingPromiseWithNonSendableResult(loop: loop) + } + + func testCompletingPromiseWithNonSendableValueUnchecked_Fallback() throws { + let loop = FallbackEventLoop() + try self._completingPromiseWithNonSendableValueUnchecked(loop: loop) + } + + func testCompletingPromiseWithNonSendableResultUnchecked_Fallback() throws { + let loop = FallbackEventLoop() + try self._completingPromiseWithNonSendableResultUnchecked(loop: loop) + } + + func testBackAndForthUnwrapping_Fallback() throws { + let loop = FallbackEventLoop() + try self._backAndForthUnwrapping(loop: loop) + } + + func testBackAndForthUnwrappingUnchecked_Fallback() throws { + let loop = FallbackEventLoop() + try self._backAndForthUnwrappingUnchecked(loop: loop) + } + + func testFutureChaining_Fallback() throws { + let loop = FallbackEventLoop() + try self._futureChaining(loop: loop) + } + + func testEventLoopIsolated_Fallback() throws { + let loop = FallbackEventLoop() + try self._eventLoopIsolated(loop: loop) + } + + func testEventLoopIsolatedUnchecked_Fallback() throws { + let loop = FallbackEventLoop() + try self._eventLoopIsolatedUnchecked(loop: loop) + } } From 6a3d89f665c0321054f3f6613b68335051f35b62 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 20 Jan 2025 15:45:40 +0000 Subject: [PATCH 2/2] Formatter --- Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift b/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift index cb7464a476d..705d6319700 100644 --- a/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift +++ b/Tests/NIOPosixTests/EventLoopFutureIsolatedTests.swift @@ -30,7 +30,7 @@ extension SuperNotSendable: Sendable {} // We use this to confirm that the fallback path for the isolated views works, by not implementing // their fast-paths. Instead, we forward to the underlying implementation. We use `AsyncTestingEventLoop` // to provide the backing implementation. -fileprivate final class FallbackEventLoop: RunnableEventLoop { +private final class FallbackEventLoop: RunnableEventLoop { private let base: NIOAsyncTestingEventLoop init() { @@ -50,12 +50,16 @@ fileprivate final class FallbackEventLoop: RunnableEventLoop { } func scheduleTask( - deadline: NIOCore.NIODeadline, _ task: @escaping @Sendable () throws -> T + deadline: NIOCore.NIODeadline, + _ task: @escaping @Sendable () throws -> T ) -> NIOCore.Scheduled { self.base.scheduleTask(deadline: deadline, task) } - func scheduleTask(in delay: NIOCore.TimeAmount, _ task: @escaping @Sendable () throws -> T) -> NIOCore.Scheduled { + func scheduleTask( + in delay: NIOCore.TimeAmount, + _ task: @escaping @Sendable () throws -> T + ) -> NIOCore.Scheduled { self.base.scheduleTask(in: delay, task) }