From f5e879faaa61894ef8d80eb00dd0759f31ed0eaf Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Sat, 4 Jan 2025 12:48:28 -0500 Subject: [PATCH 1/3] Add speculative support for OpenBSD. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenBSD has partial support in the Swift toolchain. This PR adds speculative support for it based on the assumption that what works with FreeBSD will generally also work with OpenBSD. The big differences: 1. We need to include `` instead of ``, 2. `/usr/bin/tar` does not support writing PKZIP files (so we look for the optional `/usr/bin/zip` instead), and 3. OpenBSD has no way to determine the path to the current executable, so we naïvely assume `argv[0]` is correct. Partially resolves #888. --- Documentation/Porting.md | 2 +- Package.swift | 8 ++++---- .../Attachments/Attachment+URL.swift | 15 ++++++++++++++- Sources/Testing/ABI/EntryPoints/EntryPoint.swift | 6 +++--- .../ABI/EntryPoints/SwiftPMEntryPoint.swift | 2 +- .../Testing/Events/Recorder/Event.Symbol.swift | 2 +- Sources/Testing/ExitTests/ExitCondition.swift | 10 ++++++---- Sources/Testing/ExitTests/ExitTest.swift | 10 +++++----- Sources/Testing/ExitTests/SpawnProcess.swift | 8 +++++--- Sources/Testing/ExitTests/WaitFor.swift | 14 ++++++++------ .../Backtrace+Symbolication.swift | 2 +- Sources/Testing/SourceAttribution/Backtrace.swift | 4 ++-- .../Support/Additions/CommandLineAdditions.swift | 10 +++++++++- Sources/Testing/Support/CError.swift | 4 ++-- Sources/Testing/Support/Environment.swift | 6 +++--- Sources/Testing/Support/FileHandle.swift | 12 ++++++------ Sources/Testing/Support/GetSymbol.swift | 6 +++--- Sources/Testing/Support/Locked.swift | 8 ++++---- Sources/Testing/Support/Versions.swift | 2 +- .../Testing/Traits/Tags/Tag.Color+Loading.swift | 4 ++-- Sources/_TestingInternals/Discovery.cpp | 2 +- Sources/_TestingInternals/include/Includes.h | 4 ++++ Sources/_TestingInternals/include/Stubs.h | 2 +- Tests/TestingTests/Support/EnvironmentTests.swift | 2 +- Tests/TestingTests/Support/FileHandleTests.swift | 4 ++-- 25 files changed, 90 insertions(+), 59 deletions(-) diff --git a/Documentation/Porting.md b/Documentation/Porting.md index 90c49bded..39d1d8e88 100644 --- a/Documentation/Porting.md +++ b/Documentation/Porting.md @@ -256,7 +256,7 @@ platform conditional and provide a stub implementation: +++ b/Sources/Testing/Support/FileHandle.swift var isTTY: Bool { - #if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI) + #if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) // ... +#elseif os(Classic) + return false diff --git a/Package.swift b/Package.swift index 13f517976..a72f7086a 100644 --- a/Package.swift +++ b/Package.swift @@ -57,7 +57,7 @@ let package = Package( cxxSettings: .packageSettings, swiftSettings: .packageSettings, linkerSettings: [ - .linkedLibrary("execinfo", .when(platforms: [.custom("freebsd")])) + .linkedLibrary("execinfo", .when(platforms: [.custom("freebsd"), .openbsd])) ] ), .testTarget( @@ -127,7 +127,7 @@ let package = Package( ) // BUG: swift-package-manager-#6367 -#if !os(Windows) && !os(FreeBSD) +#if !os(Windows) && !os(FreeBSD) && !os(OpenBSD) package.targets.append(contentsOf: [ .testTarget( name: "TestingMacrosTests", @@ -156,7 +156,7 @@ extension Array where Element == PackageDescription.SwiftSetting { .define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), .define("SWT_NO_PROCESS_SPAWNING", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), - .define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .windows, .wasi])), + .define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi])), .define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])), .define("SWT_NO_PIPES", .when(platforms: [.wasi])), ] @@ -191,7 +191,7 @@ extension Array where Element == PackageDescription.CXXSetting { result += [ .define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), .define("SWT_NO_PROCESS_SPAWNING", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), - .define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .windows, .wasi])), + .define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi])), .define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])), .define("SWT_NO_PIPES", .when(platforms: [.wasi])), ] diff --git a/Sources/Overlays/_Testing_Foundation/Attachments/Attachment+URL.swift b/Sources/Overlays/_Testing_Foundation/Attachments/Attachment+URL.swift index 9d0a0a5f1..78aef33b9 100644 --- a/Sources/Overlays/_Testing_Foundation/Attachments/Attachment+URL.swift +++ b/Sources/Overlays/_Testing_Foundation/Attachments/Attachment+URL.swift @@ -164,10 +164,23 @@ private func _compressContentsOfDirectory(at directoryURL: URL) async throws -> // // On Linux (which does not have FreeBSD's version of tar(1)), we can use // zip(1) instead. + // + // OpenBSD's tar(1) does not support writing PKZIP archives, and /usr/bin/zip + // tool is an optional install, so we check if it's present before trying to + // execute it. #if os(Linux) let archiverPath = "/usr/bin/zip" #elseif SWT_TARGET_OS_APPLE || os(FreeBSD) let archiverPath = "/usr/bin/tar" +#elseif os(OpenBSD) + let archiverPath = "/usr/bin/zip" + var isDirectory = false + if !FileManager.default.fileExists(atPath: archiverPath, isDirectory: &isDirectory) || isDirectory { + throw CocoaError(.fileNoSuchFile, userInfo: [ + NSLocalizedDescriptionKey: "The 'zip' package is not installed.", + NSFilePathErrorKey: archiverPath + ]) + } #elseif os(Windows) guard let archiverPath = _archiverPath else { throw CocoaError(.fileWriteUnknown, userInfo: [ @@ -187,7 +200,7 @@ private func _compressContentsOfDirectory(at directoryURL: URL) async throws -> let sourcePath = directoryURL.fileSystemPath let destinationPath = temporaryURL.fileSystemPath -#if os(Linux) +#if os(Linux) || os(OpenBSD) // The zip command constructs relative paths from the current working // directory rather than from command-line arguments. process.arguments = [destinationPath, "--recurse-paths", "."] diff --git a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift index ea37326d5..a0a5df2a0 100644 --- a/Sources/Testing/ABI/EntryPoints/EntryPoint.swift +++ b/Sources/Testing/ABI/EntryPoints/EntryPoint.swift @@ -656,7 +656,7 @@ extension Event.ConsoleOutputRecorder.Options { /// Whether or not the system terminal claims to support 16-color ANSI escape /// codes. private static var _terminalSupports16ColorANSIEscapeCodes: Bool { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) if let termVariable = Environment.variable(named: "TERM") { return termVariable != "dumb" } @@ -678,7 +678,7 @@ extension Event.ConsoleOutputRecorder.Options { /// Whether or not the system terminal claims to support 256-color ANSI escape /// codes. private static var _terminalSupports256ColorANSIEscapeCodes: Bool { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) if let termVariable = Environment.variable(named: "TERM") { return strstr(termVariable, "256") != nil } @@ -700,7 +700,7 @@ extension Event.ConsoleOutputRecorder.Options { /// Whether or not the system terminal claims to support true-color ANSI /// escape codes. private static var _terminalSupportsTrueColorANSIEscapeCodes: Bool { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) if let colortermVariable = Environment.variable(named: "COLORTERM") { return strstr(colortermVariable, "truecolor") != nil } diff --git a/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift b/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift index 52e4d2a54..3c72e9f20 100644 --- a/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift +++ b/Sources/Testing/ABI/EntryPoints/SwiftPMEntryPoint.swift @@ -24,7 +24,7 @@ private import _TestingInternals /// /// This constant is not part of the public interface of the testing library. var EXIT_NO_TESTS_FOUND: CInt { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) EX_UNAVAILABLE #elseif os(Windows) CInt(ERROR_NOT_FOUND) diff --git a/Sources/Testing/Events/Recorder/Event.Symbol.swift b/Sources/Testing/Events/Recorder/Event.Symbol.swift index aed2bbc3c..0f50ed95c 100644 --- a/Sources/Testing/Events/Recorder/Event.Symbol.swift +++ b/Sources/Testing/Events/Recorder/Event.Symbol.swift @@ -106,7 +106,7 @@ extension Event.Symbol { /// be used to represent it in text-based output. The value of this property /// is platform-dependent. public var unicodeCharacter: Character { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) switch self { case .default: // Unicode: WHITE DIAMOND diff --git a/Sources/Testing/ExitTests/ExitCondition.swift b/Sources/Testing/ExitTests/ExitCondition.swift index d589b5367..19f884303 100644 --- a/Sources/Testing/ExitTests/ExitCondition.swift +++ b/Sources/Testing/ExitTests/ExitCondition.swift @@ -49,12 +49,13 @@ public enum ExitCondition: Sendable { /// | macOS | [``](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/_Exit.3.html), [``](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/sysexits.3.html) | /// | Linux | [``](https://sourceware.org/glibc/manual/latest/html_node/Exit-Status.html), `` | /// | FreeBSD | [``](https://man.freebsd.org/cgi/man.cgi?exit(3)), [``](https://man.freebsd.org/cgi/man.cgi?sysexits(3)) | + /// | OpenBSD | [``](https://man.openbsd.org/exit.3), [``](https://man.openbsd.org/sysexits.3) | /// | Windows | [``](https://learn.microsoft.com/en-us/cpp/c-runtime-library/exit-success-exit-failure) | /// - /// On macOS, FreeBSD, and Windows, the full exit code reported by the process - /// is yielded to the parent process. Linux and other POSIX-like systems may - /// only reliably report the low unsigned 8 bits (0–255) of the exit - /// code. + /// On macOS, FreeBSD, OpenBSD, and Windows, the full exit code reported by + /// the process is yielded to the parent process. Linux and other POSIX-like + /// systems may only reliably report the low unsigned 8 bits (0–255) of + /// the exit code. case exitCode(_ exitCode: CInt) /// The process terminated with the given signal. @@ -70,6 +71,7 @@ public enum ExitCondition: Sendable { /// | macOS | [``](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/signal.3.html) | /// | Linux | [``](https://sourceware.org/glibc/manual/latest/html_node/Standard-Signals.html) | /// | FreeBSD | [``](https://man.freebsd.org/cgi/man.cgi?signal(3)) | + /// | OpenBSD | [``](https://man.openbsd.org/signal.3) | /// | Windows | [``](https://learn.microsoft.com/en-us/cpp/c-runtime-library/signal-constants) | case signal(_ signal: CInt) } diff --git a/Sources/Testing/ExitTests/ExitTest.swift b/Sources/Testing/ExitTests/ExitTest.swift index 5c3179ae5..2b602a5aa 100644 --- a/Sources/Testing/ExitTests/ExitTest.swift +++ b/Sources/Testing/ExitTests/ExitTest.swift @@ -110,9 +110,9 @@ extension ExitTest { // SEE: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/coredump.c#n610 var rl = rlimit(rlim_cur: 1, rlim_max: 1) _ = setrlimit(CInt(RLIMIT_CORE.rawValue), &rl) -#elseif os(FreeBSD) - // As with Linux, disable the generation core files. FreeBSD does not, as - // far as I can tell, special-case RLIMIT_CORE=1. +#elseif os(FreeBSD) || os(OpenBSD) + // As with Linux, disable the generation core files. The BSDs do not, as far + // as I can tell, special-case RLIMIT_CORE=1. var rl = rlimit(rlim_cur: 0, rlim_max: 0) _ = setrlimit(RLIMIT_CORE, &rl) #elseif os(Windows) @@ -344,7 +344,7 @@ extension ExitTest { } var fd: CInt? -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) fd = CInt(backChannelEnvironmentVariable) #elseif os(Windows) if let handle = UInt(backChannelEnvironmentVariable).flatMap(HANDLE.init(bitPattern:)) { @@ -541,7 +541,7 @@ extension ExitTest { // known environment variable to the corresponding file descriptor // (HANDLE on Windows.) var backChannelEnvironmentVariable: String? -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) backChannelEnvironmentVariable = backChannelWriteEnd.withUnsafePOSIXFileDescriptor { fd in fd.map(String.init(describing:)) } diff --git a/Sources/Testing/ExitTests/SpawnProcess.swift b/Sources/Testing/ExitTests/SpawnProcess.swift index 204fd9bbe..37d08c434 100644 --- a/Sources/Testing/ExitTests/SpawnProcess.swift +++ b/Sources/Testing/ExitTests/SpawnProcess.swift @@ -17,7 +17,7 @@ internal import _TestingInternals /// A platform-specific value identifying a process running on the current /// system. -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) typealias ProcessID = pid_t #elseif os(Windows) typealias ProcessID = HANDLE @@ -62,13 +62,13 @@ func spawnExecutable( ) throws -> ProcessID { // Darwin and Linux differ in their optionality for the posix_spawn types we // use, so use this typealias to paper over the differences. -#if SWT_TARGET_OS_APPLE || os(FreeBSD) +#if SWT_TARGET_OS_APPLE || os(FreeBSD) || os(OpenBSD) typealias P = T? #elseif os(Linux) typealias P = T #endif -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) return try withUnsafeTemporaryAllocation(of: P.self, capacity: 1) { fileActions in let fileActions = fileActions.baseAddress! guard 0 == posix_spawn_file_actions_init(fileActions) else { @@ -156,6 +156,8 @@ func spawnExecutable( // `swt_posix_spawn_file_actions_addclosefrom_np` to guard the // availability of this function. _ = posix_spawn_file_actions_addclosefrom_np(fileActions, highestFD + 1) +#elseif os(OpenBSD) + // OpenBSD does not have any equivalent functionality. #else #warning("Platform-specific implementation missing: cannot close unused file descriptors") #endif diff --git a/Sources/Testing/ExitTests/WaitFor.swift b/Sources/Testing/ExitTests/WaitFor.swift index 668fe8dcb..239b4a4ba 100644 --- a/Sources/Testing/ExitTests/WaitFor.swift +++ b/Sources/Testing/ExitTests/WaitFor.swift @@ -11,7 +11,7 @@ #if !SWT_NO_PROCESS_SPAWNING internal import _TestingInternals -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) /// Block the calling thread, wait for the target process to exit, and return /// a value describing the conditions under which it exited. /// @@ -78,14 +78,14 @@ func wait(for pid: consuming pid_t) async throws -> ExitCondition { return try _blockAndWait(for: pid) } -#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) +#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) /// A mapping of awaited child PIDs to their corresponding Swift continuations. private let _childProcessContinuations = Locked<[pid_t: CheckedContinuation]>() /// A condition variable used to suspend the waiter thread created by /// `_createWaitThread()` when there are no child processes to await. private nonisolated(unsafe) let _waitThreadNoChildrenCondition = { -#if os(FreeBSD) +#if os(FreeBSD) || os(OpenBSD) let result = UnsafeMutablePointer.allocate(capacity: 1) #else let result = UnsafeMutablePointer.allocate(capacity: 1) @@ -136,7 +136,7 @@ private let _createWaitThread: Void = { // Create the thread. It will run immediately; because it runs in an infinite // loop, we aren't worried about detaching or joining it. -#if SWT_TARGET_OS_APPLE || os(FreeBSD) +#if SWT_TARGET_OS_APPLE || os(FreeBSD) || os(OpenBSD) var thread: pthread_t? #else var thread = pthread_t() @@ -147,14 +147,16 @@ private let _createWaitThread: Void = { { _ in // Set the thread name to help with diagnostics. Note that different // platforms support different thread name lengths. See MAXTHREADNAMESIZE - // on Darwin, TASK_COMM_LEN on Linux, and MAXCOMLEN on FreeBSD. We try to - // maximize legibility in the available space. + // on Darwin, TASK_COMM_LEN on Linux, MAXCOMLEN on FreeBSD, and _MAXCOMLEN + // on OpenBSD. We try to maximize legibility in the available space. #if SWT_TARGET_OS_APPLE _ = pthread_setname_np("Swift Testing exit test monitor") #elseif os(Linux) _ = swt_pthread_setname_np(pthread_self(), "SWT ExT monitor") #elseif os(FreeBSD) _ = pthread_set_name_np(pthread_self(), "SWT ex test monitor") +#elseif os(OpenBSD) + _ = pthread_set_name_np(pthread_self(), "SWT exit test monitor") #else #warning("Platform-specific implementation missing: thread naming unavailable") #endif diff --git a/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift b/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift index 833b95231..89fdffce0 100644 --- a/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift +++ b/Sources/Testing/SourceAttribution/Backtrace+Symbolication.swift @@ -71,7 +71,7 @@ extension Backtrace { result[i] = SymbolicatedAddress(address: address, offset: offset, symbolName: symbolName) } } -#elseif os(Linux) || os(FreeBSD) || os(Android) +#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) // Although these platforms have dladdr(), they do not have symbol names // from DWARF binaries by default, only from shared libraries. The standard // library's backtracing functionality has implemented sufficient ELF/DWARF diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index b8301c0a4..a6019860c 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -69,7 +69,7 @@ public struct Backtrace: Sendable { initializedCount = addresses.withMemoryRebound(to: UnsafeMutableRawPointer.self) { addresses in .init(clamping: backtrace(addresses.baseAddress!, .init(clamping: addresses.count))) } -#elseif os(Linux) || os(FreeBSD) +#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) initializedCount = .init(clamping: backtrace(addresses.baseAddress!, .init(clamping: addresses.count))) #elseif os(Windows) initializedCount = Int(clamping: RtlCaptureStackBackTrace(0, ULONG(clamping: addresses.count), addresses.baseAddress!, nil)) @@ -181,7 +181,7 @@ extension Backtrace { /// crash. To avoid said crash, we'll keep a strong reference to the /// object (abandoning memory until the process exits.) /// ([swift-#62985](https://github.com/swiftlang/swift/issues/62985)) -#if os(Windows) || os(FreeBSD) +#if os(Windows) || os(FreeBSD) || os(OpenBSD) nonisolated(unsafe) var errorObject: AnyObject? #else nonisolated(unsafe) weak var errorObject: AnyObject? diff --git a/Sources/Testing/Support/Additions/CommandLineAdditions.swift b/Sources/Testing/Support/Additions/CommandLineAdditions.swift index 762ab7290..0fda59839 100644 --- a/Sources/Testing/Support/Additions/CommandLineAdditions.swift +++ b/Sources/Testing/Support/Additions/CommandLineAdditions.swift @@ -55,6 +55,14 @@ extension CommandLine { return String(cString: buffer.baseAddress!) } } +#elseif os(OpenBSD) + // OpenBSD does not have API to get a path to the running executable. Use + // arguments[0]. We do a basic sniff test for a path-like string, but + // otherwise return argv[0] verbatim. + guard let argv0 = arguments.first, argv0.contains("/") else { + throw CError(rawValue: ENOEXEC) + } + return argv0 #elseif os(Windows) var result: String? #if DEBUG @@ -87,7 +95,7 @@ extension CommandLine { return arguments[0] #else #warning("Platform-specific implementation missing: executable path unavailable") - return "" + throw SystemError(description: "The executable path of the current process could not be determined.") #endif } } diff --git a/Sources/Testing/Support/CError.swift b/Sources/Testing/Support/CError.swift index 47b9d6612..a8462fda4 100644 --- a/Sources/Testing/Support/CError.swift +++ b/Sources/Testing/Support/CError.swift @@ -47,8 +47,8 @@ func strerror(_ errorCode: CInt) -> String { _ = strerror_s(buffer.baseAddress!, buffer.count, errorCode) return strnlen(buffer.baseAddress!, buffer.count) } -#elseif os(FreeBSD) - // FreeBSD's implementation of strerror() is not thread-safe. +#elseif os(FreeBSD) || os(OpenBSD) + // FreeBSD's/OpenBSD's implementation of strerror() is not thread-safe. String(unsafeUninitializedCapacity: 1024) { buffer in _ = strerror_r(errorCode, buffer.baseAddress!, buffer.count) return strnlen(buffer.baseAddress!, buffer.count) diff --git a/Sources/Testing/Support/Environment.swift b/Sources/Testing/Support/Environment.swift index e10505877..ec2ee9c74 100644 --- a/Sources/Testing/Support/Environment.swift +++ b/Sources/Testing/Support/Environment.swift @@ -42,7 +42,7 @@ enum Environment { } } -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) /// Get all environment variables from a POSIX environment block. /// /// - Parameters: @@ -103,7 +103,7 @@ enum Environment { } #endif return _get(fromEnviron: _NSGetEnviron()!.pointee!) -#elseif os(Linux) || os(FreeBSD) || os(Android) +#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) _get(fromEnviron: swt_environ()) #elseif os(WASI) _get(fromEnviron: __wasilibc_get_environ()) @@ -170,7 +170,7 @@ enum Environment { } return nil } -#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI) +#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) getenv(name).flatMap { String(validatingCString: $0) } #elseif os(Windows) name.withCString(encodedAs: UTF16.self) { name in diff --git a/Sources/Testing/Support/FileHandle.swift b/Sources/Testing/Support/FileHandle.swift index c234206f8..2a2bfe967 100644 --- a/Sources/Testing/Support/FileHandle.swift +++ b/Sources/Testing/Support/FileHandle.swift @@ -215,7 +215,7 @@ struct FileHandle: ~Copyable, Sendable { /// descriptor, `nil` is passed to `body`. borrowing func withUnsafePOSIXFileDescriptor(_ body: (CInt?) throws -> R) rethrows -> R { try withUnsafeCFILEHandle { handle in -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) let fd = fileno(handle) #elseif os(Windows) let fd = _fileno(handle) @@ -274,7 +274,7 @@ struct FileHandle: ~Copyable, Sendable { /// other threads. borrowing func withLock(_ body: () throws -> R) rethrows -> R { try withUnsafeCFILEHandle { handle in -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) flockfile(handle) defer { funlockfile(handle) @@ -309,7 +309,7 @@ extension FileHandle { // If possible, reserve enough space in the resulting buffer to contain // the contents of the file being read. var size: Int? -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) withUnsafePOSIXFileDescriptor { fd in var s = stat() if let fd, 0 == fstat(fd, &s) { @@ -505,7 +505,7 @@ extension FileHandle { extension FileHandle { /// Is this file handle a TTY or PTY? var isTTY: Bool { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) // If stderr is a TTY and TERM is set, that's good enough for us. withUnsafePOSIXFileDescriptor { fd in if let fd, 0 != isatty(fd), let term = Environment.variable(named: "TERM"), !term.isEmpty { @@ -532,7 +532,7 @@ extension FileHandle { #if !SWT_NO_PIPES /// Is this file handle a pipe or FIFO? var isPipe: Bool { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) withUnsafePOSIXFileDescriptor { fd in guard let fd else { return false @@ -607,7 +607,7 @@ func fileExists(atPath path: String) -> Bool { /// resolved, the resulting string may differ slightly but refers to the same /// file system object. If the path could not be resolved, returns `nil`. func canonicalizePath(_ path: String) -> String? { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) path.withCString { path in if let resolvedCPath = realpath(path, nil) { defer { diff --git a/Sources/Testing/Support/GetSymbol.swift b/Sources/Testing/Support/GetSymbol.swift index 3d4eb32d8..264bc0daa 100644 --- a/Sources/Testing/Support/GetSymbol.swift +++ b/Sources/Testing/Support/GetSymbol.swift @@ -13,7 +13,7 @@ internal import _TestingInternals #if !SWT_NO_DYNAMIC_LINKING /// The platform-specific type of a loaded image handle. -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) typealias ImageAddress = UnsafeMutableRawPointer #elseif os(Windows) typealias ImageAddress = HMODULE @@ -28,7 +28,7 @@ typealias ImageAddress = Never /// and cannot be imported directly into Swift. As well, `RTLD_DEFAULT` is only /// defined on Linux when `_GNU_SOURCE` is defined, so it is not sufficient to /// declare a wrapper function in the internal module's Stubs.h file. -#if SWT_TARGET_OS_APPLE || os(FreeBSD) +#if SWT_TARGET_OS_APPLE || os(FreeBSD) || os(OpenBSD) private nonisolated(unsafe) let RTLD_DEFAULT = ImageAddress(bitPattern: -2) #elseif os(Android) && _pointerBitWidth(_32) private nonisolated(unsafe) let RTLD_DEFAULT = ImageAddress(bitPattern: UInt(0xFFFFFFFF)) @@ -59,7 +59,7 @@ private nonisolated(unsafe) let RTLD_DEFAULT = ImageAddress(bitPattern: 0) /// calling `EnumProcessModules()` and iterating over the returned handles /// looking for one containing the given function. func symbol(in handle: ImageAddress? = nil, named symbolName: String) -> UnsafeRawPointer? { -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) dlsym(handle ?? RTLD_DEFAULT, symbolName).map(UnsafeRawPointer.init) #elseif os(Windows) symbolName.withCString { symbolName in diff --git a/Sources/Testing/Support/Locked.swift b/Sources/Testing/Support/Locked.swift index 11c4c4c86..d0edbc801 100644 --- a/Sources/Testing/Support/Locked.swift +++ b/Sources/Testing/Support/Locked.swift @@ -38,7 +38,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { /// or `OSAllocatedUnfairLock`. #if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) typealias PlatformLock = pthread_mutex_t -#elseif os(FreeBSD) +#elseif os(FreeBSD) || os(OpenBSD) typealias PlatformLock = pthread_mutex_t? #elseif os(Windows) typealias PlatformLock = SRWLOCK @@ -54,7 +54,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { private final class _Storage: ManagedBuffer { deinit { withUnsafeMutablePointerToElements { lock in -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) _ = pthread_mutex_destroy(lock) #elseif os(Windows) // No deinitialization needed. @@ -73,7 +73,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { init(rawValue: T) { _storage = _Storage.create(minimumCapacity: 1, makingHeaderWith: { _ in rawValue }) _storage.withUnsafeMutablePointerToElements { lock in -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) _ = pthread_mutex_init(lock, nil) #elseif os(Windows) InitializeSRWLock(lock) @@ -103,7 +103,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { /// concurrency tools. nonmutating func withLock(_ body: (inout T) throws -> R) rethrows -> R { try _storage.withUnsafeMutablePointers { rawValue, lock in -#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) _ = pthread_mutex_lock(lock) defer { _ = pthread_mutex_unlock(lock) diff --git a/Sources/Testing/Support/Versions.swift b/Sources/Testing/Support/Versions.swift index 592722486..5e974c6f1 100644 --- a/Sources/Testing/Support/Versions.swift +++ b/Sources/Testing/Support/Versions.swift @@ -31,7 +31,7 @@ let operatingSystemVersion: String = { default: return "\(productVersion) (\(buildNumber))" } -#elseif !SWT_NO_UNAME && (SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD)) +#elseif !SWT_NO_UNAME && (SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD)) var name = utsname() if 0 == uname(&name) { let release = withUnsafeBytes(of: name.release) { release in diff --git a/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift b/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift index 3e3682e6f..2ab35b107 100644 --- a/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift +++ b/Sources/Testing/Traits/Tags/Tag.Color+Loading.swift @@ -11,7 +11,7 @@ private import _TestingInternals #if !SWT_NO_FILE_IO -#if os(macOS) || (os(iOS) && targetEnvironment(macCatalyst)) || os(Linux) || os(FreeBSD) +#if os(macOS) || (os(iOS) && targetEnvironment(macCatalyst)) || os(Linux) || os(FreeBSD) || os(OpenBSD) /// The path to the current user's home directory, if known. private var _homeDirectoryPath: String? { #if SWT_TARGET_OS_APPLE @@ -57,7 +57,7 @@ var swiftTestingDirectoryPath: String? { // The (default) name of the .swift-testing directory. let swiftTestingDirectoryName = ".swift-testing" -#if os(macOS) || (os(iOS) && targetEnvironment(macCatalyst)) || os(Linux) || os(FreeBSD) +#if os(macOS) || (os(iOS) && targetEnvironment(macCatalyst)) || os(Linux) || os(FreeBSD) || os(OpenBSD) if let homeDirectoryPath = _homeDirectoryPath { return appendPathComponent(swiftTestingDirectoryName, to: homeDirectoryPath) } diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp index eac49c45d..baf4ebd90 100644 --- a/Sources/_TestingInternals/Discovery.cpp +++ b/Sources/_TestingInternals/Discovery.cpp @@ -412,7 +412,7 @@ static void enumerateTypeMetadataSections(const SectionEnumerator& body) { } } -#elif defined(__linux__) || defined(__FreeBSD__) || defined(__ANDROID__) +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__ANDROID__) #pragma mark - ELF implementation /// Specifies the address range corresponding to a section. diff --git a/Sources/_TestingInternals/include/Includes.h b/Sources/_TestingInternals/include/Includes.h index 51c02e277..b1f4c7973 100644 --- a/Sources/_TestingInternals/include/Includes.h +++ b/Sources/_TestingInternals/include/Includes.h @@ -133,6 +133,10 @@ #include #endif +#if defined(__OpenBSD__) +#include +#endif + #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #define NOMINMAX diff --git a/Sources/_TestingInternals/include/Stubs.h b/Sources/_TestingInternals/include/Stubs.h index 4e114f751..caeb7c493 100644 --- a/Sources/_TestingInternals/include/Stubs.h +++ b/Sources/_TestingInternals/include/Stubs.h @@ -93,7 +93,7 @@ static DWORD_PTR swt_PROC_THREAD_ATTRIBUTE_HANDLE_LIST(void) { } #endif -#if defined(__linux__) || defined(__FreeBSD__) || defined(__ANDROID__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__ANDROID__) /// The environment block. /// /// By POSIX convention, the environment block variable is declared in client diff --git a/Tests/TestingTests/Support/EnvironmentTests.swift b/Tests/TestingTests/Support/EnvironmentTests.swift index a4fb8ddd9..512ebfe7b 100644 --- a/Tests/TestingTests/Support/EnvironmentTests.swift +++ b/Tests/TestingTests/Support/EnvironmentTests.swift @@ -90,7 +90,7 @@ extension Environment { environment[name] = value } return true -#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI) +#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) if let value { return 0 == setenv(name, value, 1) } diff --git a/Tests/TestingTests/Support/FileHandleTests.swift b/Tests/TestingTests/Support/FileHandleTests.swift index c7f347357..c837ac7cf 100644 --- a/Tests/TestingTests/Support/FileHandleTests.swift +++ b/Tests/TestingTests/Support/FileHandleTests.swift @@ -14,7 +14,7 @@ private import _TestingInternals #if !SWT_NO_FILE_IO // NOTE: we don't run these tests on iOS (etc.) because processes on those // platforms are sandboxed and do not have arbitrary filesystem access. -#if os(macOS) || os(Linux) || os(FreeBSD) || os(Android) || os(Windows) +#if os(macOS) || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(Windows) @Suite("FileHandle Tests") struct FileHandleTests { // FileHandle is non-copyable, so it cannot yet be used as a test parameter. @@ -255,7 +255,7 @@ func temporaryDirectory() throws -> String { } return try #require(Environment.variable(named: "TMPDIR")) } -#elseif os(Linux) || os(FreeBSD) +#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) "/tmp" #elseif os(Android) Environment.variable(named: "TMPDIR") ?? "/data/local/tmp" From a73823245da67b6430844373d85e58c05a83017f Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Sun, 5 Jan 2025 12:16:46 -0500 Subject: [PATCH 2/3] Simulate posix_spawn_file_actions_addclosefrom_np() on OpenBSD --- Sources/Testing/ExitTests/ExitTest.swift | 8 ++++++++ Sources/Testing/ExitTests/SpawnProcess.swift | 13 +++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Sources/Testing/ExitTests/ExitTest.swift b/Sources/Testing/ExitTests/ExitTest.swift index 2b602a5aa..af7981297 100644 --- a/Sources/Testing/ExitTests/ExitTest.swift +++ b/Sources/Testing/ExitTests/ExitTest.swift @@ -152,6 +152,14 @@ extension ExitTest { } #endif +#if os(OpenBSD) + // OpenBSD does not have posix_spawn_file_actions_addclosefrom_np(). + // However, it does have closefrom(2), which we call here as a best effort. + if let from = Environment.variable(named: "SWT_CLOSEFROM").flatMap(CInt.init) { + _ = closefrom(from) + } +#endif + do { try await body() } catch { diff --git a/Sources/Testing/ExitTests/SpawnProcess.swift b/Sources/Testing/ExitTests/SpawnProcess.swift index 37d08c434..fd18aad8a 100644 --- a/Sources/Testing/ExitTests/SpawnProcess.swift +++ b/Sources/Testing/ExitTests/SpawnProcess.swift @@ -105,9 +105,7 @@ func spawnExecutable( } // Forward standard I/O streams and any explicitly added file handles. -#if os(Linux) || os(FreeBSD) - var highestFD = CInt(-1) -#endif + var highestFD = max(STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO) func inherit(_ fileHandle: borrowing FileHandle, as standardFD: CInt? = nil) throws { try fileHandle.withUnsafePOSIXFileDescriptor { fd in guard let fd else { @@ -118,9 +116,8 @@ func spawnExecutable( } else { #if SWT_TARGET_OS_APPLE _ = posix_spawn_file_actions_addinherit_np(fileActions, fd) -#elseif os(Linux) || os(FreeBSD) - highestFD = max(highestFD, fd) #endif + highestFD = max(highestFD, fd) } } } @@ -157,7 +154,11 @@ func spawnExecutable( // availability of this function. _ = posix_spawn_file_actions_addclosefrom_np(fileActions, highestFD + 1) #elseif os(OpenBSD) - // OpenBSD does not have any equivalent functionality. + // OpenBSD does not have posix_spawn_file_actions_addclosefrom_np(). + // However, it does have closefrom(2), which we can call from within the + // spawned child process if we control its execution. + var environment = environment + environment["SWT_CLOSEFROM"] = String(describing: highestFD + 1) #else #warning("Platform-specific implementation missing: cannot close unused file descriptors") #endif From 065d769a6b6cbfb6992219d871a2184e55aaae3d Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 6 Jan 2025 08:28:52 -0500 Subject: [PATCH 3/3] Update path to optional zip tool --- .../_Testing_Foundation/Attachments/Attachment+URL.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Overlays/_Testing_Foundation/Attachments/Attachment+URL.swift b/Sources/Overlays/_Testing_Foundation/Attachments/Attachment+URL.swift index 78aef33b9..305bdd35b 100644 --- a/Sources/Overlays/_Testing_Foundation/Attachments/Attachment+URL.swift +++ b/Sources/Overlays/_Testing_Foundation/Attachments/Attachment+URL.swift @@ -173,7 +173,7 @@ private func _compressContentsOfDirectory(at directoryURL: URL) async throws -> #elseif SWT_TARGET_OS_APPLE || os(FreeBSD) let archiverPath = "/usr/bin/tar" #elseif os(OpenBSD) - let archiverPath = "/usr/bin/zip" + let archiverPath = "/usr/local/bin/zip" var isDirectory = false if !FileManager.default.fileExists(atPath: archiverPath, isDirectory: &isDirectory) || isDirectory { throw CocoaError(.fileNoSuchFile, userInfo: [