From 329ef54dd8204dcd2ea82860d808f4a75fd6f5a5 Mon Sep 17 00:00:00 2001 From: hamzahrmalik Date: Mon, 13 Jan 2025 15:21:42 +0000 Subject: [PATCH] Add channel initializer closure to NIOAsyncTestingChannel.init (#3053) Since nio 2.78, adding handlers to a pipeline requires the handlers to be sendable. That makes the [NIOAsyncTestingChannel.init(handlers:loop:)](https://swiftpackageindex.com/apple/swift-nio/2.78.0/documentation/nioembedded/nioasynctestingchannel/init(handlers:loop:)) function cumbersome, because you cannot create handlers and then call the function (unless your handlers are Sendable) even if you never use the handlers elsewhere. This PR adds a new initializer which takes a closure. The closure is run on-loop before the channel is registered. This means we can do: ```swift let channel = try await NIOAsyncTestingChannel { let handler = MyUnsendableHandler() try $0.pipeline.syncOperations.addHandler(handler) } ``` --- Sources/NIOEmbedded/AsyncTestingChannel.swift | 25 +++++++++++++++---- .../AsyncTestingChannelTests.swift | 11 ++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Sources/NIOEmbedded/AsyncTestingChannel.swift b/Sources/NIOEmbedded/AsyncTestingChannel.swift index d3a4834f7e..8e0ca9c33a 100644 --- a/Sources/NIOEmbedded/AsyncTestingChannel.swift +++ b/Sources/NIOEmbedded/AsyncTestingChannel.swift @@ -307,12 +307,27 @@ public final class NIOAsyncTestingChannel: Channel { handlers: [ChannelHandler & Sendable], loop: NIOAsyncTestingEventLoop = NIOAsyncTestingEventLoop() ) async { - self.init(loop: loop) - - try! await self._pipeline.addHandlers(handlers) + try! await self.init(loop: loop) { channel in + try channel.pipeline.syncOperations.addHandlers(handlers) + } + } - // This will never throw... - try! await self.register() + /// Create a new instance. + /// + /// During creation it will automatically also register itself on the ``NIOAsyncTestingEventLoop``. + /// + /// - Parameters: + /// - loop: The ``NIOAsyncTestingEventLoop`` to use. + /// - channelInitializer: The initialization closure which will be run on the `EventLoop` before registration. This could be used to add handlers using `syncOperations`. + public convenience init( + loop: NIOAsyncTestingEventLoop = NIOAsyncTestingEventLoop(), + channelInitializer: @escaping @Sendable (NIOAsyncTestingChannel) throws -> Void + ) async throws { + self.init(loop: loop) + try await loop.submit { + try channelInitializer(self) + }.get() + try await self.register() } /// Asynchronously closes the ``NIOAsyncTestingChannel``. diff --git a/Tests/NIOEmbeddedTests/AsyncTestingChannelTests.swift b/Tests/NIOEmbeddedTests/AsyncTestingChannelTests.swift index 5022dcd3ca..4e9cd07547 100644 --- a/Tests/NIOEmbeddedTests/AsyncTestingChannelTests.swift +++ b/Tests/NIOEmbeddedTests/AsyncTestingChannelTests.swift @@ -65,6 +65,17 @@ class AsyncTestingChannelTests: XCTestCase { XCTAssertNoThrow(try channel.pipeline.removeHandler(name: "handler2").wait()) } + func testClosureInit() async throws { + final class Handler: ChannelInboundHandler, Sendable { + typealias InboundIn = Never + } + + let channel = try await NIOAsyncTestingChannel { + try $0.pipeline.syncOperations.addHandler(Handler()) + } + XCTAssertNoThrow(try channel.pipeline.handler(type: Handler.self).wait()) + } + func testWaitForInboundWrite() async throws { let channel = NIOAsyncTestingChannel() let task = Task {