Make ByteBufferView mutable (#1208)
Motivation: ByteBufferView isn't a mutable collection, but it probably should be. Modifications: - Add `copyBytes(at:to:length)` to `ByteBuffer` to copy bytes from a readable region of a buffer to another part of the buffer - Conform `ByteBufferView` to `MutableCollection`, `RangeReplaceableCollection` and `MutableDataProtocol` - Add an allocation counting test Result: `ByteBufferView` is now mutable.
This commit is contained in:
parent
7d8e6b2323
commit
302dee3e1a
|
@ -0,0 +1,40 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// This source file is part of the SwiftNIO open source project
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017-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
|
||||||
|
|
||||||
|
func run(identifier: String) {
|
||||||
|
let allocator = ByteBufferAllocator()
|
||||||
|
let data = Array(repeating: UInt8(0), count: 1024)
|
||||||
|
|
||||||
|
measure(identifier: identifier) {
|
||||||
|
var count = 0
|
||||||
|
|
||||||
|
for _ in 0..<1_000 {
|
||||||
|
var buffer = allocator.buffer(capacity: data.count)
|
||||||
|
buffer.writeBytes(data)
|
||||||
|
|
||||||
|
var view = ByteBufferView(buffer)
|
||||||
|
|
||||||
|
// Unfortunately this CoWs: https://bugs.swift.org/browse/SR-11675
|
||||||
|
view[0] = 42
|
||||||
|
view.replaceSubrange(0..<4, with: [0x0, 0x1, 0x2, 0x3])
|
||||||
|
|
||||||
|
var modified = ByteBuffer(view)
|
||||||
|
modified.setBytes([0xa, 0xb, 0xc], at: modified.readerIndex)
|
||||||
|
count &+= modified.readableBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
}
|
|
@ -284,7 +284,7 @@ public struct ByteBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private mutating func _copyStorageAndRebase(capacity: _Capacity, resetIndices: Bool = false) {
|
@usableFromInline mutating func _copyStorageAndRebase(capacity: _Capacity, resetIndices: Bool = false) {
|
||||||
let indexRebaseAmount = resetIndices ? self._readerIndex : 0
|
let indexRebaseAmount = resetIndices ? self._readerIndex : 0
|
||||||
let storageRebaseAmount = self._slice.lowerBound + indexRebaseAmount
|
let storageRebaseAmount = self._slice.lowerBound + indexRebaseAmount
|
||||||
let newSlice = storageRebaseAmount ..< min(storageRebaseAmount + _toCapacity(self._slice.count), self._slice.upperBound, storageRebaseAmount + capacity)
|
let newSlice = storageRebaseAmount ..< min(storageRebaseAmount + _toCapacity(self._slice.count), self._slice.upperBound, storageRebaseAmount + capacity)
|
||||||
|
@ -359,11 +359,15 @@ public struct ByteBuffer {
|
||||||
let extraCapacity = newEndIndex > self._slice.upperBound ? newEndIndex - self._slice.upperBound : 0
|
let extraCapacity = newEndIndex > self._slice.upperBound ? newEndIndex - self._slice.upperBound : 0
|
||||||
self._copyStorageAndRebase(extraCapacity: extraCapacity)
|
self._copyStorageAndRebase(extraCapacity: extraCapacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
self._ensureAvailableCapacity(_Capacity(bytesCount), at: index)
|
self._ensureAvailableCapacity(_Capacity(bytesCount), at: index)
|
||||||
|
self._setBytesAssumingUniqueBufferAccess(bytes, at: index)
|
||||||
|
return _toCapacity(bytesCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@inlinable
|
||||||
|
mutating func _setBytesAssumingUniqueBufferAccess(_ bytes: UnsafeRawBufferPointer, at index: _Index) {
|
||||||
let targetPtr = UnsafeMutableRawBufferPointer(rebasing: self._slicedStorageBuffer.dropFirst(Int(index)))
|
let targetPtr = UnsafeMutableRawBufferPointer(rebasing: self._slicedStorageBuffer.dropFirst(Int(index)))
|
||||||
targetPtr.copyMemory(from: bytes)
|
targetPtr.copyMemory(from: bytes)
|
||||||
return _toCapacity(Int(bytesCount))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@inline(never)
|
@inline(never)
|
||||||
|
@ -733,14 +737,14 @@ extension ByteBuffer: CustomStringConvertible {
|
||||||
|
|
||||||
/* change types to the user visible `Int` */
|
/* change types to the user visible `Int` */
|
||||||
extension ByteBuffer {
|
extension ByteBuffer {
|
||||||
/// Copy the collection of `bytes` into the `ByteBuffer` at `index`.
|
/// Copy the collection of `bytes` into the `ByteBuffer` at `index`. Does not move the writer index.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
@inlinable
|
@inlinable
|
||||||
public mutating func setBytes<Bytes: Sequence>(_ bytes: Bytes, at index: Int) -> Int where Bytes.Element == UInt8 {
|
public mutating func setBytes<Bytes: Sequence>(_ bytes: Bytes, at index: Int) -> Int where Bytes.Element == UInt8 {
|
||||||
return Int(self._setBytes(bytes, at: _toIndex(index)))
|
return Int(self._setBytes(bytes, at: _toIndex(index)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy `bytes` into the `ByteBuffer` at `index`.
|
/// Copy `bytes` into the `ByteBuffer` at `index`. Does not move the writer index.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
@inlinable
|
@inlinable
|
||||||
public mutating func setBytes(_ bytes: UnsafeRawBufferPointer, at index: Int) -> Int {
|
public mutating func setBytes(_ bytes: UnsafeRawBufferPointer, at index: Int) -> Int {
|
||||||
|
@ -800,6 +804,69 @@ extension ByteBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ByteBuffer {
|
||||||
|
/// Copies `length` `bytes` starting at the `fromIndex` to `toIndex`. Does not move the writer index.
|
||||||
|
///
|
||||||
|
/// - Note: Overlapping ranges, for example `copyBytes(at: 1, to: 2, length: 5)` are allowed.
|
||||||
|
/// - Precondition: The range represented by `fromIndex` and `length` must be readable bytes,
|
||||||
|
/// that is: `fromIndex >= readerIndex` and `fromIndex + length <= writerIndex`.
|
||||||
|
/// - Parameter fromIndex: The index of the first byte to copy.
|
||||||
|
/// - Parameter toIndex: The index into to which the first byte will be copied.
|
||||||
|
/// - Parameter length: The number of bytes which should be copied.
|
||||||
|
@discardableResult
|
||||||
|
@inlinable
|
||||||
|
public mutating func copyBytes(at fromIndex: Int, to toIndex: Int, length: Int) throws -> Int {
|
||||||
|
switch length {
|
||||||
|
case ..<0:
|
||||||
|
throw CopyBytesError.negativeLength
|
||||||
|
case 0:
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
()
|
||||||
|
}
|
||||||
|
guard self.readerIndex <= fromIndex && fromIndex + length <= self.writerIndex else {
|
||||||
|
throw CopyBytesError.unreadableSourceBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isKnownUniquelyReferenced(&self._storage) {
|
||||||
|
let newEndIndex = max(self._writerIndex, _toIndex(toIndex + length))
|
||||||
|
self._copyStorageAndRebase(capacity: newEndIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
self._ensureAvailableCapacity(_Capacity(length), at: _toIndex(toIndex))
|
||||||
|
self.withVeryUnsafeBytes { ptr in
|
||||||
|
let srcPtr = UnsafeRawBufferPointer(start: ptr.baseAddress!.advanced(by: fromIndex), count: length)
|
||||||
|
self._setBytesAssumingUniqueBufferAccess(srcPtr, at: _toIndex(toIndex))
|
||||||
|
}
|
||||||
|
|
||||||
|
return length
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors thrown when calling `copyBytes`.
|
||||||
|
public struct CopyBytesError: Error {
|
||||||
|
private enum BaseError: Hashable {
|
||||||
|
case negativeLength
|
||||||
|
case unreadableSourceBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
private var baseError: BaseError
|
||||||
|
|
||||||
|
/// The length of the bytes to copy was negative.
|
||||||
|
public static let negativeLength: CopyBytesError = .init(baseError: .negativeLength)
|
||||||
|
|
||||||
|
/// The bytes to copy are not readable.
|
||||||
|
public static let unreadableSourceBytes: CopyBytesError = .init(baseError: .unreadableSourceBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ByteBuffer.CopyBytesError: Hashable { }
|
||||||
|
|
||||||
|
extension ByteBuffer.CopyBytesError: CustomDebugStringConvertible {
|
||||||
|
public var debugDescription: String {
|
||||||
|
return String(describing: self.baseError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension ByteBuffer: Equatable {
|
extension ByteBuffer: Equatable {
|
||||||
// TODO: I don't think this makes sense. This should compare bytes 0..<writerIndex instead.
|
// TODO: I don't think this makes sense. This should compare bytes 0..<writerIndex instead.
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
/// A read-only view into a portion of a `ByteBuffer`.
|
/// A view into a portion of a `ByteBuffer`.
|
||||||
///
|
///
|
||||||
/// A `ByteBufferView` is useful whenever a `Collection where Element == UInt8` representing a portion of a
|
/// A `ByteBufferView` is useful whenever a `Collection where Element == UInt8` representing a portion of a
|
||||||
/// `ByteBuffer` is needed.
|
/// `ByteBuffer` is needed.
|
||||||
|
@ -21,28 +21,33 @@ public struct ByteBufferView: RandomAccessCollection {
|
||||||
public typealias Index = Int
|
public typealias Index = Int
|
||||||
public typealias SubSequence = ByteBufferView
|
public typealias SubSequence = ByteBufferView
|
||||||
|
|
||||||
fileprivate let buffer: ByteBuffer
|
/* private but usableFromInline */ @usableFromInline var _buffer: ByteBuffer
|
||||||
fileprivate let range: Range<Index>
|
/* private but usableFromInline */ @usableFromInline var _range: Range<Index>
|
||||||
|
|
||||||
internal init(buffer: ByteBuffer, range: Range<Index>) {
|
internal init(buffer: ByteBuffer, range: Range<Index>) {
|
||||||
precondition(range.lowerBound >= 0 && range.upperBound <= buffer.capacity)
|
precondition(range.lowerBound >= 0 && range.upperBound <= buffer.capacity)
|
||||||
self.buffer = buffer
|
self._buffer = buffer
|
||||||
self.range = range
|
self._range = range
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a `ByteBufferView` from the readable bytes of the given `buffer`.
|
||||||
|
public init(_ buffer: ByteBuffer) {
|
||||||
|
self = ByteBufferView(buffer: buffer, range: buffer.readerIndex ..< buffer.writerIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
|
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
|
||||||
return try self.buffer.withVeryUnsafeBytes { ptr in
|
return try self._buffer.withVeryUnsafeBytes { ptr in
|
||||||
try body(UnsafeRawBufferPointer.init(start: ptr.baseAddress!.advanced(by: self.range.lowerBound),
|
try body(UnsafeRawBufferPointer(start: ptr.baseAddress!.advanced(by: self._range.lowerBound),
|
||||||
count: self.range.count))
|
count: self._range.count))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var startIndex: Index {
|
public var startIndex: Index {
|
||||||
return self.range.lowerBound
|
return self._range.lowerBound
|
||||||
}
|
}
|
||||||
|
|
||||||
public var endIndex: Index {
|
public var endIndex: Index {
|
||||||
return self.range.upperBound
|
return self._range.upperBound
|
||||||
}
|
}
|
||||||
|
|
||||||
public func index(after i: Index) -> Index {
|
public func index(after i: Index) -> Index {
|
||||||
|
@ -50,14 +55,27 @@ public struct ByteBufferView: RandomAccessCollection {
|
||||||
}
|
}
|
||||||
|
|
||||||
public subscript(position: Index) -> UInt8 {
|
public subscript(position: Index) -> UInt8 {
|
||||||
guard position >= self.range.lowerBound && position < self.range.upperBound else {
|
get {
|
||||||
preconditionFailure("index \(position) out of range")
|
guard position >= self._range.lowerBound && position < self._range.upperBound else {
|
||||||
|
preconditionFailure("index \(position) out of range")
|
||||||
|
}
|
||||||
|
return self._buffer.getInteger(at: position)! // range check above
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
guard position >= self._range.lowerBound && position < self._range.upperBound else {
|
||||||
|
preconditionFailure("index \(position) out of range")
|
||||||
|
}
|
||||||
|
self._buffer.setInteger(newValue, at: position)
|
||||||
}
|
}
|
||||||
return self.buffer.getInteger(at: position)! // range check above
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public subscript(range: Range<Index>) -> ByteBufferView {
|
public subscript(range: Range<Index>) -> ByteBufferView {
|
||||||
return ByteBufferView(buffer: self.buffer, range: range)
|
get {
|
||||||
|
return ByteBufferView(buffer: self._buffer, range: range)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self.replaceSubrange(range, with: newValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@inlinable
|
@inlinable
|
||||||
|
@ -68,10 +86,58 @@ public struct ByteBufferView: RandomAccessCollection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ByteBufferView: MutableCollection {}
|
||||||
|
|
||||||
|
extension ByteBufferView: RangeReplaceableCollection {
|
||||||
|
// required by `RangeReplaceableCollection`
|
||||||
|
public init() {
|
||||||
|
self = ByteBufferView(ByteBufferAllocator().buffer(capacity: 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
@inlinable
|
||||||
|
public mutating func replaceSubrange<C: Collection>(_ subrange: Range<Index>, with newElements: C) where ByteBufferView.Element == C.Element {
|
||||||
|
precondition(subrange.startIndex >= self.startIndex && subrange.endIndex <= self.endIndex,
|
||||||
|
"subrange out of bounds")
|
||||||
|
|
||||||
|
if newElements.count == subrange.count {
|
||||||
|
self._buffer.setBytes(newElements, at: subrange.startIndex)
|
||||||
|
} else if newElements.count < subrange.count {
|
||||||
|
// Replace the subrange.
|
||||||
|
self._buffer.setBytes(newElements, at: subrange.startIndex)
|
||||||
|
|
||||||
|
// Remove the unwanted bytes between the newly copied bytes and the end of the subrange.
|
||||||
|
// try! is fine here: the copied range is within the view and the length can't be negative.
|
||||||
|
try! self._buffer.copyBytes(at: subrange.endIndex,
|
||||||
|
to: subrange.startIndex.advanced(by: newElements.count),
|
||||||
|
length: subrange.endIndex.distance(to: self._buffer.writerIndex))
|
||||||
|
|
||||||
|
// Shorten the range.
|
||||||
|
let removedBytes = subrange.count - newElements.count
|
||||||
|
self._buffer.moveWriterIndex(to: self._buffer.writerIndex - removedBytes)
|
||||||
|
self._range = self._range.dropLast(removedBytes)
|
||||||
|
} else {
|
||||||
|
// Make space for the new elements.
|
||||||
|
// try! is fine here: the copied range is within the view and the length can't be negative.
|
||||||
|
try! self._buffer.copyBytes(at: subrange.endIndex,
|
||||||
|
to: subrange.startIndex.advanced(by: newElements.count),
|
||||||
|
length: subrange.endIndex.distance(to: self._buffer.writerIndex))
|
||||||
|
|
||||||
|
// Replace the bytes.
|
||||||
|
self._buffer.setBytes(newElements, at: subrange.startIndex)
|
||||||
|
|
||||||
|
// Widen the range.
|
||||||
|
let additionalByteCount = newElements.count - subrange.count
|
||||||
|
self._buffer.moveWriterIndex(forwardBy: additionalByteCount)
|
||||||
|
self._range = self._range.startIndex ..< self._range.endIndex.advanced(by: additionalByteCount)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension ByteBuffer {
|
extension ByteBuffer {
|
||||||
/// A view into the readable bytes of the `ByteBuffer`.
|
/// A view into the readable bytes of the `ByteBuffer`.
|
||||||
public var readableBytesView: ByteBufferView {
|
public var readableBytesView: ByteBufferView {
|
||||||
return ByteBufferView(buffer: self, range: self.readerIndex ..< self.readerIndex + self.readableBytes)
|
return ByteBufferView(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a view into some portion of the readable bytes of a `ByteBuffer`.
|
/// Returns a view into some portion of the readable bytes of a `ByteBuffer`.
|
||||||
|
@ -92,6 +158,6 @@ extension ByteBuffer {
|
||||||
///
|
///
|
||||||
/// - parameter view: The `ByteBufferView` which you want to get a `ByteBuffer` from.
|
/// - parameter view: The `ByteBufferView` which you want to get a `ByteBuffer` from.
|
||||||
public init(_ view: ByteBufferView) {
|
public init(_ view: ByteBufferView) {
|
||||||
self = view.buffer.getSlice(at: view.range.startIndex, length: view.range.count)!
|
self = view._buffer.getSlice(at: view.startIndex, length: view.count)!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,3 +210,5 @@ extension ByteBufferView: DataProtocol {
|
||||||
return .init(self)
|
return .init(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ByteBufferView: MutableDataProtocol {}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// 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 Foundation
|
||||||
|
import XCTest
|
||||||
|
import NIO
|
||||||
|
import NIOFoundationCompat
|
||||||
|
|
||||||
|
class ByteBufferViewDataProtocolTests: XCTestCase {
|
||||||
|
|
||||||
|
func testResetBytes() {
|
||||||
|
var view = ByteBufferView()
|
||||||
|
view.resetBytes(in: view.indices)
|
||||||
|
XCTAssertTrue(view.elementsEqual([]))
|
||||||
|
|
||||||
|
view.replaceSubrange(view.indices, with: [1, 2, 3, 4, 5])
|
||||||
|
|
||||||
|
view.resetBytes(in: 0..<2)
|
||||||
|
XCTAssertTrue(view.elementsEqual([0, 0, 3, 4, 5]))
|
||||||
|
|
||||||
|
view.resetBytes(in: 2...4)
|
||||||
|
XCTAssertTrue(view.elementsEqual([0, 0, 0, 0, 0]))
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,6 +85,13 @@ extension ByteBufferTest {
|
||||||
("testWriteBuffer", testWriteBuffer),
|
("testWriteBuffer", testWriteBuffer),
|
||||||
("testMisalignedIntegerRead", testMisalignedIntegerRead),
|
("testMisalignedIntegerRead", testMisalignedIntegerRead),
|
||||||
("testSetAndWriteBytes", testSetAndWriteBytes),
|
("testSetAndWriteBytes", testSetAndWriteBytes),
|
||||||
|
("testCopyBytesWithNegativeLength", testCopyBytesWithNegativeLength),
|
||||||
|
("testCopyBytesNonReadable", testCopyBytesNonReadable),
|
||||||
|
("testCopyBytes", testCopyBytes),
|
||||||
|
("testCopyZeroBytesOutOfBoundsIsOk", testCopyZeroBytesOutOfBoundsIsOk),
|
||||||
|
("testCopyBytesBeyondWriterIndex", testCopyBytesBeyondWriterIndex),
|
||||||
|
("testCopyBytesOverSelf", testCopyBytesOverSelf),
|
||||||
|
("testCopyBytesCoWs", testCopyBytesCoWs),
|
||||||
("testWriteABunchOfCollections", testWriteABunchOfCollections),
|
("testWriteABunchOfCollections", testWriteABunchOfCollections),
|
||||||
("testSetABunchOfCollections", testSetABunchOfCollections),
|
("testSetABunchOfCollections", testSetABunchOfCollections),
|
||||||
("testTryStringTooLong", testTryStringTooLong),
|
("testTryStringTooLong", testTryStringTooLong),
|
||||||
|
@ -131,6 +138,15 @@ extension ByteBufferTest {
|
||||||
("testViewsStartIndexIsStable", testViewsStartIndexIsStable),
|
("testViewsStartIndexIsStable", testViewsStartIndexIsStable),
|
||||||
("testSlicesOfByteBufferViewsAreByteBufferViews", testSlicesOfByteBufferViewsAreByteBufferViews),
|
("testSlicesOfByteBufferViewsAreByteBufferViews", testSlicesOfByteBufferViewsAreByteBufferViews),
|
||||||
("testReadableBufferViewRangeEqualCapacity", testReadableBufferViewRangeEqualCapacity),
|
("testReadableBufferViewRangeEqualCapacity", testReadableBufferViewRangeEqualCapacity),
|
||||||
|
("testBufferViewCoWs", testBufferViewCoWs),
|
||||||
|
("testBufferViewMutationViaSubscriptIndex", testBufferViewMutationViaSubscriptIndex),
|
||||||
|
("testBufferViewReplaceBeyondEndOfRange", testBufferViewReplaceBeyondEndOfRange),
|
||||||
|
("testBufferViewReplaceWithSubrangeOfSelf", testBufferViewReplaceWithSubrangeOfSelf),
|
||||||
|
("testBufferViewMutationViaSubscriptRange", testBufferViewMutationViaSubscriptRange),
|
||||||
|
("testBufferViewReplaceSubrangeWithEqualLengthBytes", testBufferViewReplaceSubrangeWithEqualLengthBytes),
|
||||||
|
("testBufferViewReplaceSubrangeWithFewerBytes", testBufferViewReplaceSubrangeWithFewerBytes),
|
||||||
|
("testBufferViewReplaceSubrangeWithMoreBytes", testBufferViewReplaceSubrangeWithMoreBytes),
|
||||||
|
("testBufferViewEmpty", testBufferViewEmpty),
|
||||||
("testByteBuffersCanBeInitializedFromByteBufferViews", testByteBuffersCanBeInitializedFromByteBufferViews),
|
("testByteBuffersCanBeInitializedFromByteBufferViews", testByteBuffersCanBeInitializedFromByteBufferViews),
|
||||||
("testReserveCapacityWhenOversize", testReserveCapacityWhenOversize),
|
("testReserveCapacityWhenOversize", testReserveCapacityWhenOversize),
|
||||||
("testReserveCapacitySameCapacity", testReserveCapacitySameCapacity),
|
("testReserveCapacitySameCapacity", testReserveCapacitySameCapacity),
|
||||||
|
|
|
@ -836,6 +836,86 @@ class ByteBufferTest: XCTestCase {
|
||||||
XCTAssertEqual(Array(repeating: str, count: 3).joined(), actualString)
|
XCTAssertEqual(Array(repeating: str, count: 3).joined(), actualString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCopyBytesWithNegativeLength() {
|
||||||
|
self.buf.writeBytes([0x0, 0x1])
|
||||||
|
XCTAssertThrowsError(try self.buf.copyBytes(at: self.buf.readerIndex, to: self.buf.readerIndex + 1, length: -1)) {
|
||||||
|
XCTAssertEqual($0 as? ByteBuffer.CopyBytesError, .negativeLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCopyBytesNonReadable() {
|
||||||
|
let oldReaderIndex = self.buf.readerIndex
|
||||||
|
self.buf.writeBytes([0x0, 0x1, 0x2])
|
||||||
|
// Partially consume the bytes.
|
||||||
|
self.buf.moveReaderIndex(forwardBy: 2)
|
||||||
|
|
||||||
|
// Copy two read bytes.
|
||||||
|
XCTAssertThrowsError(try self.buf.copyBytes(at: oldReaderIndex, to: self.buf.writerIndex, length: 2)) {
|
||||||
|
XCTAssertEqual($0 as? ByteBuffer.CopyBytesError, .unreadableSourceBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy one read byte and one readable byte.
|
||||||
|
XCTAssertThrowsError(try self.buf.copyBytes(at: oldReaderIndex + 1, to: self.buf.writerIndex, length: 2)) {
|
||||||
|
XCTAssertEqual($0 as? ByteBuffer.CopyBytesError, .unreadableSourceBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy one readable byte and one uninitialized byte.
|
||||||
|
XCTAssertThrowsError(try self.buf.copyBytes(at: oldReaderIndex + 3, to: self.buf.writerIndex, length: 2)) {
|
||||||
|
XCTAssertEqual($0 as? ByteBuffer.CopyBytesError, .unreadableSourceBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy two uninitialized bytes.
|
||||||
|
XCTAssertThrowsError(try self.buf.copyBytes(at: self.buf.writerIndex, to: oldReaderIndex, length: 2)) {
|
||||||
|
XCTAssertEqual($0 as? ByteBuffer.CopyBytesError, .unreadableSourceBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCopyBytes() throws {
|
||||||
|
self.buf.writeBytes([0, 1, 2, 3])
|
||||||
|
XCTAssertNoThrow(try self.buf.copyBytes(at: self.buf.readerIndex, to: self.buf.readerIndex + 2, length: 2))
|
||||||
|
XCTAssertEqual(self.buf.readableBytes, 4)
|
||||||
|
XCTAssertEqual(self.buf.readBytes(length: self.buf.readableBytes), [0, 1, 0, 1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCopyZeroBytesOutOfBoundsIsOk() throws {
|
||||||
|
XCTAssertEqual(try self.buf.copyBytes(at: self.buf.writerIndex, to: self.buf.writerIndex + 42, length: 0), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCopyBytesBeyondWriterIndex() throws {
|
||||||
|
self.buf.writeBytes([0, 1, 2, 3])
|
||||||
|
// Write beyond the writerIndex
|
||||||
|
XCTAssertNoThrow(try self.buf.copyBytes(at: self.buf.readerIndex, to: self.buf.readerIndex + 4, length: 2))
|
||||||
|
XCTAssertEqual(self.buf.readableBytes, 4)
|
||||||
|
XCTAssertEqual(self.buf.readBytes(length: 2), [0, 1])
|
||||||
|
XCTAssertNotNil(self.buf.readBytes(length: 2)) // could be anything!
|
||||||
|
self.buf.moveWriterIndex(forwardBy: 2) // We know these have been written.
|
||||||
|
XCTAssertEqual(self.buf.readBytes(length: 2), [0, 1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCopyBytesOverSelf() throws {
|
||||||
|
self.buf.writeBytes([0, 1, 2, 3])
|
||||||
|
XCTAssertNoThrow(try self.buf.copyBytes(at: self.buf.readerIndex, to: self.buf.readerIndex + 1, length: 3))
|
||||||
|
XCTAssertEqual(self.buf.readableBytes, 4)
|
||||||
|
XCTAssertEqual(self.buf.readBytes(length: self.buf.readableBytes), [0, 0, 1, 2])
|
||||||
|
|
||||||
|
self.buf.writeBytes([0, 1, 2, 3])
|
||||||
|
XCTAssertNoThrow(try self.buf.copyBytes(at: self.buf.readerIndex + 1, to: self.buf.readerIndex, length: 3))
|
||||||
|
XCTAssertEqual(self.buf.readableBytes, 4)
|
||||||
|
XCTAssertEqual(self.buf.readBytes(length: self.buf.readableBytes), [1, 2, 3, 3])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCopyBytesCoWs() throws {
|
||||||
|
let bytes: [UInt8] = (0..<self.buf.writableBytes).map { UInt8($0 % Int(UInt8.max)) }
|
||||||
|
self.buf.writeBytes(bytes)
|
||||||
|
var otherBuf = self.buf!
|
||||||
|
|
||||||
|
XCTAssertEqual(self.buf.writableBytes, 0)
|
||||||
|
|
||||||
|
XCTAssertNoThrow(try self.buf.copyBytes(at: self.buf.readerIndex, to: self.buf.readerIndex + 2, length: 1))
|
||||||
|
XCTAssertNotEqual(self.buf.readBytes(length: self.buf.readableBytes),
|
||||||
|
otherBuf.readBytes(length: otherBuf.readableBytes))
|
||||||
|
}
|
||||||
|
|
||||||
func testWriteABunchOfCollections() throws {
|
func testWriteABunchOfCollections() throws {
|
||||||
let overallData = "0123456789abcdef".data(using: .utf8)!
|
let overallData = "0123456789abcdef".data(using: .utf8)!
|
||||||
buf.writeBytes("0123".utf8)
|
buf.writeBytes("0123".utf8)
|
||||||
|
@ -1669,6 +1749,148 @@ class ByteBufferTest: XCTestCase {
|
||||||
XCTAssertEqual(buf.readableBytes, viewSlice.count)
|
XCTAssertEqual(buf.readableBytes, viewSlice.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBufferViewCoWs() throws {
|
||||||
|
self.buf.writeBytes([0x0, 0x1, 0x2])
|
||||||
|
var view = self.buf.readableBytesView
|
||||||
|
view.replaceSubrange(view.indices, with: [0xa, 0xb, 0xc])
|
||||||
|
|
||||||
|
XCTAssertEqual(self.buf.readBytes(length: 3), [0x0, 0x1, 0x2])
|
||||||
|
XCTAssertTrue(view.elementsEqual([0xa, 0xb, 0xc]))
|
||||||
|
|
||||||
|
self.buf.writeBytes([0x0, 0x1, 0x2])
|
||||||
|
view = self.buf.readableBytesView
|
||||||
|
view.replaceSubrange(view.indices, with: [0xa, 0xb])
|
||||||
|
|
||||||
|
XCTAssertEqual(self.buf.readBytes(length: 3), [0x0, 0x1, 0x2])
|
||||||
|
XCTAssertTrue(view.elementsEqual([0xa, 0xb]))
|
||||||
|
|
||||||
|
self.buf.writeBytes([0x0, 0x1, 0x2])
|
||||||
|
view = self.buf.readableBytesView
|
||||||
|
view.replaceSubrange(view.indices, with: [0xa, 0xb, 0xc, 0xd])
|
||||||
|
|
||||||
|
XCTAssertEqual(self.buf.readBytes(length: 3), [0x0, 0x1, 0x2])
|
||||||
|
XCTAssertTrue(view.elementsEqual([0xa, 0xb, 0xc, 0xd]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBufferViewMutationViaSubscriptIndex() throws {
|
||||||
|
self.buf.writeBytes([0x0, 0x1, 0x2])
|
||||||
|
var view = self.buf.readableBytesView
|
||||||
|
|
||||||
|
view[0] = 0xa
|
||||||
|
view[1] = 0xb
|
||||||
|
view[2] = 0xc
|
||||||
|
|
||||||
|
XCTAssertTrue(view.elementsEqual([0xa, 0xb, 0xc]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBufferViewReplaceBeyondEndOfRange() throws {
|
||||||
|
self.buf.writeBytes([1, 2, 3])
|
||||||
|
var view = self.buf.readableBytesView
|
||||||
|
view.replaceSubrange(2..<3, with: [2, 3, 4])
|
||||||
|
XCTAssertTrue(view.elementsEqual([1, 2, 2, 3, 4]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBufferViewReplaceWithSubrangeOfSelf() throws {
|
||||||
|
let oneToNine: [UInt8] = (1...9).map { $0 }
|
||||||
|
self.buf.writeBytes(oneToNine)
|
||||||
|
var view = self.buf.readableBytesView
|
||||||
|
|
||||||
|
view[6..<9] = view[0..<3]
|
||||||
|
XCTAssertTrue(view.elementsEqual([1, 2, 3, 4, 5, 6, 1, 2, 3]))
|
||||||
|
|
||||||
|
view[0..<3] = view[1..<4]
|
||||||
|
XCTAssertTrue(view.elementsEqual([2, 3, 4, 4, 5, 6, 1, 2, 3]))
|
||||||
|
|
||||||
|
view[1..<4] = view[0..<3]
|
||||||
|
XCTAssertTrue(view.elementsEqual([2, 2, 3, 4, 5, 6, 1, 2, 3]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBufferViewMutationViaSubscriptRange() throws {
|
||||||
|
let oneToNine: [UInt8] = (1...9).map { $0 }
|
||||||
|
var oneToNineBuffer = self.allocator.buffer(capacity: 9)
|
||||||
|
oneToNineBuffer.writeBytes(oneToNine)
|
||||||
|
let oneToNineView = oneToNineBuffer.readableBytesView
|
||||||
|
|
||||||
|
self.buf.writeBytes(Array(repeating: UInt8(0), count: 9))
|
||||||
|
var view = self.buf.readableBytesView
|
||||||
|
|
||||||
|
view[0..<3] = oneToNineView[0..<3]
|
||||||
|
XCTAssertTrue(view.elementsEqual([1, 2, 3, 0, 0, 0, 0, 0, 0]))
|
||||||
|
|
||||||
|
// Replace with shorter range
|
||||||
|
view[3..<6] = oneToNineView[3..<5]
|
||||||
|
XCTAssertTrue(view.elementsEqual([1, 2, 3, 4, 5, 0, 0, 0]))
|
||||||
|
|
||||||
|
// Replace with longer range
|
||||||
|
view[5..<8] = oneToNineView[5..<9]
|
||||||
|
XCTAssertTrue(view.elementsEqual(oneToNine))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBufferViewReplaceSubrangeWithEqualLengthBytes() throws {
|
||||||
|
self.buf.writeBytes([0x0, 0x1, 0x2, 0x3, 0x4])
|
||||||
|
|
||||||
|
var view = ByteBufferView(self.buf)
|
||||||
|
XCTAssertEqual(view.count, self.buf.readableBytes)
|
||||||
|
|
||||||
|
view.replaceSubrange(view.indices.suffix(3), with: [0xd, 0xe, 0xf])
|
||||||
|
|
||||||
|
var modifiedBuf = ByteBuffer(view)
|
||||||
|
XCTAssertEqual(self.buf.readerIndex, modifiedBuf.readerIndex)
|
||||||
|
XCTAssertEqual(self.buf.writerIndex, modifiedBuf.writerIndex)
|
||||||
|
XCTAssertEqual([0x0, 0x1, 0xd, 0xe, 0xf], modifiedBuf.readBytes(length: modifiedBuf.readableBytes)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBufferViewReplaceSubrangeWithFewerBytes() throws {
|
||||||
|
self.buf.writeBytes([0x0, 0x1, 0x2, 0x3, 0x4])
|
||||||
|
|
||||||
|
var view = ByteBufferView(self.buf)
|
||||||
|
view.replaceSubrange(view.indices.suffix(3), with: [0xd])
|
||||||
|
|
||||||
|
var modifiedBuf = ByteBuffer(view)
|
||||||
|
XCTAssertEqual(self.buf.readerIndex, modifiedBuf.readerIndex)
|
||||||
|
XCTAssertEqual(self.buf.writerIndex - 2, modifiedBuf.writerIndex)
|
||||||
|
XCTAssertEqual([0x0, 0x1, 0xd], modifiedBuf.readBytes(length: modifiedBuf.readableBytes)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBufferViewReplaceSubrangeWithMoreBytes() throws {
|
||||||
|
self.buf.writeBytes([0x0, 0x1, 0x2, 0x3])
|
||||||
|
|
||||||
|
var view = ByteBufferView(self.buf)
|
||||||
|
XCTAssertTrue(view.elementsEqual([0x0, 0x1, 0x2, 0x3]))
|
||||||
|
|
||||||
|
view.replaceSubrange(view.indices.suffix(1), with: [0xa, 0xb])
|
||||||
|
XCTAssertTrue(view.elementsEqual([0x0, 0x1, 0x2, 0xa, 0xb]))
|
||||||
|
|
||||||
|
var modifiedBuf = ByteBuffer(view)
|
||||||
|
XCTAssertEqual(self.buf.readerIndex, modifiedBuf.readerIndex)
|
||||||
|
XCTAssertEqual(self.buf.writerIndex + 1, modifiedBuf.writerIndex)
|
||||||
|
XCTAssertEqual([0x0, 0x1, 0x2, 0xa, 0xb], modifiedBuf.readBytes(length: modifiedBuf.readableBytes)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBufferViewEmpty() throws {
|
||||||
|
self.buf.writeBytes([0, 1, 2])
|
||||||
|
|
||||||
|
var view = ByteBufferView()
|
||||||
|
view[0..<0] = self.buf.readableBytesView
|
||||||
|
XCTAssertEqual(view.indices, 0..<3)
|
||||||
|
|
||||||
|
self.buf = ByteBuffer(view)
|
||||||
|
XCTAssertEqual(self.buf.readerIndex, 0)
|
||||||
|
XCTAssertEqual(self.buf.writerIndex, 3)
|
||||||
|
|
||||||
|
let anotherBuf = self.buf!
|
||||||
|
XCTAssertEqual([0, 1, 2], self.buf.readBytes(length: self.buf.readableBytes))
|
||||||
|
|
||||||
|
var anotherView = anotherBuf.readableBytesView
|
||||||
|
anotherView.replaceSubrange(0..<3, with: [])
|
||||||
|
XCTAssertTrue(anotherView.isEmpty)
|
||||||
|
|
||||||
|
self.buf = ByteBuffer(anotherView)
|
||||||
|
XCTAssertEqual(self.buf.readerIndex, 0)
|
||||||
|
XCTAssertEqual(self.buf.writerIndex, 0)
|
||||||
|
XCTAssertEqual([], self.buf.readBytes(length: self.buf.readableBytes))
|
||||||
|
}
|
||||||
|
|
||||||
func testByteBuffersCanBeInitializedFromByteBufferViews() throws {
|
func testByteBuffersCanBeInitializedFromByteBufferViews() throws {
|
||||||
self.buf.writeString("hello")
|
self.buf.writeString("hello")
|
||||||
|
|
||||||
|
|
|
@ -414,7 +414,7 @@ public final class ByteToMessageDecoderTest: XCTestCase {
|
||||||
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
|
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
|
||||||
}))
|
}))
|
||||||
XCTAssertNoThrow(XCTAssertEqual("56", try channel.readInbound(as: ByteBuffer.self).map {
|
XCTAssertNoThrow(XCTAssertEqual("56", try channel.readInbound(as: ByteBuffer.self).map {
|
||||||
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
|
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
|
||||||
}))
|
}))
|
||||||
XCTAssertNoThrow(XCTAssertEqual("78", try channel.readInbound(as: ByteBuffer.self).map {
|
XCTAssertNoThrow(XCTAssertEqual("78", try channel.readInbound(as: ByteBuffer.self).map {
|
||||||
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
|
String(decoding: $0.readableBytesView, as: Unicode.UTF8.self)
|
||||||
|
|
|
@ -83,12 +83,12 @@ public final class WebSocketFrameDecoderTest: XCTestCase {
|
||||||
while let d = try self.encoderChannel.readOutbound(as: ByteBuffer.self) {
|
while let d = try self.encoderChannel.readOutbound(as: ByteBuffer.self) {
|
||||||
XCTAssertNoThrow(try self.decoderChannel.writeInbound(d))
|
XCTAssertNoThrow(try self.decoderChannel.writeInbound(d))
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let producedFrame: WebSocketFrame = try self.decoderChannel.readInbound() else {
|
guard let producedFrame: WebSocketFrame = try self.decoderChannel.readInbound() else {
|
||||||
XCTFail("Did not produce a frame")
|
XCTFail("Did not produce a frame")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should only have gotten one frame!
|
// Should only have gotten one frame!
|
||||||
XCTAssertNoThrow(XCTAssertNil(try self.decoderChannel.readInbound(as: WebSocketFrame.self)))
|
XCTAssertNoThrow(XCTAssertNil(try self.decoderChannel.readInbound(as: WebSocketFrame.self)))
|
||||||
return producedFrame
|
return producedFrame
|
||||||
|
|
|
@ -31,6 +31,7 @@ services:
|
||||||
- MAX_ALLOCS_ALLOWED_encode_1000_ws_frames_new_buffer=4010
|
- MAX_ALLOCS_ALLOWED_encode_1000_ws_frames_new_buffer=4010
|
||||||
- MAX_ALLOCS_ALLOWED_encode_1000_ws_frames_new_buffer_with_space=4010
|
- MAX_ALLOCS_ALLOWED_encode_1000_ws_frames_new_buffer_with_space=4010
|
||||||
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=1000
|
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=1000
|
||||||
|
- MAX_ALLOCS_ALLOWED_modifying_byte_buffer_view=6010
|
||||||
- SANITIZER_ARG=--sanitize=thread
|
- SANITIZER_ARG=--sanitize=thread
|
||||||
|
|
||||||
performance-test:
|
performance-test:
|
||||||
|
|
|
@ -31,6 +31,7 @@ services:
|
||||||
- MAX_ALLOCS_ALLOWED_encode_1000_ws_frames_new_buffer=4010
|
- MAX_ALLOCS_ALLOWED_encode_1000_ws_frames_new_buffer=4010
|
||||||
- MAX_ALLOCS_ALLOWED_encode_1000_ws_frames_new_buffer_with_space=4010
|
- MAX_ALLOCS_ALLOWED_encode_1000_ws_frames_new_buffer_with_space=4010
|
||||||
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=1000
|
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=1000
|
||||||
|
- MAX_ALLOCS_ALLOWED_modifying_byte_buffer_view=6010
|
||||||
|
|
||||||
performance-test:
|
performance-test:
|
||||||
image: swift-nio:18.04-5.0
|
image: swift-nio:18.04-5.0
|
||||||
|
|
Loading…
Reference in New Issue