-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make SynchronizedFileSink.close unavailable from async (#195)
Motivation syncClose will block whatever thread it's on indefinitely. That makes it unsafe to call in async contexts. Modifications Add a new close() method that's async. Make the existing method unavailable from async. Add some tests. Results Easier to close these from async contexts
- Loading branch information
Showing
4 changed files
with
191 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
Tests/NIOExtrasTests/SynchronizedFileSinkTests+XCTest.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftNIO open source project | ||
// | ||
// Copyright (c) 2018-2023 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// SynchronizedFileSinkTests+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 SynchronizedFileSinkTests { | ||
|
||
@available(*, deprecated, message: "not actually deprecated. Just deprecated to allow deprecated tests (which test deprecated functionality) without warnings") | ||
static var allTests : [(String, (SynchronizedFileSinkTests) -> () throws -> Void)] { | ||
return [ | ||
("testSimpleFileSink", testSimpleFileSink), | ||
("testSimpleFileSinkAsyncShutdown", testSimpleFileSinkAsyncShutdown), | ||
] | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftNIO open source project | ||
// | ||
// Copyright (c) 2023 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 NIOCore | ||
import NIOEmbedded | ||
@testable import NIOExtras | ||
|
||
final class SynchronizedFileSinkTests: XCTestCase { | ||
func testSimpleFileSink() throws { | ||
try withTemporaryFile { file, path in | ||
let sink = try NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile(path: path, errorHandler: { XCTFail("Caught error \($0)") }) | ||
|
||
sink.write(buffer: ByteBuffer(string: "Hello, ")) | ||
sink.write(buffer: ByteBuffer(string: "world!")) | ||
try sink.syncClose() | ||
|
||
let data = try Data(contentsOf: URL(fileURLWithPath: path)) | ||
XCTAssertEqual(data, Data(NIOWritePCAPHandler.pcapFileHeader.readableBytesView) + Data("Hello, world!".utf8)) | ||
} | ||
} | ||
|
||
func testSimpleFileSinkAsyncShutdown() throws { | ||
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return } | ||
XCTAsyncTest { | ||
try await withTemporaryFile { file, path in | ||
let sink = try NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile(path: path, errorHandler: { XCTFail("Caught error \($0)") }) | ||
|
||
sink.write(buffer: ByteBuffer(string: "Hello, ")) | ||
sink.write(buffer: ByteBuffer(string: "world!")) | ||
try await sink.close() | ||
|
||
let data = try Data(contentsOf: URL(fileURLWithPath: path)) | ||
XCTAssertEqual(data, Data(NIOWritePCAPHandler.pcapFileHeader.readableBytesView) + Data("Hello, world!".utf8)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
fileprivate func withTemporaryFile<T>(content: String? = nil, _ body: (NIOCore.NIOFileHandle, String) throws -> T) throws -> T { | ||
let temporaryFilePath = "\(temporaryDirectory)/nio_extras_\(UUID())" | ||
FileManager.default.createFile(atPath: temporaryFilePath, contents: content?.data(using: .utf8)) | ||
defer { | ||
XCTAssertNoThrow(try FileManager.default.removeItem(atPath: temporaryFilePath)) | ||
} | ||
|
||
let fileHandle = try NIOFileHandle(path: temporaryFilePath, mode: [.read, .write]) | ||
defer { | ||
XCTAssertNoThrow(try fileHandle.close()) | ||
} | ||
|
||
return try body(fileHandle, temporaryFilePath) | ||
} | ||
|
||
fileprivate func withTemporaryFile<T>(content: String? = nil, _ body: (NIOCore.NIOFileHandle, String) async throws -> T) async throws -> T { | ||
let temporaryFilePath = "\(temporaryDirectory)/nio_extras_\(UUID())" | ||
FileManager.default.createFile(atPath: temporaryFilePath, contents: content?.data(using: .utf8)) | ||
defer { | ||
XCTAssertNoThrow(try FileManager.default.removeItem(atPath: temporaryFilePath)) | ||
} | ||
|
||
let fileHandle = try NIOFileHandle(path: temporaryFilePath, mode: [.read, .write]) | ||
defer { | ||
XCTAssertNoThrow(try fileHandle.close()) | ||
} | ||
|
||
return try await body(fileHandle, temporaryFilePath) | ||
} | ||
|
||
fileprivate var temporaryDirectory: String { | ||
#if os(Linux) | ||
return "/tmp" | ||
#else | ||
if #available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *) { | ||
return FileManager.default.temporaryDirectory.path | ||
} else { | ||
return "/tmp" | ||
} | ||
#endif // os | ||
} | ||
|
||
extension XCTestCase { | ||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) | ||
/// Cross-platform XCTest support for async-await tests. | ||
/// | ||
/// Currently the Linux implementation of XCTest doesn't have async-await support. | ||
/// Until it does, we make use of this shim which uses a detached `Task` along with | ||
/// `XCTest.wait(for:timeout:)` to wrap the operation. | ||
/// | ||
/// - NOTE: Support for Linux is tracked by https://bugs.swift.org/browse/SR-14403. | ||
/// - NOTE: Implementation currently in progress: https://github.com/apple/swift-corelibs-xctest/pull/326 | ||
func XCTAsyncTest( | ||
expectationDescription: String = "Async operation", | ||
timeout: TimeInterval = 30, | ||
file: StaticString = #filePath, | ||
line: UInt = #line, | ||
function: StaticString = #function, | ||
operation: @escaping @Sendable () async throws -> Void | ||
) { | ||
let expectation = self.expectation(description: expectationDescription) | ||
Task { | ||
do { | ||
try await operation() | ||
} catch { | ||
XCTFail("Error thrown while executing \(function): \(error)", file: file, line: line) | ||
Thread.callStackSymbols.forEach { print($0) } | ||
} | ||
expectation.fulfill() | ||
} | ||
self.wait(for: [expectation], timeout: timeout) | ||
} | ||
} |