Skip to content

Commit 1c30f0f

Browse files
klaaspieterLukasa
andauthored
Support options on AsyncTestingChannel and EmbeddedChannel (#3308)
Support options on AsyncTestingChannel and EmbeddedChannel Fixes #3305 ### Motivation: The channels are intended for testing purposes so while most options have no practical use setting and getting them can be useful for testing. For example a traceroute implementation will set TTL to the current hop number. Testing such an implementation requires the channels to pretend to support the TTL option. ### Modifications: Added option storage to AsyncTestingChannel and EmbedededChannel and made `getOptionSync` and `setOptionSync` read and write from that storage. ### Result: EmbeddedChannel and AsyncTestingChannel support changing their options. --------- Co-authored-by: Cory Benfield <[email protected]>
1 parent a114cfb commit 1c30f0f

File tree

4 files changed

+103
-15
lines changed

4 files changed

+103
-15
lines changed

Sources/NIOEmbedded/AsyncTestingChannel.swift

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -205,15 +205,20 @@ public final class NIOAsyncTestingChannel: Channel {
205205
nonisolated(unsafe) var channelcore: EmbeddedChannelCore!
206206
nonisolated(unsafe) private var _pipeline: ChannelPipeline!
207207

208-
private struct State {
208+
@usableFromInline
209+
internal struct State: Sendable {
209210
var isWritable: Bool
210211
var localAddress: SocketAddress?
211212
var remoteAddress: SocketAddress?
213+
214+
@usableFromInline
215+
var options: [(option: any ChannelOption, value: any Sendable)]
212216
}
213217

214218
/// Guards any of the getters/setters that can be accessed from any thread.
215-
private let stateLock = NIOLockedValueBox(
216-
State(isWritable: true, localAddress: nil, remoteAddress: nil)
219+
@usableFromInline
220+
internal let _stateLock = NIOLockedValueBox(
221+
State(isWritable: true, localAddress: nil, remoteAddress: nil, options: [])
217222
)
218223

219224
/// - see: `Channel._channelCore`
@@ -229,10 +234,10 @@ public final class NIOAsyncTestingChannel: Channel {
229234
/// - see: `Channel.isWritable`
230235
public var isWritable: Bool {
231236
get {
232-
self.stateLock.withLockedValue { $0.isWritable }
237+
self._stateLock.withLockedValue { $0.isWritable }
233238
}
234239
set {
235-
self.stateLock.withLockedValue {
240+
self._stateLock.withLockedValue {
236241
$0.isWritable = newValue
237242
}
238243
}
@@ -241,10 +246,10 @@ public final class NIOAsyncTestingChannel: Channel {
241246
/// - see: `Channel.localAddress`
242247
public var localAddress: SocketAddress? {
243248
get {
244-
self.stateLock.withLockedValue { $0.localAddress }
249+
self._stateLock.withLockedValue { $0.localAddress }
245250
}
246251
set {
247-
self.stateLock.withLockedValue {
252+
self._stateLock.withLockedValue {
248253
$0.localAddress = newValue
249254
}
250255
}
@@ -253,15 +258,20 @@ public final class NIOAsyncTestingChannel: Channel {
253258
/// - see: `Channel.remoteAddress`
254259
public var remoteAddress: SocketAddress? {
255260
get {
256-
self.stateLock.withLockedValue { $0.remoteAddress }
261+
self._stateLock.withLockedValue { $0.remoteAddress }
257262
}
258263
set {
259-
self.stateLock.withLockedValue {
264+
self._stateLock.withLockedValue {
260265
$0.remoteAddress = newValue
261266
}
262267
}
263268
}
264269

270+
/// The `ChannelOption`s set on this channel.
271+
/// - see: `NIOAsyncTestingChannel.setOption`
272+
public var options: [(option: any ChannelOption, value: any Sendable)] {
273+
self._stateLock.withLockedValue { $0.options }
274+
}
265275
/// Create a new instance.
266276
///
267277
/// During creation it will automatically also register itself on the ``NIOAsyncTestingEventLoop``.
@@ -572,12 +582,12 @@ public final class NIOAsyncTestingChannel: Channel {
572582

573583
@inlinable
574584
internal func setOptionSync<Option: ChannelOption>(_ option: Option, value: Option.Value) {
585+
addOption(option, value: value)
586+
575587
if option is ChannelOptions.Types.AllowRemoteHalfClosureOption {
576588
self.allowRemoteHalfClosure = value as! Bool
577589
return
578590
}
579-
// No other options supported
580-
fatalError("option not supported")
581591
}
582592

583593
/// - see: `Channel.getOption`
@@ -606,7 +616,32 @@ public final class NIOAsyncTestingChannel: Channel {
606616

607617
return result as! Option.Value
608618
}
609-
fatalError("option \(option) not supported")
619+
620+
guard let value = self.optionValue(for: option) else {
621+
fatalError("option \(option) not supported")
622+
}
623+
624+
return value
625+
}
626+
627+
@inlinable
628+
internal func optionValue<Option: ChannelOption>(for option: Option) -> Option.Value? {
629+
self.options.first(where: { $0.option is Option })?.value as? Option.Value
630+
}
631+
632+
@inlinable
633+
internal func addOption<Option: ChannelOption>(_ option: Option, value: Option.Value) {
634+
// override the option if it exists
635+
self._stateLock.withLockedValue { state in
636+
var options = state.options
637+
let optionIndex = options.firstIndex(where: { $0.option is Option })
638+
if let optionIndex = optionIndex {
639+
options[optionIndex] = (option, value)
640+
} else {
641+
options.append((option, value))
642+
}
643+
state.options = options
644+
}
610645
}
611646

612647
/// Fires the (outbound) `bind` event through the `ChannelPipeline`. If the event hits the ``NIOAsyncTestingChannel`` which

Sources/NIOEmbedded/Embedded.swift

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,13 @@ public final class EmbeddedChannel: Channel {
795795
/// - see: `Channel.isWritable`
796796
public var isWritable: Bool = true
797797

798+
@usableFromInline
799+
internal var _options: [(option: any ChannelOption, value: any Sendable)] = []
800+
801+
/// The `ChannelOption`s set on this channel.
802+
/// - see: `Embedded.setOption`
803+
public var options: [(option: any ChannelOption, value: any Sendable)] { self._options }
804+
798805
/// Synchronously closes the `EmbeddedChannel`.
799806
///
800807
/// Errors in the `EmbeddedChannel` can be consumed using `throwIfErrorCaught`.
@@ -1023,12 +1030,13 @@ public final class EmbeddedChannel: Channel {
10231030
@inlinable
10241031
internal func setOptionSync<Option: ChannelOption>(_ option: Option, value: Option.Value) {
10251032
self.embeddedEventLoop.checkCorrectThread()
1033+
1034+
self.addOption(option, value: value)
1035+
10261036
if option is ChannelOptions.Types.AllowRemoteHalfClosureOption {
10271037
self.allowRemoteHalfClosure = value as! Bool
10281038
return
10291039
}
1030-
// No other options supported
1031-
fatalError("option not supported")
10321040
}
10331041

10341042
/// - see: `Channel.getOption`
@@ -1055,7 +1063,26 @@ public final class EmbeddedChannel: Channel {
10551063

10561064
return result as! Option.Value
10571065
}
1058-
fatalError("option \(option) not supported")
1066+
1067+
guard let value = optionValue(for: option) else {
1068+
fatalError("option \(option) not supported")
1069+
}
1070+
1071+
return value
1072+
}
1073+
1074+
@inlinable
1075+
internal func addOption<Option: ChannelOption>(_ option: Option, value: Option.Value) {
1076+
if let optionIndex = self._options.firstIndex(where: { $0.option is Option }) {
1077+
self._options[optionIndex] = (option: option, value: value)
1078+
} else {
1079+
self._options.append((option: option, value: value))
1080+
}
1081+
}
1082+
1083+
@inlinable
1084+
internal func optionValue<Option: ChannelOption>(for option: Option) -> Option.Value? {
1085+
self.options.first(where: { $0.option is Option })?.value as? Option.Value
10591086
}
10601087

10611088
/// Fires the (outbound) `bind` event through the `ChannelPipeline`. If the event hits the `EmbeddedChannel` which

Tests/NIOEmbeddedTests/AsyncTestingChannelTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,19 @@ class AsyncTestingChannelTests: XCTestCase {
681681
try await XCTAsyncAssertEqual(buf, try await channel.waitForOutboundWrite(as: ByteBuffer.self))
682682
try await XCTAsyncAssertTrue(try await channel.finish().isClean)
683683
}
684+
685+
func testGetSetOption() async throws {
686+
let channel = NIOAsyncTestingChannel()
687+
let option = ChannelOptions.socket(IPPROTO_IP, IP_TTL)
688+
let _ = try await channel.setOption(option, value: 1).get()
689+
690+
let optionValue1 = try await channel.getOption(option).get()
691+
XCTAssertEqual(1, optionValue1)
692+
693+
let _ = try await channel.setOption(option, value: 2).get()
694+
let optionValue2 = try await channel.getOption(option).get()
695+
XCTAssertEqual(2, optionValue2)
696+
}
684697
}
685698

686699
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)

Tests/NIOEmbeddedTests/EmbeddedChannelTest.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,4 +692,17 @@ class EmbeddedChannelTest: XCTestCase {
692692
XCTAssertEqual(buf, try channel.readOutbound(as: ByteBuffer.self))
693693
XCTAssertTrue(try channel.finish().isClean)
694694
}
695+
696+
func testGetSetOption() throws {
697+
let channel = EmbeddedChannel()
698+
let option = ChannelOptions.socket(IPPROTO_IP, IP_TTL)
699+
let _ = channel.setOption(option, value: 1)
700+
701+
let optionValue1 = try channel.getOption(option).wait()
702+
XCTAssertEqual(1, optionValue1)
703+
704+
let _ = channel.setOption(option, value: 2)
705+
let optionValue2 = try channel.getOption(option).wait()
706+
XCTAssertEqual(2, optionValue2)
707+
}
695708
}

0 commit comments

Comments
 (0)