Create NIOTestUtils & add B2MD verifier (#939)
Motivation: When writing B2MDs, there are a couple of scenarios that always need to be tested: firehose feeding, drip feeding, many messages, ... It's tedious writing those tests over and over again for every B2MD. Modifications: - Add a simple B2MD verifier that users can use in unit tests. - Add a new, public `NIOTestUtils` module which contains utilities mostly useful for testing. Crucially however, it does not depend on `XCTest` so it can be used it all targets. Result: Hopefully fewer bugs in B2MDs.
This commit is contained in:
parent
d1c7cd0bac
commit
5513bb202a
|
@ -59,16 +59,20 @@ var targets: [PackageDescription.Target] = [
|
|||
dependencies: ["NIO"]),
|
||||
.target(name: "NIOUDPEchoClient",
|
||||
dependencies: ["NIO"]),
|
||||
.target(name: "NIOTestUtils",
|
||||
dependencies: ["NIO"]),
|
||||
.testTarget(name: "NIOTests",
|
||||
dependencies: ["NIO", "NIOFoundationCompat"]),
|
||||
.testTarget(name: "NIOConcurrencyHelpersTests",
|
||||
dependencies: ["NIOConcurrencyHelpers"]),
|
||||
.testTarget(name: "NIOHTTP1Tests",
|
||||
dependencies: ["NIOHTTP1", "NIOFoundationCompat"]),
|
||||
dependencies: ["NIOHTTP1", "NIOFoundationCompat", "NIOTestUtils"]),
|
||||
.testTarget(name: "NIOTLSTests",
|
||||
dependencies: ["NIO", "NIOTLS", "NIOFoundationCompat"]),
|
||||
.testTarget(name: "NIOWebSocketTests",
|
||||
dependencies: ["NIO", "NIOWebSocket"]),
|
||||
.testTarget(name: "NIOTestUtilsTests",
|
||||
dependencies: ["NIOTestUtils"]),
|
||||
]
|
||||
|
||||
let package = Package(
|
||||
|
@ -94,6 +98,7 @@ let package = Package(
|
|||
.library(name: "NIOConcurrencyHelpers", targets: ["NIOConcurrencyHelpers"]),
|
||||
.library(name: "NIOFoundationCompat", targets: ["NIOFoundationCompat"]),
|
||||
.library(name: "NIOWebSocket", targets: ["NIOWebSocket"]),
|
||||
.library(name: "NIOTestUtils", targets: ["NIOTestUtils"]),
|
||||
],
|
||||
dependencies: [
|
||||
],
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import NIO
|
||||
|
||||
public enum ByteToMessageDecoderVerifier {
|
||||
/// - seealso: verifyDecoder(inputOutputPairs:decoderFactory:)
|
||||
///
|
||||
/// Verify `ByteToMessageDecoder`s with `String` inputs
|
||||
public static func verifyDecoder<Decoder: ByteToMessageDecoder>(stringInputOutputPairs: [(String, [Decoder.InboundOut])],
|
||||
decoderFactory: @escaping () -> Decoder) throws where Decoder.InboundOut: Equatable {
|
||||
let alloc = ByteBufferAllocator()
|
||||
let ioPairs = stringInputOutputPairs.map { (ioPair: (String, [Decoder.InboundOut])) -> (ByteBuffer, [Decoder.InboundOut]) in
|
||||
var buffer = alloc.buffer(capacity: ioPair.0.utf8.count)
|
||||
buffer.writeString(ioPair.0)
|
||||
return (buffer, ioPair.1)
|
||||
}
|
||||
return try ByteToMessageDecoderVerifier.verifyDecoder(inputOutputPairs: ioPairs, decoderFactory: decoderFactory)
|
||||
}
|
||||
|
||||
/// Verifies a `ByteToMessageDecoder` by performing a number of tests.
|
||||
///
|
||||
/// This method is mostly useful in unit tests for `ByteToMessageDecoder`s. It feeds the inputs from
|
||||
/// `inputOutputPairs` into the decoder in various ways and expects the decoder to produce the outputs from
|
||||
/// `inputOutputPairs`.
|
||||
///
|
||||
/// The verification performs various tests, for example:
|
||||
///
|
||||
/// - drip feeding the bytes, one by one
|
||||
/// - sending many messages in one `ByteBuffer`
|
||||
/// - sending each complete message in one `ByteBuffer`
|
||||
///
|
||||
/// For `ExampleDecoder` that produces `ExampleDecoderOutput`s you would use this method the following way:
|
||||
///
|
||||
/// var exampleInput1 = channel.allocator.buffer(capacity: 16)
|
||||
/// exampleInput1.writeString("example-in1")
|
||||
/// var exampleInput2 = channel.allocator.buffer(capacity: 16)
|
||||
/// exampleInput2.writeString("example-in2")
|
||||
/// let expectedInOuts = [(exampleInput1, ExampleDecoderOutput("1")),
|
||||
/// (exampleInput2, ExampleDecoderOutput("2"))
|
||||
/// ]
|
||||
/// XCTAssertNoThrow(try ByteToMessageDecoderVerifier.verifyDecoder(inputOutputPairs: expectedInOuts,
|
||||
/// decoderFactory: { ExampleDecoder() }))
|
||||
public static func verifyDecoder<Decoder: ByteToMessageDecoder>(inputOutputPairs: [(ByteBuffer, [Decoder.InboundOut])],
|
||||
decoderFactory: @escaping () -> Decoder) throws where Decoder.InboundOut: Equatable {
|
||||
typealias Out = Decoder.InboundOut
|
||||
|
||||
func verifySimple(channel: RecordingChannel) throws {
|
||||
for (input, expectedOutputs) in inputOutputPairs.shuffled() {
|
||||
try channel.writeInbound(input)
|
||||
for expectedOutput in expectedOutputs {
|
||||
guard let actualOutput = try channel.readInbound(as: Out.self) else {
|
||||
throw VerificationError<Out>(inputs: channel.inboundWrites,
|
||||
errorCode: .underProduction(expectedOutput))
|
||||
}
|
||||
guard actualOutput == expectedOutput else {
|
||||
throw VerificationError<Out>(inputs: channel.inboundWrites,
|
||||
errorCode: .wrongProduction(actual: actualOutput,
|
||||
expected: expectedOutput))
|
||||
}
|
||||
}
|
||||
let actualExtraOutput = try channel.readInbound(as: Out.self)
|
||||
guard actualExtraOutput == nil else {
|
||||
throw VerificationError<Out>(inputs: channel.inboundWrites,
|
||||
errorCode: .overProduction(actualExtraOutput!))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyDripFeed(channel: RecordingChannel) throws {
|
||||
for _ in 0..<10 {
|
||||
for (input, expectedOutputs) in inputOutputPairs.shuffled() {
|
||||
for c in input.readableBytesView {
|
||||
var buffer = channel.allocator.buffer(capacity: 12)
|
||||
buffer.writeString("BEFORE")
|
||||
buffer.writeInteger(c)
|
||||
buffer.writeString("AFTER")
|
||||
buffer.moveReaderIndex(forwardBy: 6)
|
||||
buffer.moveWriterIndex(to: buffer.readerIndex + 1)
|
||||
try channel.writeInbound(buffer)
|
||||
}
|
||||
for expectedOutput in expectedOutputs {
|
||||
guard let actualOutput = try channel.readInbound(as: Out.self) else {
|
||||
throw VerificationError<Out>(inputs: channel.inboundWrites,
|
||||
errorCode: .underProduction(expectedOutput))
|
||||
}
|
||||
guard actualOutput == expectedOutput else {
|
||||
throw VerificationError<Out>(inputs: channel.inboundWrites,
|
||||
errorCode: .wrongProduction(actual: actualOutput,
|
||||
expected: expectedOutput))
|
||||
}
|
||||
}
|
||||
let actualExtraOutput = try channel.readInbound(as: Out.self)
|
||||
guard actualExtraOutput == nil else {
|
||||
throw VerificationError<Out>(inputs: channel.inboundWrites,
|
||||
errorCode: .overProduction(actualExtraOutput!))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyManyAtOnce(channel: RecordingChannel) throws {
|
||||
var overallBuffer = channel.allocator.buffer(capacity: 1024)
|
||||
var overallExpecteds: [Out] = []
|
||||
|
||||
for _ in 0..<10 {
|
||||
for (var input, expectedOutputs) in inputOutputPairs.shuffled() {
|
||||
overallBuffer.writeBuffer(&input)
|
||||
overallExpecteds.append(contentsOf: expectedOutputs)
|
||||
}
|
||||
}
|
||||
|
||||
try channel.writeInbound(overallBuffer)
|
||||
for expectedOutput in overallExpecteds {
|
||||
guard let actualOutput = try channel.readInbound(as: Out.self) else {
|
||||
throw VerificationError<Out>(inputs: channel.inboundWrites,
|
||||
errorCode: .underProduction(expectedOutput))
|
||||
}
|
||||
guard actualOutput == expectedOutput else {
|
||||
throw VerificationError<Out>(inputs: channel.inboundWrites,
|
||||
errorCode: .wrongProduction(actual: actualOutput,
|
||||
expected: expectedOutput))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let decoder: Decoder = decoderFactory()
|
||||
let channel = RecordingChannel(EmbeddedChannel(handler: ByteToMessageHandler<Decoder>(decoder)))
|
||||
|
||||
try verifySimple(channel: channel)
|
||||
try verifyDripFeed(channel: channel)
|
||||
try verifyManyAtOnce(channel: channel)
|
||||
|
||||
if case .leftOvers(inbound: let ib, outbound: let ob, pendingOutbound: let pob) = try channel.finish() {
|
||||
throw VerificationError<Out>(inputs: channel.inboundWrites,
|
||||
errorCode: .leftOversOnDeconstructingChannel(inbound: ib,
|
||||
outbound: ob,
|
||||
pendingOutbound: pob))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ByteToMessageDecoderVerifier {
|
||||
private class RecordingChannel {
|
||||
private let actualChannel: EmbeddedChannel
|
||||
private(set) var inboundWrites: [ByteBuffer] = []
|
||||
|
||||
init(_ actualChannel: EmbeddedChannel) {
|
||||
self.actualChannel = actualChannel
|
||||
}
|
||||
|
||||
func readInbound<T>(as type: T.Type = T.self) throws -> T? {
|
||||
return try self.actualChannel.readInbound()
|
||||
}
|
||||
|
||||
@discardableResult public func writeInbound(_ data: ByteBuffer) throws -> EmbeddedChannel.BufferState {
|
||||
self.inboundWrites.append(data)
|
||||
return try self.actualChannel.writeInbound(data)
|
||||
}
|
||||
|
||||
var allocator: ByteBufferAllocator {
|
||||
return self.actualChannel.allocator
|
||||
}
|
||||
|
||||
func finish() throws -> EmbeddedChannel.LeftOverState {
|
||||
return try self.actualChannel.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ByteToMessageDecoderVerifier {
|
||||
/// A `VerificationError` is thrown when the verification of a `ByteToMessageDecoder` failed.
|
||||
public struct VerificationError<OutputType: Equatable>: Error {
|
||||
/// Contains the `inputs` that were passed to the `ByteToMessageDecoder` at the point where it failed
|
||||
/// verification.
|
||||
public var inputs: [ByteBuffer]
|
||||
|
||||
/// `errorCode` describes the concrete problem that was detected.
|
||||
public var errorCode: ErrorCode
|
||||
|
||||
public enum ErrorCode {
|
||||
/// The `errorCode` will be `wrongProduction` when the `expected` output didn't match the `actual`
|
||||
/// output.
|
||||
case wrongProduction(actual: OutputType, expected: OutputType)
|
||||
|
||||
/// The `errorCode` will be set to `overProduction` when a decoding result was yielded where
|
||||
/// nothing was expected.
|
||||
case overProduction(OutputType)
|
||||
|
||||
/// The `errorCode` will be set to `underProduction` when a decoder didn't yield output when output was
|
||||
/// expected. The expected output is delivered as the associated value.
|
||||
case underProduction(OutputType)
|
||||
|
||||
/// The `errorCode` will be set to `leftOversOnDeconstructionChannel` if there were left-over items
|
||||
/// in the `Channel` on deconstruction. This usually means that your `ByteToMessageDecoder` did not process
|
||||
/// certain items.
|
||||
case leftOversOnDeconstructingChannel(inbound: [NIOAny], outbound: [NIOAny], pendingOutbound: [NIOAny])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import XCTest
|
|||
@testable import NIOConcurrencyHelpersTests
|
||||
@testable import NIOHTTP1Tests
|
||||
@testable import NIOTLSTests
|
||||
@testable import NIOTestUtilsTests
|
||||
@testable import NIOTests
|
||||
@testable import NIOWebSocketTests
|
||||
|
||||
|
@ -40,6 +41,7 @@ import XCTest
|
|||
testCase(ByteBufferTest.allTests),
|
||||
testCase(ByteBufferUtilsTest.allTests),
|
||||
testCase(ByteToMessageDecoderTest.allTests),
|
||||
testCase(ByteToMessageDecoderVerifierTest.allTests),
|
||||
testCase(ChannelNotificationTest.allTests),
|
||||
testCase(ChannelOptionStorageTest.allTests),
|
||||
testCase(ChannelPipelineTest.allTests),
|
||||
|
|
|
@ -45,6 +45,7 @@ extension HTTPDecoderTest {
|
|||
("testNonASCIIWorksAsHeaderValue", testNonASCIIWorksAsHeaderValue),
|
||||
("testDoesNotDeliverLeftoversUnnecessarily", testDoesNotDeliverLeftoversUnnecessarily),
|
||||
("testHTTPResponseWithoutHeaders", testHTTPResponseWithoutHeaders),
|
||||
("testBasicVerifications", testBasicVerifications),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import XCTest
|
||||
import NIO
|
||||
import NIOHTTP1
|
||||
import NIOTestUtils
|
||||
|
||||
class HTTPDecoderTest: XCTestCase {
|
||||
private var channel: EmbeddedChannel!
|
||||
|
@ -547,4 +548,49 @@ class HTTPDecoderTest: XCTestCase {
|
|||
XCTAssertNoThrow(XCTAssertEqual(HTTPClientResponsePart.head(.init(version: .init(major: 1, minor: 0),
|
||||
status: .ok)), try channel.readInbound()))
|
||||
}
|
||||
|
||||
func testBasicVerifications() {
|
||||
let byteBufferContainingJustAnX: ByteBuffer = {
|
||||
var buffer = ByteBufferAllocator().buffer(capacity: 1)
|
||||
buffer.writeString("X")
|
||||
return buffer
|
||||
}()
|
||||
let expectedInOuts: [(String, [HTTPServerRequestPart])] = [
|
||||
("GET / HTTP/1.1\r\n\r\n",
|
||||
[.head(.init(version: .init(major: 1, minor: 1), method: .GET, uri: "/")),
|
||||
.end(nil)]),
|
||||
("POST /foo HTTP/1.1\r\n\r\n",
|
||||
[.head(.init(version: .init(major: 1, minor: 1), method: .POST, uri: "/foo")),
|
||||
.end(nil)]),
|
||||
("POST / HTTP/1.1\r\ncontent-length: 1\r\n\r\nX",
|
||||
[.head(.init(version: .init(major: 1, minor: 1),
|
||||
method: .POST,
|
||||
uri: "/",
|
||||
headers: .init([("content-length", "1")]))),
|
||||
.body(byteBufferContainingJustAnX),
|
||||
.end(nil)]),
|
||||
("POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n1\r\nX\r\n0\r\n\r\n",
|
||||
[.head(.init(version: .init(major: 1, minor: 1),
|
||||
method: .POST,
|
||||
uri: "/",
|
||||
headers: .init([("transfer-encoding", "chunked")]))),
|
||||
.body(byteBufferContainingJustAnX),
|
||||
.end(nil)]),
|
||||
("POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\none: two\r\n\r\n1\r\nX\r\n0\r\nfoo: bar\r\n\r\n",
|
||||
[.head(.init(version: .init(major: 1, minor: 1),
|
||||
method: .POST,
|
||||
uri: "/",
|
||||
headers: .init([("transfer-encoding", "chunked"), ("one", "two")]))),
|
||||
.body(byteBufferContainingJustAnX),
|
||||
.end(.init([("foo", "bar")]))]),
|
||||
]
|
||||
|
||||
let expectedInOutsBB: [(ByteBuffer, [HTTPServerRequestPart])] = expectedInOuts.map { io in
|
||||
var buffer = ByteBufferAllocator().buffer(capacity: io.0.utf8.count)
|
||||
buffer.writeString(io.0)
|
||||
return (buffer, io.1)
|
||||
}
|
||||
XCTAssertNoThrow(try ByteToMessageDecoderVerifier.verifyDecoder(inputOutputPairs: expectedInOutsBB,
|
||||
decoderFactory: { HTTPRequestDecoder() }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// ByteToMessageDecoderVerifierTest+XCTest.swift
|
||||
//
|
||||
import XCTest
|
||||
|
||||
///
|
||||
/// NOTE: This file was generated by generate_linux_tests.rb
|
||||
///
|
||||
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
|
||||
///
|
||||
|
||||
extension ByteToMessageDecoderVerifierTest {
|
||||
|
||||
static var allTests : [(String, (ByteToMessageDecoderVerifierTest) -> () throws -> Void)] {
|
||||
return [
|
||||
("testWrongResults", testWrongResults),
|
||||
("testNoOutputWhenWeShouldHaveOutput", testNoOutputWhenWeShouldHaveOutput),
|
||||
("testOutputWhenWeShouldNotProduceOutput", testOutputWhenWeShouldNotProduceOutput),
|
||||
("testLeftovers", testLeftovers),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@testable import NIO
|
||||
import NIOTestUtils
|
||||
import XCTest
|
||||
|
||||
typealias VerificationError = ByteToMessageDecoderVerifier.VerificationError<String>
|
||||
|
||||
class ByteToMessageDecoderVerifierTest: XCTestCase {
|
||||
func testWrongResults() {
|
||||
struct AlwaysProduceY: ByteToMessageDecoder {
|
||||
typealias InboundOut = String
|
||||
|
||||
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
|
||||
buffer.moveReaderIndex(to: buffer.writerIndex)
|
||||
context.fireChannelRead(self.wrapInboundOut("Y"))
|
||||
return .needMoreData
|
||||
}
|
||||
|
||||
func decodeLast(context: ChannelHandlerContext,
|
||||
buffer: inout ByteBuffer,
|
||||
seenEOF: Bool) throws -> DecodingState {
|
||||
while try self.decode(context: context, buffer: &buffer) == .continue {}
|
||||
return .needMoreData
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertThrowsError(try ByteToMessageDecoderVerifier.verifyDecoder(stringInputOutputPairs: [("x", ["x"])],
|
||||
decoderFactory: AlwaysProduceY.init)) {
|
||||
error in
|
||||
switch error {
|
||||
case let error as VerificationError:
|
||||
XCTAssertEqual(1, error.inputs.count)
|
||||
switch error.errorCode {
|
||||
case .wrongProduction(actual: let actual, expected: let expected):
|
||||
XCTAssertEqual("Y", actual)
|
||||
XCTAssertEqual("x", expected)
|
||||
default:
|
||||
XCTFail("unexpected error: \(error)")
|
||||
}
|
||||
default:
|
||||
XCTFail("unexpected error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testNoOutputWhenWeShouldHaveOutput() {
|
||||
struct NeverProduce: ByteToMessageDecoder {
|
||||
typealias InboundOut = String
|
||||
|
||||
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
|
||||
buffer.moveReaderIndex(to: buffer.writerIndex)
|
||||
return .needMoreData
|
||||
}
|
||||
|
||||
func decodeLast(context: ChannelHandlerContext,
|
||||
buffer: inout ByteBuffer,
|
||||
seenEOF: Bool) throws -> DecodingState {
|
||||
while try self.decode(context: context, buffer: &buffer) == .continue {}
|
||||
return .needMoreData
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertThrowsError(try ByteToMessageDecoderVerifier.verifyDecoder(stringInputOutputPairs: [("x", ["x"])],
|
||||
decoderFactory: NeverProduce.init)) {
|
||||
error in
|
||||
switch error {
|
||||
case let error as VerificationError:
|
||||
XCTAssertEqual(1, error.inputs.count)
|
||||
switch error.errorCode {
|
||||
case .underProduction(let expected):
|
||||
XCTAssertEqual("x", expected)
|
||||
default:
|
||||
XCTFail("unexpected error: \(error)")
|
||||
}
|
||||
default:
|
||||
XCTFail("unexpected error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testOutputWhenWeShouldNotProduceOutput() {
|
||||
struct ProduceTooEarly: ByteToMessageDecoder {
|
||||
typealias InboundOut = String
|
||||
|
||||
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
|
||||
context.fireChannelRead(self.wrapInboundOut("Y"))
|
||||
return .needMoreData
|
||||
}
|
||||
|
||||
func decodeLast(context: ChannelHandlerContext,
|
||||
buffer: inout ByteBuffer,
|
||||
seenEOF: Bool) throws -> DecodingState {
|
||||
while try self.decode(context: context, buffer: &buffer) == .continue {}
|
||||
return .needMoreData
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertThrowsError(try ByteToMessageDecoderVerifier.verifyDecoder(stringInputOutputPairs: [("xxxxxx", ["Y"])],
|
||||
decoderFactory: ProduceTooEarly.init)) {
|
||||
error in
|
||||
switch error {
|
||||
case let error as VerificationError:
|
||||
switch error.errorCode {
|
||||
case .overProduction(let actual):
|
||||
XCTAssertEqual("Y", actual)
|
||||
default:
|
||||
XCTFail("unexpected error: \(error)")
|
||||
}
|
||||
default:
|
||||
XCTFail("unexpected error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testLeftovers() {
|
||||
struct NeverDoAnything: ByteToMessageDecoder {
|
||||
typealias InboundOut = String
|
||||
|
||||
func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {
|
||||
return .needMoreData
|
||||
}
|
||||
|
||||
func decodeLast(context: ChannelHandlerContext,
|
||||
buffer: inout ByteBuffer,
|
||||
seenEOF: Bool) throws -> DecodingState {
|
||||
while try self.decode(context: context, buffer: &buffer) == .continue {}
|
||||
if buffer.readableBytes > 0 {
|
||||
context.fireChannelRead(self.wrapInboundOut("leftover"))
|
||||
}
|
||||
return .needMoreData
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertThrowsError(try ByteToMessageDecoderVerifier.verifyDecoder(stringInputOutputPairs: [("xxxxxx", [])],
|
||||
decoderFactory: NeverDoAnything.init)) {
|
||||
error in
|
||||
switch error {
|
||||
case let error as VerificationError:
|
||||
switch error.errorCode {
|
||||
case .leftOversOnDeconstructingChannel(inbound: let inbound,
|
||||
outbound: let outbound,
|
||||
pendingOutbound: let pending):
|
||||
XCTAssertEqual(0, outbound.count)
|
||||
XCTAssertEqual(["leftover"], inbound.map { $0.tryAs(type: String.self) })
|
||||
XCTAssertEqual(0, pending.count)
|
||||
default:
|
||||
XCTFail("unexpected error: \(error)")
|
||||
}
|
||||
default:
|
||||
XCTFail("unexpected error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue