283 lines
10 KiB
Swift
283 lines
10 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2017-2021 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 XCTest
|
|
import NIOCore
|
|
@testable import NIOPosix
|
|
|
|
class FileRegionTest : XCTestCase {
|
|
|
|
func testWriteFileRegion() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let numBytes = 16 * 1024
|
|
|
|
var content = ""
|
|
for i in 0..<numBytes {
|
|
content.append("\(i)")
|
|
}
|
|
let bytes = Array(content.utf8)
|
|
|
|
let countingHandler = ByteCountingHandler(numBytes: bytes.count, promise: group.next().makePromise())
|
|
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
|
|
.childChannelInitializer { $0.pipeline.addHandler(countingHandler) }
|
|
.bind(host: "127.0.0.1", port: 0)
|
|
.wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.connect(to: serverChannel.localAddress!)
|
|
.wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.close().wait())
|
|
}
|
|
|
|
try withTemporaryFile { _, filePath in
|
|
let handle = try NIOFileHandle(path: filePath)
|
|
let fr = FileRegion(fileHandle: handle, readerIndex: 0, endIndex: bytes.count)
|
|
defer {
|
|
XCTAssertNoThrow(try handle.close())
|
|
}
|
|
try content.write(toFile: filePath, atomically: false, encoding: .ascii)
|
|
try clientChannel.writeAndFlush(NIOAny(fr)).wait()
|
|
var buffer = clientChannel.allocator.buffer(capacity: bytes.count)
|
|
buffer.writeBytes(bytes)
|
|
try countingHandler.assertReceived(buffer: buffer)
|
|
}
|
|
}
|
|
|
|
func testWriteEmptyFileRegionDoesNotHang() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let countingHandler = ByteCountingHandler(numBytes: 0, promise: group.next().makePromise())
|
|
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
|
|
.childChannelInitializer { $0.pipeline.addHandler(countingHandler) }
|
|
.bind(host: "127.0.0.1", port: 0)
|
|
.wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.connect(to: serverChannel.localAddress!)
|
|
.wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.close().wait())
|
|
}
|
|
|
|
try withTemporaryFile { _, filePath in
|
|
let handle = try NIOFileHandle(path: filePath)
|
|
let fr = FileRegion(fileHandle: handle, readerIndex: 0, endIndex: 0)
|
|
defer {
|
|
XCTAssertNoThrow(try handle.close())
|
|
}
|
|
try "".write(toFile: filePath, atomically: false, encoding: .ascii)
|
|
|
|
var futures: [EventLoopFuture<Void>] = []
|
|
for _ in 0..<10 {
|
|
futures.append(clientChannel.write(NIOAny(fr)))
|
|
}
|
|
try clientChannel.writeAndFlush(NIOAny(fr)).wait()
|
|
try futures.forEach { try $0.wait() }
|
|
}
|
|
}
|
|
|
|
func testOutstandingFileRegionsWork() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let numBytes = 16 * 1024
|
|
|
|
var content = ""
|
|
for i in 0..<numBytes {
|
|
content.append("\(i)")
|
|
}
|
|
let bytes = Array(content.utf8)
|
|
|
|
let countingHandler = ByteCountingHandler(numBytes: bytes.count, promise: group.next().makePromise())
|
|
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
|
|
.childChannelInitializer { $0.pipeline.addHandler(countingHandler) }
|
|
.bind(host: "127.0.0.1", port: 0)
|
|
.wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.connect(to: serverChannel.localAddress!)
|
|
.wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.syncCloseAcceptingAlreadyClosed())
|
|
}
|
|
|
|
try withTemporaryFile { fd, filePath in
|
|
let fh1 = try NIOFileHandle(path: filePath)
|
|
let fh2 = try NIOFileHandle(path: filePath)
|
|
let fr1 = FileRegion(fileHandle: fh1, readerIndex: 0, endIndex: bytes.count)
|
|
let fr2 = FileRegion(fileHandle: fh2, readerIndex: 0, endIndex: bytes.count)
|
|
defer {
|
|
XCTAssertNoThrow(try fh1.close())
|
|
XCTAssertNoThrow(try fh2.close())
|
|
}
|
|
try content.write(toFile: filePath, atomically: false, encoding: .ascii)
|
|
XCTAssertThrowsError(try clientChannel.writeAndFlush(NIOAny(fr1)).flatMap { () -> EventLoopFuture<Void> in
|
|
let frFuture = clientChannel.write(NIOAny(fr2))
|
|
var buffer = clientChannel.allocator.buffer(capacity: bytes.count)
|
|
buffer.writeBytes(bytes)
|
|
let bbFuture = clientChannel.write(NIOAny(buffer))
|
|
clientChannel.close(promise: nil)
|
|
clientChannel.flush()
|
|
return frFuture.flatMap { bbFuture }
|
|
}.wait()) { error in
|
|
XCTAssertEqual(.ioOnClosedChannel, error as? ChannelError)
|
|
}
|
|
|
|
var buffer = clientChannel.allocator.buffer(capacity: bytes.count)
|
|
buffer.writeBytes(bytes)
|
|
try countingHandler.assertReceived(buffer: buffer)
|
|
}
|
|
}
|
|
|
|
func testWholeFileFileRegion() throws {
|
|
try withTemporaryFile(content: "hello") { fd, path in
|
|
let handle = try NIOFileHandle(path: path)
|
|
let region = try FileRegion(fileHandle: handle)
|
|
defer {
|
|
XCTAssertNoThrow(try handle.close())
|
|
}
|
|
XCTAssertEqual(0, region.readerIndex)
|
|
XCTAssertEqual(5, region.readableBytes)
|
|
XCTAssertEqual(5, region.endIndex)
|
|
}
|
|
}
|
|
|
|
func testWholeEmptyFileFileRegion() throws {
|
|
try withTemporaryFile(content: "") { _, path in
|
|
let handle = try NIOFileHandle(path: path)
|
|
let region = try FileRegion(fileHandle: handle)
|
|
defer {
|
|
XCTAssertNoThrow(try handle.close())
|
|
}
|
|
XCTAssertEqual(0, region.readerIndex)
|
|
XCTAssertEqual(0, region.readableBytes)
|
|
XCTAssertEqual(0, region.endIndex)
|
|
}
|
|
}
|
|
|
|
func testFileRegionDuplicatesShareSeekPointer() throws {
|
|
try withTemporaryFile(content: "0123456789") { fh1, path in
|
|
let fh2 = try fh1.duplicate()
|
|
|
|
var fr1Bytes: [UInt8] = Array(repeating: 0, count: 5)
|
|
var fr2Bytes = fr1Bytes
|
|
try fh1.withUnsafeFileDescriptor { fd in
|
|
let r = try Posix.read(descriptor: fd, pointer: &fr1Bytes, size: 5)
|
|
XCTAssertEqual(r, IOResult<Int>.processed(5))
|
|
}
|
|
try fh2.withUnsafeFileDescriptor { fd in
|
|
let r = try Posix.read(descriptor: fd, pointer: &fr2Bytes, size: 5)
|
|
XCTAssertEqual(r, IOResult<Int>.processed(5))
|
|
}
|
|
defer {
|
|
// fr2's underlying fd must be closed by us.
|
|
XCTAssertNoThrow(try fh2.close())
|
|
}
|
|
|
|
XCTAssertEqual(Array("01234".utf8), fr1Bytes)
|
|
XCTAssertEqual(Array("56789".utf8), fr2Bytes)
|
|
}
|
|
}
|
|
|
|
func testMassiveFileRegionThatJustAboutWorks() {
|
|
withTemporaryFile(content: "0123456789") { fh, path in
|
|
// just in case someone uses 32bit platforms
|
|
let readerIndex = UInt64(_UInt56.max) < UInt64(Int.max) ? Int(_UInt56.max) : Int.max
|
|
let fr = FileRegion(fileHandle: fh, readerIndex: readerIndex, endIndex: Int.max)
|
|
XCTAssertEqual(readerIndex, fr.readerIndex)
|
|
XCTAssertEqual(Int.max, fr.endIndex)
|
|
}
|
|
}
|
|
|
|
func testMassiveFileRegionReaderIndexWorks() {
|
|
withTemporaryFile(content: "0123456789") { fh, path in
|
|
// just in case someone uses 32bit platforms
|
|
let readerIndex = (UInt64(_UInt56.max) < UInt64(Int.max) ? Int(_UInt56.max) : Int.max) - 1000
|
|
var fr = FileRegion(fileHandle: fh, readerIndex: readerIndex, endIndex: Int.max)
|
|
for i in 0..<1000 {
|
|
XCTAssertEqual(readerIndex + i, fr.readerIndex)
|
|
XCTAssertEqual(Int.max, fr.endIndex)
|
|
fr.moveReaderIndex(forwardBy: 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testFileRegionAndIODataFitsInACoupleOfEnums() throws {
|
|
enum Level4 {
|
|
case case1(FileRegion)
|
|
case case2(FileRegion)
|
|
case case3(IOData)
|
|
case case4(IOData)
|
|
}
|
|
enum Level3 {
|
|
case case1(Level4)
|
|
case case2(Level4)
|
|
case case3(Level4)
|
|
case case4(Level4)
|
|
}
|
|
enum Level2 {
|
|
case case1(Level3)
|
|
case case2(Level3)
|
|
case case3(Level3)
|
|
case case4(Level3)
|
|
}
|
|
enum Level1 {
|
|
case case1(Level2)
|
|
case case2(Level2)
|
|
case case3(Level2)
|
|
case case4(Level2)
|
|
}
|
|
|
|
XCTAssertLessThanOrEqual(MemoryLayout<FileRegion>.size, 23)
|
|
XCTAssertLessThanOrEqual(MemoryLayout<Level1>.size, 24)
|
|
|
|
XCTAssertNoThrow(try withTemporaryFile(content: "0123456789") { fh, path in
|
|
let fr = try FileRegion(fileHandle: fh)
|
|
XCTAssertLessThanOrEqual(MemoryLayout.size(ofValue: Level1.case1(.case2(.case3(.case4(.fileRegion(fr)))))), 24)
|
|
XCTAssertLessThanOrEqual(MemoryLayout.size(ofValue: Level1.case1(.case3(.case4(.case1(fr))))), 24)
|
|
})
|
|
}
|
|
}
|