Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Sources/Testing/EmbeddedPlatform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ extension JSON {
///
/// - Throws: If the given path could not be opened for writing or, in
/// Embedded Swift, if the platform does not support writing JSON at all.
init(forWritingAtPath path: String) throws {
init(forWritingAtPath path: String) throws(CError) {
#if !hasFeature(Embedded)
_file = try FileHandle(forWritingAtPath: path)
#else
Expand All @@ -96,9 +96,9 @@ extension JSON {
/// unnecessary copies of `json` in memory.
///
/// - Throws: Any error that occurs while writing `json` or `terminator`.
func write(_ json: UnsafeRawBufferPointer, terminatedBy terminator: UInt8?) throws {
func write(_ json: UnsafeRawBufferPointer, terminatedBy terminator: UInt8?) throws(CError) {
#if !hasFeature(Embedded)
_ = try _file.withLock {
_ = try _file.withLock { () throws(CError) in
try _file.write(json)
if let terminator {
try _file.write(terminator)
Expand Down
6 changes: 4 additions & 2 deletions Sources/Testing/ExitTests/ExitTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -628,8 +628,10 @@ extension ExitTest {
/// and standard error streams of the current process.
private static func _writeBarrierValues() {
let barrierValue = Self.barrierValue
try? FileHandle.stdout.write(barrierValue)
try? FileHandle.stderr.write(barrierValue)
barrierValue.withUnsafeBufferPointer { barrierValue in
try? FileHandle.stdout.write(barrierValue)
try? FileHandle.stderr.write(barrierValue)
}
}

/// A handler that is invoked when an exit test starts.
Expand Down
84 changes: 33 additions & 51 deletions Sources/Testing/Support/FileHandle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ struct FileHandle: ~Copyable, Sendable {
/// - options: The options to open `path` with.
///
/// - Throws: Any error preventing the stream from being opened.
init(atPath path: String, options: OpenOptions) throws {
init(atPath path: String, options: OpenOptions) throws(CError) {
#if os(Windows)
// Special-case CONOUT$ to map to stdout. This way, if somebody specifies
// CONOUT$ as the target path for XML or JSON output from `swift test`,
Expand All @@ -156,8 +156,8 @@ struct FileHandle: ~Copyable, Sendable {
}

// Windows deprecates fopen() as insecure, so call _wfopen_s() instead.
let fileHandle = try path.withCString(encodedAs: UTF16.self) { path in
try options.stringValue.withCString(encodedAs: UTF16.self) { mode in
let fileHandle = try path.withCString(encodedAs: UTF16.self) { path throws(CError) in
try options.stringValue.withCString(encodedAs: UTF16.self) { mode throws(CError) in
var file: SWT_FILEHandle?
let result = _wfopen_s(&file, path, mode)
guard let file, result == 0 else {
Expand Down Expand Up @@ -186,7 +186,7 @@ struct FileHandle: ~Copyable, Sendable {
/// By default, the resulting file handle is not inherited by any child
/// processes (that is, `FD_CLOEXEC` is set on POSIX-like systems and
/// `HANDLE_FLAG_INHERIT` is cleared on Windows.).
init(forReadingAtPath path: String, options: OpenOptions = []) throws {
init(forReadingAtPath path: String, options: OpenOptions = []) throws(CError) {
try self.init(atPath: path, options: options.union(.readAccess))
}

Expand All @@ -202,7 +202,7 @@ struct FileHandle: ~Copyable, Sendable {
/// By default, the resulting file handle is not inherited by any child
/// processes (that is, `FD_CLOEXEC` is set on POSIX-like systems and
/// `HANDLE_FLAG_INHERIT` is cleared on Windows.).
init(forWritingAtPath path: String, options: OpenOptions = []) throws {
init(forWritingAtPath path: String, options: OpenOptions = []) throws(CError) {
try self.init(atPath: path, options: options.union(.writeAccess))
}

Expand Down Expand Up @@ -232,7 +232,7 @@ struct FileHandle: ~Copyable, Sendable {
///
/// The resulting file handle takes ownership of `fd` and closes it when it is
/// deinitialized or if an error is thrown from this initializer.
init(unsafePOSIXFileDescriptor fd: consuming CInt, options: OpenOptions) throws {
init(unsafePOSIXFileDescriptor fd: consuming CInt, options: OpenOptions) throws(CError) {
#if os(Windows)
let fileHandle = _fdopen(fd, options.stringValue)
#else
Expand Down Expand Up @@ -289,7 +289,7 @@ struct FileHandle: ~Copyable, Sendable {
///
/// Use this function when calling C I/O interfaces such as `fputs()` on the
/// underlying C file handle.
borrowing func withUnsafeCFILEHandle<R>(_ body: (borrowing SWT_FILEHandle) throws -> R) rethrows -> R {
borrowing func withUnsafeCFILEHandle<R, E>(_ body: (borrowing SWT_FILEHandle) throws(E) -> R) throws(E) -> R {
try body(_fileHandle)
}

Expand All @@ -306,8 +306,8 @@ struct FileHandle: ~Copyable, Sendable {
/// that require a file descriptor instead of the standard `FILE *`
/// representation. If the file handle cannot be converted to a file
/// descriptor, `nil` is passed to `body`.
borrowing func withUnsafePOSIXFileDescriptor<R>(_ body: (borrowing CInt?) throws -> R) rethrows -> R {
try withUnsafeCFILEHandle { handle in
borrowing func withUnsafePOSIXFileDescriptor<R, E>(_ body: (borrowing CInt?) throws(E) -> R) throws(E) -> R {
try withUnsafeCFILEHandle { handle throws(E) in
#if os(anyAppleOS) || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI)
let fd = fileno(handle)
#elseif os(Windows)
Expand Down Expand Up @@ -337,7 +337,7 @@ struct FileHandle: ~Copyable, Sendable {
///
/// The resulting file handle takes ownership of `handle` and closes it when
/// it is deinitialized or if an error is thrown from this initializer.
init(unsafeWindowsHANDLE handle: consuming HANDLE, options: OpenOptions) throws {
init(unsafeWindowsHANDLE handle: consuming HANDLE, options: OpenOptions) throws(CError) {
var flags: CInt = _O_BINARY
switch (options.contains(.readAccess), options.contains(.writeAccess)) {
case (true, true):
Expand Down Expand Up @@ -376,8 +376,8 @@ struct FileHandle: ~Copyable, Sendable {
/// that require the file's `HANDLE` representation instead of the standard
/// `FILE *` representation. If the file handle cannot be converted to a
/// Windows handle, `nil` is passed to `body`.
borrowing func withUnsafeWindowsHANDLE<R>(_ body: (borrowing HANDLE?) throws -> R) rethrows -> R {
try withUnsafePOSIXFileDescriptor { fd in
borrowing func withUnsafeWindowsHANDLE<R, E>(_ body: (borrowing HANDLE?) throws(E) -> R) throws(E) -> R {
try withUnsafePOSIXFileDescriptor { fd throws(E) in
guard let fd else {
return try body(nil)
}
Expand All @@ -403,8 +403,8 @@ struct FileHandle: ~Copyable, Sendable {
/// to the underlying file. It can be used when, for example, write operations
/// are split across multiple calls but must not be interleaved with writes on
/// other threads.
borrowing func withLock<R>(_ body: () throws -> R) rethrows -> R {
try withUnsafeCFILEHandle { handle in
borrowing func withLock<R, E>(_ body: () throws(E) -> R) throws(E) -> R {
try withUnsafeCFILEHandle { handle throws(E) in
#if os(anyAppleOS) || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)
flockfile(handle)
defer {
Expand Down Expand Up @@ -434,7 +434,7 @@ extension FileHandle {
/// current offset and ending at the end of the file.
///
/// - Throws: Any error that occurred while reading the file.
func readToEnd() throws -> [UInt8] {
func readToEnd() throws(CError) -> [UInt8] {
var result = [UInt8]()

// If possible, reserve enough space in the resulting buffer to contain
Expand All @@ -459,9 +459,9 @@ extension FileHandle {
result.reserveCapacity(size)
}

try withLock {
try withUnsafeCFILEHandle { file in
try withUnsafeTemporaryAllocation(byteCount: 1024, alignment: 1) { buffer in
try withLock { () throws(CError) in
try withUnsafeCFILEHandle { file throws(CError) in
try withUnsafeTemporaryAllocation(byteCount: 1024, alignment: 1) { buffer throws(CError) in
repeat {
let countRead = fread(buffer.baseAddress!, 1, buffer.count, file)
if 0 != ferror(file) {
Expand Down Expand Up @@ -490,8 +490,9 @@ extension FileHandle {
/// stream or the first byte that matches `isTerminator`; and the terminator
/// byte if one was encountered.
///
/// - Throws: Any error that occurred while reading the file.
func read(until isTerminator: (UInt8) throws -> Bool) throws -> (bytesRead: [UInt8], terminator: UInt8?) {
/// - Throws: Any error that occurred while reading the file, or any error
/// thrown by `isTerminator`.
func read<E>(until isTerminator: (UInt8) throws(E) -> Bool) throws(any Error) -> (bytesRead: [UInt8], terminator: UInt8?) {
var bytesRead = [UInt8]()
var terminator: UInt8?

Expand Down Expand Up @@ -535,8 +536,8 @@ extension FileHandle {
///
/// - Throws: Any error that occurred while writing `bytes`. If an error
/// occurs while flushing the file, it is not thrown.
func write(_ bytes: UnsafeBufferPointer<UInt8>, flushAfterward: Bool = true) throws {
try withUnsafeCFILEHandle { file in
func write(_ bytes: UnsafeBufferPointer<UInt8>, flushAfterward: Bool = true) throws(CError) {
try withUnsafeCFILEHandle { file throws(CError) in
defer {
if flushAfterward {
_ = fflush(file)
Expand All @@ -552,25 +553,6 @@ extension FileHandle {
}
}

/// Write a sequence of bytes to this file handle.
///
/// - Parameters:
/// - bytes: The bytes to write.
/// - flushAfterward: Whether or not to flush the file (with `fflush()`)
/// after writing. If `true`, `fflush()` is called even if an error
/// occurred while writing.
///
/// - Throws: Any error that occurred while writing `bytes`. If an error
/// occurs while flushing the file, it is not thrown.
///
/// - Precondition: `bytes` must provide contiguous storage.
func write(_ bytes: some Sequence<UInt8>, flushAfterward: Bool = true) throws {
let hasContiguousStorage: Void? = try bytes.withContiguousStorageIfAvailable { bytes in
try write(bytes, flushAfterward: flushAfterward)
}
precondition(hasContiguousStorage != nil, "byte sequence must provide contiguous storage: \(bytes)")
}

/// Write a sequence of bytes to this file handle.
///
/// - Parameters:
Expand All @@ -582,8 +564,8 @@ extension FileHandle {
///
/// - Throws: Any error that occurred while writing `bytes`. If an error
/// occurs while flushing the file, it is not thrown.
func write(_ bytes: UnsafeRawBufferPointer, flushAfterward: Bool = true) throws {
try bytes.withMemoryRebound(to: UInt8.self) { bytes in
func write(_ bytes: UnsafeRawBufferPointer, flushAfterward: Bool = true) throws(CError) {
try bytes.withMemoryRebound(to: UInt8.self) { bytes throws(CError) in
try write(bytes, flushAfterward: flushAfterward)
}
}
Expand All @@ -598,8 +580,8 @@ extension FileHandle {
///
/// - Throws: Any error that occurred while writing `bytes`. If an error
/// occurs while flushing the file, it is not thrown.
func write(_ byte: UInt8, flushAfterward: Bool = true) throws {
try withUnsafeCFILEHandle { file in
func write(_ byte: UInt8, flushAfterward: Bool = true) throws(CError) {
try withUnsafeCFILEHandle { file throws(CError) in
defer {
if flushAfterward {
_ = fflush(file)
Expand All @@ -624,16 +606,16 @@ extension FileHandle {
/// occurs while flushing the file, it is not thrown.
///
/// `string` is converted to a UTF-8 C string and written to this file handle.
func write(_ string: String, flushAfterward: Bool = true) throws {
try withUnsafeCFILEHandle { file in
func write(_ string: String, flushAfterward: Bool = true) throws(CError) {
try withUnsafeCFILEHandle { file throws(CError) in
defer {
if flushAfterward {
_ = fflush(file)
}
}

var string = string
try string.withUTF8 { string in
try string.withUTF8 { string throws(CError) in
try write(string, flushAfterward: flushAfterward)
}
}
Expand Down Expand Up @@ -734,8 +716,8 @@ extension FileHandle {
/// By default, the resulting file handles are not inherited by any child
/// processes (that is, `FD_CLOEXEC` is set on POSIX-like systems and
/// `HANDLE_FLAG_INHERIT` is cleared on Windows.).
static func makePipe(readEnd: inout FileHandle?, writeEnd: inout FileHandle?) throws {
var (fdReadEnd, fdWriteEnd) = try withUnsafeTemporaryAllocation(of: CInt.self, capacity: 2) { fds in
static func makePipe(readEnd: inout FileHandle?, writeEnd: inout FileHandle?) throws(CError) {
var (fdReadEnd, fdWriteEnd) = try withUnsafeTemporaryAllocation(of: CInt.self, capacity: 2) { fds throws(CError) in
guard 0 == _pipeWithO_CLOEXEC(fds.baseAddress!) else {
throw CError(rawValue: swt_errno())
}
Expand Down Expand Up @@ -908,7 +890,7 @@ func canonicalizePath(_ path: String) -> String? {
/// - fd: The file descriptor.
///
/// - Throws: Any error that occurred while setting the flag.
func setFD_CLOEXEC(_ flag: Bool, onFileDescriptor fd: CInt) throws {
func setFD_CLOEXEC(_ flag: Bool, onFileDescriptor fd: CInt) throws(CError) {
switch swt_getfdflags(fd) {
case -1:
// An error occurred reading the flags for this file descriptor.
Expand Down
24 changes: 11 additions & 13 deletions Tests/TestingTests/Support/FileHandleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,21 +103,14 @@ struct FileHandleTests {
func canWrite() throws {
try withTemporaryPath { path in
let fileHandle = try FileHandle(forWritingAtPath: path)
try fileHandle.write([0, 1, 2, 3, 4, 5])
let bytes: [UInt8] = [0, 1, 2, 3, 4, 5]
try bytes.withUnsafeBufferPointer { bytes in
try fileHandle.write(bytes)
}
try fileHandle.write("Hello world!")
}
}

#if !SWT_NO_EXIT_TESTS
@Test("Writing requires contiguous storage")
func writeIsContiguous() async {
await #expect(processExitsWith: .failure) {
let fileHandle = try FileHandle.null(options: [.writeAccess])
try fileHandle.write([1, 2, 3, 4, 5].lazy.filter { $0 == 1 })
}
}
#endif

@Test("Can read from a file")
func canRead() throws {
let bytes: [UInt8] = (0 ..< 8192).map { _ in
Expand All @@ -126,7 +119,9 @@ struct FileHandleTests {
try withTemporaryPath { path in
do {
let fileHandle = try FileHandle(forWritingAtPath: path)
try fileHandle.write(bytes)
try bytes.withUnsafeBufferPointer { bytes in
try fileHandle.write(bytes)
}
}
let fileHandle = try FileHandle(forReadingAtPath: path)
let bytes2 = try fileHandle.readToEnd()
Expand All @@ -138,7 +133,10 @@ struct FileHandleTests {
func cannotWriteBytesToReadOnlyFile() throws {
let fileHandle = try FileHandle.null(options: [.readAccess])
#expect(throws: CError.self) {
try fileHandle.write([0, 1, 2, 3, 4, 5])
let bytes: [UInt8] = [0, 1, 2, 3, 4, 5]
try bytes.withUnsafeBufferPointer { bytes in
try fileHandle.write(bytes)
}
}
}

Expand Down
Loading