From c321da64ae98cdaa74173b693959a96b4663a000 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 30 Aug 2024 10:59:27 -0400 Subject: [PATCH 1/2] Special-case `FileHandle(forWritingAtPath: "CONOUT$")` on Windows. This PR special-cases calls to `FileHandle.init(forWritingAtPath:)` when passed `"CONOUT$"` (the reserved "console output" file) on Windows such that it does not open a new file but just returns a copy of `.stdout` instead. This change is necessary because, on Windows, opening and locking `"CONOUT$"` does not lock `stdout` (they're considered entirely different files) and output written to `"CONOUT$"` is wrapped at the console's column limit even though it's being written as binary data. The VSCode Swift plugin authors would like to be able to specify writing the JSON event stream to `"CONOUT$"`, but the observed behaviour is stopping them from doing so. --- Sources/Testing/Support/FileHandle.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Sources/Testing/Support/FileHandle.swift b/Sources/Testing/Support/FileHandle.swift index 453cedcd0..474c8402d 100644 --- a/Sources/Testing/Support/FileHandle.swift +++ b/Sources/Testing/Support/FileHandle.swift @@ -88,6 +88,22 @@ struct FileHandle: ~Copyable, Sendable { /// /// - Throws: Any error preventing the stream from being opened. init(forWritingAtPath path: String) throws { +#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`, + // output will be correctly interleaved with writes to `stdout`. If we don't + // do this, the file will open successfully but will be opened in text mode + // (despite us asking for binary mode), will wrap at the virtual console's + // column limit, and won't share a file lock with the C `stdout` handle. + // + // To our knowledge, this sort of special-casing is not required on + // POSIX-like platforms (i.e. when opening "/dev/stdout"), but it can be + // adapted for use there if some POSIX-like platform does need it. + if path == "CONOUT$" { + self = .stdout + return + } +#endif try self.init(atPath: path, mode: "wb") } From 8c59fcc8d883e2822967313588f5100e09e770e3 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 30 Aug 2024 11:11:53 -0400 Subject: [PATCH 2/2] Move it up to the real fopen() bottleneck --- Sources/Testing/Support/FileHandle.swift | 31 ++++++++++++------------ 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Sources/Testing/Support/FileHandle.swift b/Sources/Testing/Support/FileHandle.swift index 474c8402d..9aca4a671 100644 --- a/Sources/Testing/Support/FileHandle.swift +++ b/Sources/Testing/Support/FileHandle.swift @@ -52,6 +52,21 @@ struct FileHandle: ~Copyable, Sendable { /// - Throws: Any error preventing the stream from being opened. init(atPath path: String, mode: String) throws { #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`, + // output will be correctly interleaved with writes to `stdout`. If we don't + // do this, the file will open successfully but will be opened in text mode + // (even if we ask for binary mode), will wrap at the virtual console's + // column limit, and won't share a file lock with the C `stdout` handle. + // + // To our knowledge, this sort of special-casing is not required on + // POSIX-like platforms (i.e. when opening "/dev/stdout"), but it can be + // adapted for use there if some POSIX-like platform does need it. + if path == "CONOUT$" && mode.contains("w") { + self = .stdout + return + } + // Windows deprecates fopen() as insecure, so call _wfopen_s() instead. let fileHandle = try path.withCString(encodedAs: UTF16.self) { path in try mode.withCString(encodedAs: UTF16.self) { mode in @@ -88,22 +103,6 @@ struct FileHandle: ~Copyable, Sendable { /// /// - Throws: Any error preventing the stream from being opened. init(forWritingAtPath path: String) throws { -#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`, - // output will be correctly interleaved with writes to `stdout`. If we don't - // do this, the file will open successfully but will be opened in text mode - // (despite us asking for binary mode), will wrap at the virtual console's - // column limit, and won't share a file lock with the C `stdout` handle. - // - // To our knowledge, this sort of special-casing is not required on - // POSIX-like platforms (i.e. when opening "/dev/stdout"), but it can be - // adapted for use there if some POSIX-like platform does need it. - if path == "CONOUT$" { - self = .stdout - return - } -#endif try self.init(atPath: path, mode: "wb") }