Skip to content

Commit 88efc08

Browse files
authored
Move Windows-specific HANDLE-to-FileHandle logic into FileHandle. (#1726)
Some minor refactoring to make the code used by exit tests that converts a Windows `HANDLE` to a Swift Testing `FileHandle` an initializer of `FileHandle` rather than part of an exit-test-specific function. This change moves our use of `_open_osfhandle()` closer to the corresponding use of `_get_osfhandle()` in `FileHandle.withUnsafeWindowsHANDLE()`. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent bd820f8 commit 88efc08

3 files changed

Lines changed: 71 additions & 29 deletions

File tree

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -691,32 +691,18 @@ extension ExitTest {
691691
// Erase the environment variable so that it cannot accidentally be opened
692692
// twice (nor, in theory, affect the code of the exit test.)
693693
Environment.setVariable(nil, named: name)
694-
var fd: CInt?
695694
#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD)
696-
fd = CInt(environmentVariable)
697-
#elseif os(Windows)
698-
if let handle = UInt(environmentVariable).flatMap(HANDLE.init(bitPattern:)) {
699-
var flags: CInt = switch (options.contains(.readAccess), options.contains(.writeAccess)) {
700-
case (true, true):
701-
_O_RDWR
702-
case (true, false):
703-
_O_RDONLY
704-
case (false, true):
705-
_O_WRONLY
706-
case (false, false):
707-
0
708-
}
709-
flags |= _O_BINARY | _O_NOINHERIT
710-
fd = _open_osfhandle(Int(bitPattern: handle), flags)
695+
guard let fd = CInt(environmentVariable), fd >= 0 else {
696+
return nil
711697
}
698+
return try? FileHandle(unsafePOSIXFileDescriptor: fd, options: options)
699+
#elseif os(Windows)
700+
return UInt(environmentVariable)
701+
.flatMap(HANDLE.init(bitPattern:))
702+
.flatMap { try? FileHandle(unsafeWindowsHANDLE: $0, options: options) }
712703
#else
713704
#warning("Platform-specific implementation missing: additional file descriptors unavailable")
714705
#endif
715-
guard let fd, fd >= 0 else {
716-
return nil
717-
}
718-
719-
return try? FileHandle(unsafePOSIXFileDescriptor: fd, options: options)
720706
}
721707

722708
/// Make a string suitable for use as the value of an environment variable

Sources/Testing/Support/FileHandle.swift

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -220,20 +220,19 @@ struct FileHandle: ~Copyable, Sendable {
220220
_closeWhenDone = closeWhenDone
221221
}
222222

223-
/// Initialize an instance of this type with an existing POSIX file descriptor
224-
/// for reading.
223+
/// Initialize an instance of this type with an existing POSIX file descriptor.
225224
///
226225
/// - Parameters:
227226
/// - fd: The POSIX file descriptor to wrap. The caller is responsible for
228-
/// ensuring that this file handle is open in the expected mode and that
229-
/// another part of the system won't close it.
227+
/// ensuring that this file descriptor is open in the expected mode and
228+
/// that another part of the system won't close it.
230229
/// - options: The options `fd` was opened with.
231230
///
232231
/// - Throws: Any error preventing the stream from being opened.
233232
///
234233
/// The resulting file handle takes ownership of `fd` and closes it when it is
235234
/// deinitialized or if an error is thrown from this initializer.
236-
init(unsafePOSIXFileDescriptor fd: CInt, options: OpenOptions) throws {
235+
init(unsafePOSIXFileDescriptor fd: consuming CInt, options: OpenOptions) throws {
237236
#if os(Windows)
238237
let fileHandle = _fdopen(fd, options.stringValue)
239238
#else
@@ -290,7 +289,7 @@ struct FileHandle: ~Copyable, Sendable {
290289
///
291290
/// Use this function when calling C I/O interfaces such as `fputs()` on the
292291
/// underlying C file handle.
293-
borrowing func withUnsafeCFILEHandle<R>(_ body: (SWT_FILEHandle) throws -> R) rethrows -> R {
292+
borrowing func withUnsafeCFILEHandle<R>(_ body: (borrowing SWT_FILEHandle) throws -> R) rethrows -> R {
294293
try body(_fileHandle)
295294
}
296295

@@ -307,7 +306,7 @@ struct FileHandle: ~Copyable, Sendable {
307306
/// that require a file descriptor instead of the standard `FILE *`
308307
/// representation. If the file handle cannot be converted to a file
309308
/// descriptor, `nil` is passed to `body`.
310-
borrowing func withUnsafePOSIXFileDescriptor<R>(_ body: (CInt?) throws -> R) rethrows -> R {
309+
borrowing func withUnsafePOSIXFileDescriptor<R>(_ body: (borrowing CInt?) throws -> R) rethrows -> R {
311310
try withUnsafeCFILEHandle { handle in
312311
#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI)
313312
let fd = fileno(handle)
@@ -326,6 +325,44 @@ struct FileHandle: ~Copyable, Sendable {
326325
}
327326

328327
#if os(Windows)
328+
/// Initialize an instance of this type with an existing Windows file handle.
329+
///
330+
/// - Parameters:
331+
/// - handle: The Windows file handle to wrap. The caller is responsible for
332+
/// ensuring that this file handle is open in the expected mode and that
333+
/// another part of the system won't close it.
334+
/// - options: The options `handle` was opened with.
335+
///
336+
/// - Throws: Any error preventing the stream from being opened.
337+
///
338+
/// The resulting file handle takes ownership of `handle` and closes it when
339+
/// it is deinitialized or if an error is thrown from this initializer.
340+
init(unsafeWindowsHANDLE handle: consuming HANDLE, options: OpenOptions) throws {
341+
var flags: CInt = _O_BINARY
342+
switch (options.contains(.readAccess), options.contains(.writeAccess)) {
343+
case (true, true):
344+
flags |= _O_RDWR
345+
case (true, false):
346+
flags |= _O_RDONLY
347+
case (false, true):
348+
flags |= _O_WRONLY
349+
case (false, false):
350+
break
351+
}
352+
if !options.contains(.inheritedByChild) {
353+
flags |= _O_NOINHERIT
354+
}
355+
356+
let fd = _open_osfhandle(Int(bitPattern: handle), flags)
357+
guard fd >= 0 else {
358+
let errorCode = swt_errno()
359+
_ = CloseHandle(handle)
360+
throw CError(rawValue: errorCode)
361+
}
362+
363+
try self.init(unsafePOSIXFileDescriptor: fd, options: options)
364+
}
365+
329366
/// Call a function and pass the underlying Windows file handle to it.
330367
///
331368
/// - Parameters:
@@ -339,7 +376,7 @@ struct FileHandle: ~Copyable, Sendable {
339376
/// that require the file's `HANDLE` representation instead of the standard
340377
/// `FILE *` representation. If the file handle cannot be converted to a
341378
/// Windows handle, `nil` is passed to `body`.
342-
borrowing func withUnsafeWindowsHANDLE<R>(_ body: (HANDLE?) throws -> R) rethrows -> R {
379+
borrowing func withUnsafeWindowsHANDLE<R>(_ body: (borrowing HANDLE?) throws -> R) rethrows -> R {
343380
try withUnsafePOSIXFileDescriptor { fd in
344381
guard let fd else {
345382
return try body(nil)

Tests/TestingTests/Support/FileHandleTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,25 @@ struct FileHandleTests {
5454
#endif
5555

5656
#if os(Windows)
57+
@Test("Can init from Windows file HANDLE")
58+
func initFromFileHANDLE() throws {
59+
let windowsHANDLE = try #require(
60+
CreateFileA(
61+
"NUL",
62+
GENERIC_READ,
63+
DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE),
64+
nil,
65+
DWORD(OPEN_ALWAYS),
66+
DWORD(FILE_ATTRIBUTE_NORMAL),
67+
nil
68+
)
69+
)
70+
let fileHandle = try FileHandle(unsafeWindowsHANDLE: windowsHANDLE, options: [.readAccess])
71+
try fileHandle.withUnsafeWindowsHANDLE { ownedHANDLE in
72+
#expect(windowsHANDLE == ownedHANDLE)
73+
}
74+
}
75+
5776
@Test("Can get Windows file HANDLE")
5877
func fileHANDLE() throws {
5978
let fileHandle = try FileHandle.temporary()

0 commit comments

Comments
 (0)