mirror of
https://github.com/swift-server/RediStack.git
synced 2026-06-02 07:37:33 +00:00
## Motivation Since Redis 6.0, a new serialization protocol format (v3) is available that gives richer semantic reasoning behind the different types to enable commands to better understand the return types to provide in their programming language. In addition, the `RESPTranslator` type is going to see more direct usage, and the current API doesn't make read well. ## Changes - Add: Internal `RESPVersion` enum that the `RESPTranslator` will start to use - Rename: `RESPTranslator.parseBytes` to `RESPTranslator.read(from:)`
537 lines
19 KiB
Swift
537 lines
19 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the RediStack open source project
|
|
//
|
|
// Copyright (c) 2019-2022 RediStack project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of RediStack project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import struct Foundation.Data
|
|
import NIOCore
|
|
@testable import RediStack
|
|
import XCTest
|
|
|
|
final class RESPTranslatorTests: XCTestCase {
|
|
private let allocator = ByteBufferAllocator()
|
|
private let parser = RESPTranslator()
|
|
}
|
|
|
|
// MARK: WRITING
|
|
|
|
extension RESPTranslatorTests {
|
|
func testWriting_simpleStrings() {
|
|
XCTAssertTrue(writingTestPass(input: .simpleString("Test1".byteBuffer), expected: "+Test1\r\n"))
|
|
XCTAssertTrue(writingTestPass(input: .simpleString("®in§³¾".byteBuffer), expected: "+®in§³¾\r\n"))
|
|
}
|
|
|
|
func testWriting_bulkStrings() {
|
|
let bytes: [UInt8] = [0x01, 0x02, 0x0a, 0x1b, 0xaa]
|
|
var buffer = allocator.buffer(capacity: 5)
|
|
buffer.writeBytes(bytes)
|
|
XCTAssertTrue(writingTestPass(input: .bulkString(buffer), expected: "$5\r\n".bytes + bytes + "\r\n".bytes))
|
|
XCTAssertTrue(writingTestPass(input: .init(bulk: "®in§³¾"), expected: "$10\r\n®in§³¾\r\n"))
|
|
XCTAssertTrue(writingTestPass(input: .init(bulk: ""), expected: "$0\r\n\r\n"))
|
|
XCTAssertTrue(writingTestPass(input: .init(bulk: Optional<Int>.none), expected: "$0\r\n\r\n"))
|
|
}
|
|
|
|
func testWriting_integers() {
|
|
XCTAssertTrue(writingTestPass(input: .integer(Int.min), expected: ":\(Int.min)\r\n"))
|
|
XCTAssertTrue(writingTestPass(input: .integer(0), expected: ":0\r\n"))
|
|
}
|
|
|
|
func testWriting_arrays() {
|
|
XCTAssertTrue(writingTestPass(input: .array([]), expected: "*0\r\n"))
|
|
XCTAssertTrue(writingTestPass(
|
|
input: .array([ .integer(3), .simpleString("foo".byteBuffer) ]),
|
|
expected: "*2\r\n:3\r\n+foo\r\n"
|
|
))
|
|
let bytes: [UInt8] = [ 0x0a, 0x1a, 0x1b, 0xff ]
|
|
var buffer = allocator.buffer(capacity: 4)
|
|
buffer.writeBytes(bytes)
|
|
XCTAssertTrue(writingTestPass(
|
|
input: .array([ .array([ .integer(10), .bulkString(buffer) ]) ]),
|
|
expected: "*1\r\n*2\r\n:10\r\n$4\r\n".bytes + bytes + "\r\n".bytes
|
|
))
|
|
}
|
|
|
|
func testWriting_errors() {
|
|
let error = RedisError(reason: "Manual error")
|
|
XCTAssertTrue(writingTestPass(input: .error(error), expected: "-\(error.message)\r\n"))
|
|
}
|
|
|
|
func testWriting_null() {
|
|
XCTAssertTrue(writingTestPass(input: .null, expected: "$-1\r\n"))
|
|
}
|
|
|
|
func testWriting_foundationData() {
|
|
let name = #function
|
|
let data = Data(name.bytes).convertedToRESPValue()
|
|
XCTAssertTrue(writingTestPass(input: data, expected: "$\(name.count)\r\n\(name)\r\n"))
|
|
}
|
|
|
|
private func writingTestPass(input: RESPValue, expected: [UInt8]) -> Bool {
|
|
var buffer = allocator.buffer(capacity: expected.count)
|
|
buffer.writeRESPValue(input)
|
|
|
|
let result = buffer.getBytes(at: 0, length: buffer.readableBytes)
|
|
|
|
return result == expected
|
|
}
|
|
|
|
private func writingTestPass(input: RESPValue, expected: String) -> Bool {
|
|
var buffer = allocator.buffer(capacity: expected.count)
|
|
buffer.writeRESPValue(input)
|
|
|
|
let result = buffer.getString(at: 0, length: buffer.readableBytes)
|
|
|
|
return result == expected
|
|
}
|
|
}
|
|
|
|
// MARK: READING
|
|
|
|
fileprivate extension ByteBuffer {
|
|
mutating func mimicTokenParse() {
|
|
self.moveReaderIndex(forwardBy: 1)
|
|
}
|
|
}
|
|
|
|
// MARK: Parse
|
|
|
|
extension RESPTranslatorTests {
|
|
func testParsing_invalidToken() {
|
|
var buffer = self.allocator.buffer(capacity: 128)
|
|
buffer.writeString("!!!!")
|
|
XCTAssertThrowsError(try self.parser.read(from: &buffer)) { error in
|
|
XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidToken)
|
|
}
|
|
}
|
|
|
|
func testParsing_invalidSymbols() {
|
|
let testRESP = "&3\r\n"
|
|
var buffer = allocator.buffer(capacity: testRESP.count)
|
|
buffer.writeString(testRESP)
|
|
|
|
XCTAssertThrowsError(try parser.read(from: &buffer))
|
|
XCTAssertEqual(buffer.readerIndex, 0)
|
|
}
|
|
|
|
func testParsing_simpleString() throws {
|
|
let value = try parseTest(inputRESP: "+OK\r\n")
|
|
XCTAssertEqual(value?.string, "OK")
|
|
}
|
|
|
|
func testParsing_simpleString_chunked() throws {
|
|
let values = try parseTest(withChunks: ["+OK\r", "\n+&t®in§³¾\r", "\n"])
|
|
XCTAssertEqual(values.count, 2)
|
|
XCTAssertEqual(values[0].string, "OK")
|
|
XCTAssertEqual(values[1].string, "&t®in§³¾")
|
|
}
|
|
|
|
func testParsing_integer() throws {
|
|
let value = try parseTest(inputRESP: ":300\r\n")
|
|
XCTAssertEqual(value?.int, 300)
|
|
}
|
|
|
|
func testParsing_integer_chunked() throws {
|
|
let values = try parseTest(withChunks: [":300\r", "\n:\(Int.min)\r", "\n:", "\(Int.max)", "\r", "\n"])
|
|
XCTAssertEqual(values.count, 3)
|
|
XCTAssertEqual(values[0].int, 300)
|
|
XCTAssertEqual(values[1].int, Int.min)
|
|
XCTAssertEqual(values[2].int, Int.max)
|
|
}
|
|
|
|
func testParsing_bulkStrings() throws {
|
|
XCTAssertEqual(try parseTest(inputRESP: "$-1\r\n")?.isNull, true)
|
|
XCTAssertEqual(try parseTest(inputRESP: "$0\r\n\r\n")?.string, "")
|
|
XCTAssertEqual(try parseTest(inputRESP: "$1\r\n!\r\n")?.string, "!")
|
|
XCTAssertEqual(
|
|
try parseTest(input: "$1\r\n".bytes + [0xa3] + "\r\n".bytes)?.bytes,
|
|
[0xa3]
|
|
)
|
|
XCTAssertEqual(
|
|
try parseTest(input: "$1\r\n".bytes + [0xba] + "\r\n".bytes)?.bytes,
|
|
[0xba]
|
|
)
|
|
}
|
|
|
|
func testParsing_bulkStrings_chunked() throws {
|
|
let t1 = try parseTest(withChunks: ["$3\r", "\naaa\r\n$", "4\r\nnio!\r\n"])
|
|
XCTAssertEqual(t1.count, 2)
|
|
XCTAssertTrue(t1[0].bytes?.count == 3)
|
|
XCTAssertTrue(t1[1].bytes?.count == 4)
|
|
|
|
let chunks: [[UInt8]] = [
|
|
"$3\r".bytes,
|
|
"\n".bytes + [0xAA, 0xA3, 0xFF] + "\r\n$".bytes,
|
|
"4\r\n".bytes + [0xbb, 0x3a, 0xba, 0xFF] + "\r\n".bytes
|
|
]
|
|
let t2 = try parseTest(withChunks: chunks)
|
|
XCTAssertTrue(t2[0].bytes?.count == 3)
|
|
XCTAssertTrue(t2[1].bytes?.count == 4)
|
|
}
|
|
|
|
func testParsing_arrays() throws {
|
|
XCTAssertEqual(try parseTest(inputRESP: "*1\r\n+!\r\n")?.array?.count, 1)
|
|
XCTAssertEqual(try parseTest(inputRESP: "*2\r\n*1\r\n:1\r\n:3\r\n")?.array?.count, 2)
|
|
XCTAssertEqual(try parseTest(input: "*0\r\n".bytes)?.array?.count, 0)
|
|
XCTAssertNil(try parseTest(input: "*-1\r\n".bytes)?.array)
|
|
}
|
|
|
|
func testParsing_arrays_chunked() throws {
|
|
let t1 = try parseTest(withChunks: ["*2\r", "\n+a\r\n+c\r\n*", "0\r\n"])
|
|
XCTAssertEqual(t1.count, 2)
|
|
XCTAssertTrue(t1[0].array?.count == 2)
|
|
XCTAssertEqual(t1[0].array?[0].string, "a")
|
|
XCTAssertEqual(t1[0].array?[1].string, "c")
|
|
XCTAssertTrue(t1[1].array?.count == 0)
|
|
|
|
let t2 = try parseTest(withChunks: [
|
|
"*-1\r".bytes,
|
|
"\n".bytes,
|
|
"*1\r".bytes,
|
|
"\n+£\r\n".bytes
|
|
])
|
|
XCTAssertEqual(t2.count, 2)
|
|
XCTAssertTrue(t2[0].isNull)
|
|
XCTAssertTrue(t2[1].array?.count == 1)
|
|
XCTAssertEqual(t2[1].array?[0].string, "£")
|
|
}
|
|
|
|
func testParsing_error() throws {
|
|
let expectedContent = "ERR unknown command 'foobar'"
|
|
let testString = "-\(expectedContent)\r\n"
|
|
|
|
var buffer = allocator.buffer(capacity: expectedContent.count)
|
|
buffer.writeString(testString)
|
|
|
|
guard let value = try parser.read(from: &buffer) else { return XCTFail("Failed to parse error") }
|
|
|
|
XCTAssertEqual(value.error?.message.contains(expectedContent), true)
|
|
}
|
|
|
|
private func parseTest(inputRESP: String) throws -> RESPValue? {
|
|
return try parseTest(input: inputRESP.bytes)
|
|
}
|
|
|
|
private func parseTest(input: [UInt8]) throws -> RESPValue? {
|
|
var buffer = allocator.buffer(capacity: input.count)
|
|
buffer.writeBytes(input)
|
|
|
|
let result = try parser.read(from: &buffer)
|
|
assert(buffer.readerIndex == buffer.writerIndex)
|
|
|
|
return result
|
|
}
|
|
|
|
private func parseTest(withChunks messageChunks: [String]) throws -> [RESPValue] {
|
|
return try parseTest(withChunks: messageChunks.map({ $0.bytes }))
|
|
}
|
|
|
|
private func parseTest(withChunks messageChunks: [[UInt8]]) throws -> [RESPValue] {
|
|
var buffer = allocator.buffer(capacity: messageChunks.joined().count)
|
|
|
|
var results = [RESPValue]()
|
|
|
|
for chunk in messageChunks {
|
|
buffer.writeBytes(chunk)
|
|
|
|
guard let result = try parser.read(from: &buffer) else { continue }
|
|
|
|
results.append(result)
|
|
}
|
|
|
|
assert(buffer.readerIndex == buffer.writerIndex)
|
|
|
|
return results
|
|
}
|
|
}
|
|
|
|
// MARK: Simple Strings
|
|
|
|
extension RESPTranslatorTests {
|
|
func testParsing_simpleString_missingNewline() {
|
|
XCTAssertNil(simpleStringParseTest("+OK"))
|
|
XCTAssertNil(simpleStringParseTest("+OK\r"))
|
|
}
|
|
|
|
func testParsing_simpleString_withNoContent() {
|
|
XCTAssertEqual(simpleStringParseTest("+\r\n"), "")
|
|
}
|
|
|
|
func testParsing_simpleString_withContent() {
|
|
XCTAssertEqual(simpleStringParseTest("+ \r\n"), " ")
|
|
XCTAssertEqual(simpleStringParseTest("+OK\r\n"), "OK")
|
|
XCTAssertEqual(simpleStringParseTest("+OK\r\n+OTHER STRING\r\n"), "OK")
|
|
XCTAssertEqual(simpleStringParseTest("+&t®in§³¾\r\n"), "&t®in§³¾")
|
|
}
|
|
|
|
func testParsing_simpleString_handlesRecursion() {
|
|
let testString = "+OK\r\n+OTHER STRING\r\n"
|
|
|
|
var buffer = allocator.buffer(capacity: testString.count)
|
|
buffer.writeString(testString)
|
|
|
|
buffer.mimicTokenParse()
|
|
var first = parser.parseSimpleString(from: &buffer)!
|
|
XCTAssertEqual(buffer.readerIndex, 5) // position of the 2nd '+'
|
|
XCTAssertEqual(first.readBytes(length: first.readableBytes), "OK".bytes)
|
|
|
|
buffer.mimicTokenParse()
|
|
var second = parser.parseSimpleString(from: &buffer)!
|
|
XCTAssertEqual(buffer.readerIndex, 20)
|
|
XCTAssertEqual(second.readBytes(length: second.readableBytes), "OTHER STRING".bytes)
|
|
}
|
|
|
|
private func simpleStringParseTest(_ inputRESP: String) -> String? {
|
|
var buffer = allocator.buffer(capacity: inputRESP.count)
|
|
buffer.writeString(inputRESP)
|
|
|
|
buffer.mimicTokenParse()
|
|
|
|
guard let stringBuffer = parser.parseSimpleString(from: &buffer) else { return nil }
|
|
|
|
return RESPValue.simpleString(stringBuffer).string
|
|
}
|
|
|
|
func testSimpleStringWithoutRemovingToken() {
|
|
var buffer = allocator.buffer(capacity: 2)
|
|
buffer.writeString("\r\n")
|
|
|
|
guard let stringBuffer = parser.parseSimpleString(from: &buffer) else {
|
|
XCTFail("Did not return")
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(RESPValue.simpleString(stringBuffer).string, "")
|
|
}
|
|
}
|
|
|
|
// MARK: Integers
|
|
|
|
extension RESPTranslatorTests {
|
|
func testParsing_integer_withAllBytes() {
|
|
XCTAssertEqual(try integerParseTest(":100\r\n"), 100)
|
|
XCTAssertEqual(try integerParseTest(":-100\r\n"), -100)
|
|
XCTAssertEqual(try integerParseTest(":\(Int.min)\r\n"), Int.min)
|
|
XCTAssertEqual(try integerParseTest(":\(Int.max)\r\n"), Int.max)
|
|
}
|
|
|
|
func testParsing_integer_missingBytes() {
|
|
XCTAssertNil(try integerParseTest(":\r"))
|
|
XCTAssertNil(try integerParseTest(":"))
|
|
XCTAssertNil(try integerParseTest(":\n"))
|
|
XCTAssertThrowsError(try integerParseTest(":\r\n")) { error in
|
|
XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidIntegerFormat)
|
|
}
|
|
}
|
|
|
|
func testParsing_integer_recursively() throws {
|
|
let testString = ":1\r\n:300\r\n"
|
|
|
|
var buffer = allocator.buffer(capacity: testString.count)
|
|
buffer.writeString(testString)
|
|
|
|
buffer.mimicTokenParse()
|
|
let first = try parser.parseInteger(from: &buffer)
|
|
XCTAssertEqual(buffer.readerIndex, 4) // position of the 2nd ':'
|
|
XCTAssertEqual(first, 1)
|
|
|
|
buffer.mimicTokenParse()
|
|
let second = try parser.parseInteger(from: &buffer)
|
|
XCTAssertEqual(buffer.readerIndex, 10)
|
|
XCTAssertEqual(second, 300)
|
|
}
|
|
|
|
func testParsing_integer_nonBase10() throws {
|
|
XCTAssertThrowsError(try integerParseTest(":0xa\r\n")) { error in
|
|
XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidIntegerFormat)
|
|
}
|
|
}
|
|
|
|
private func integerParseTest(_ inputRESP: String) throws -> Int? {
|
|
var buffer = allocator.buffer(capacity: inputRESP.count)
|
|
buffer.writeString(inputRESP)
|
|
buffer.mimicTokenParse()
|
|
return try parser.parseInteger(from: &buffer)
|
|
}
|
|
}
|
|
|
|
// MARK: Bulk Strings
|
|
|
|
extension RESPTranslatorTests {
|
|
func testParsing_bulkString_sizeMismatch() {
|
|
var buffer = self.allocator.buffer(capacity: 128)
|
|
buffer.writeString("$2\r\ntoo long\r\n")
|
|
buffer.mimicTokenParse()
|
|
XCTAssertThrowsError(try self.parser.parseBulkString(from: &buffer)) { error in
|
|
XCTAssertEqual(error as? RESPTranslator.ParsingError, .bulkStringSizeMismatch)
|
|
}
|
|
}
|
|
|
|
func testParsing_bulkString_invalidNegativeSize() {
|
|
var buffer = self.allocator.buffer(capacity: 128)
|
|
buffer.writeString("$-4\r\nwhat\r\n")
|
|
buffer.mimicTokenParse()
|
|
XCTAssertThrowsError(try self.parser.parseBulkString(from: &buffer)) { error in
|
|
XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidBulkStringSize)
|
|
}
|
|
}
|
|
|
|
func testParsing_bulkString_SizeIsNaN() {
|
|
var buffer = self.allocator.buffer(capacity: 128)
|
|
buffer.writeString("$FOO\r\nwhat\r\n")
|
|
buffer.mimicTokenParse()
|
|
XCTAssertThrowsError(try self.parser.parseBulkString(from: &buffer)) { error in
|
|
XCTAssertEqual(error as? RESPTranslator.ParsingError, .invalidIntegerFormat)
|
|
}
|
|
}
|
|
|
|
func testParsing_bulkString_missingEndings() {
|
|
for message in ["$6", "$6\r\n", "$6\r\nabcdef", "$0\r\n"] {
|
|
XCTAssertNil(bulkStringParseTest(inputRESP: message))
|
|
}
|
|
}
|
|
|
|
func testParsing_bulkString_withNoSize() {
|
|
let result = bulkStringParseTest(inputRESP: "$0\r\n\r\n")
|
|
XCTAssertEqual(result?.bytes?.count, 0)
|
|
}
|
|
|
|
func testParsing_bulkString_withContent() {
|
|
let result = bulkStringParseTest(inputRESP: "$1\r\n:\r\n")?.bytes
|
|
XCTAssertEqual(result?.count, 1)
|
|
XCTAssertEqual(result?[0], .colon)
|
|
}
|
|
|
|
func testParsing_bulkString_null() {
|
|
let result = bulkStringParseTest(inputRESP: "$-1\r\n")
|
|
XCTAssertEqual(result?.isNull, true)
|
|
}
|
|
|
|
func testParsing_bulkString_rawBytes() {
|
|
let bytes: [UInt8] = [0x00, 0x01, 0x02, 0x03, 0x0A, 0xFF]
|
|
let allBytes = "$\(bytes.count)\r\n".bytes + bytes + "\r\n".bytes
|
|
|
|
let result = bulkStringParseTest(input: allBytes)
|
|
XCTAssertEqual(result?.bytes?.count, bytes.count)
|
|
}
|
|
|
|
func testParsing_bulkString_recursively() {
|
|
let testString = "$0\r\n\r\n$5\r\nredis\r\n"
|
|
|
|
var buffer = allocator.buffer(capacity: testString.count)
|
|
buffer.writeString(testString)
|
|
|
|
buffer.mimicTokenParse()
|
|
let first = try? parser.parseBulkString(from: &buffer)
|
|
XCTAssertEqual(buffer.readerIndex, 6) // position of the 2nd '$'
|
|
|
|
buffer.mimicTokenParse()
|
|
let second = try? parser.parseBulkString(from: &buffer)
|
|
XCTAssertEqual(buffer.readerIndex, 17)
|
|
|
|
XCTAssertEqual(first?.string, "")
|
|
XCTAssertEqual(second?.string, "redis")
|
|
}
|
|
|
|
private func bulkStringParseTest(inputRESP: String) -> RESPValue? {
|
|
return bulkStringParseTest(input: inputRESP.bytes)
|
|
}
|
|
|
|
private func bulkStringParseTest(input: [UInt8]) -> RESPValue? {
|
|
var buffer = allocator.buffer(capacity: input.count)
|
|
buffer.writeBytes(input)
|
|
|
|
buffer.mimicTokenParse()
|
|
|
|
guard let result = try? parser.parseBulkString(from: &buffer) else { return nil }
|
|
return result
|
|
}
|
|
}
|
|
|
|
// MARK: Arrays
|
|
|
|
extension RESPTranslatorTests {
|
|
func testParsing_array_whenNull() throws {
|
|
let result = try arrayParseTest(inputRESP: "*-1\r\n")
|
|
XCTAssertEqual(result?.isNull, true)
|
|
}
|
|
|
|
func testParsing_array_whenEmpty() throws {
|
|
let result = try arrayParseTest(inputRESP: "*0\r\n")
|
|
XCTAssertEqual(result?.array?.count, 0)
|
|
}
|
|
|
|
func testParsing_array_withMixedTypes() throws {
|
|
let result = try arrayParseTest(inputRESP: "*3\r\n:3\r\n+OK\r\n$1\r\na\r\n")?.array
|
|
XCTAssertEqual(result?.count, 3)
|
|
XCTAssertEqual(result?[0].int, 3)
|
|
XCTAssertEqual(result?[1].string, "OK")
|
|
XCTAssertEqual(result?[2].bytes?.count, 1)
|
|
}
|
|
|
|
func testParsing_array_withNullElements() throws {
|
|
let result = try arrayParseTest(inputRESP: "*3\r\n:3\r\n$-1\r\n:30\r\n")?.array
|
|
XCTAssertEqual(result?.count, 3)
|
|
XCTAssertEqual(result?[0].int, 3)
|
|
XCTAssertEqual(result?[1].isNull, true)
|
|
XCTAssertEqual(result?[2].int, 30)
|
|
}
|
|
|
|
func testParsing_array_nested() throws {
|
|
let result = try arrayParseTest(inputRESP: "*2\r\n:3\r\n*2\r\n:30\r\n:15\r\n")?.array
|
|
XCTAssertEqual(result?.count, 2)
|
|
XCTAssertEqual(result?[0].int, 3)
|
|
let nested = result?[1].array
|
|
XCTAssertEqual(nested?.count, 2)
|
|
XCTAssertEqual(nested?[0].int, 30)
|
|
XCTAssertEqual(nested?[1].int, 15)
|
|
}
|
|
|
|
func testParsing_array_recursively() throws {
|
|
let testString = "*1\r\n:3\r\n*2\r\n+OK\r\n$3\r\nabc\r\n"
|
|
|
|
var buffer = allocator.buffer(capacity: testString.count)
|
|
buffer.writeString(testString)
|
|
|
|
buffer.mimicTokenParse()
|
|
let first = try parser.parseArray(from: &buffer)
|
|
XCTAssertEqual(buffer.readerIndex, 8) // position of the 2nd '$'
|
|
|
|
buffer.mimicTokenParse()
|
|
let second = try parser.parseArray(from: &buffer)
|
|
XCTAssertEqual(buffer.readerIndex, 26)
|
|
|
|
guard
|
|
let array1 = first?.array,
|
|
let array2 = second?.array
|
|
else { return XCTFail("Failed to parse both values") }
|
|
|
|
XCTAssertEqual(array1.count, 1)
|
|
XCTAssertEqual(array1[0].int, 3)
|
|
XCTAssertEqual(array2.count, 2)
|
|
XCTAssertEqual(array2[0].string, "OK")
|
|
XCTAssertEqual(array2[1].string, "abc")
|
|
}
|
|
|
|
private func arrayParseTest(inputRESP: String) throws -> RESPValue? {
|
|
return try arrayParseTest(input: inputRESP.bytes)
|
|
}
|
|
|
|
private func arrayParseTest(input: [UInt8]) throws -> RESPValue? {
|
|
var buffer = allocator.buffer(capacity: input.count)
|
|
buffer.writeBytes(input)
|
|
buffer.mimicTokenParse()
|
|
|
|
guard let result = try parser.parseArray(from: &buffer) else { return nil }
|
|
return result
|
|
}
|
|
}
|