diff --git a/Sources/Testing/Support/Additions/CommandLineAdditions.swift b/Sources/Testing/Support/Additions/CommandLineAdditions.swift index 57d9851a8..99f4a1f0f 100644 --- a/Sources/Testing/Support/Additions/CommandLineAdditions.swift +++ b/Sources/Testing/Support/Additions/CommandLineAdditions.swift @@ -56,13 +56,10 @@ extension CommandLine { } } #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 { + guard let executablePath = swt_getExecutablePath().flatMap(String.init(validatingCString:)) else { throw CError(rawValue: ENOEXEC) } - return argv0 + return executablePath #elseif os(Windows) var result: String? #if DEBUG diff --git a/Sources/_TestingInternals/CMakeLists.txt b/Sources/_TestingInternals/CMakeLists.txt index a951c7d4b..6dcd5622d 100644 --- a/Sources/_TestingInternals/CMakeLists.txt +++ b/Sources/_TestingInternals/CMakeLists.txt @@ -12,6 +12,7 @@ include(GitCommit) include(TargetTriple) add_library(_TestingInternals STATIC Discovery.cpp + Stubs.cpp Versions.cpp WillThrow.cpp) target_include_directories(_TestingInternals PUBLIC diff --git a/Sources/_TestingInternals/Stubs.cpp b/Sources/_TestingInternals/Stubs.cpp new file mode 100644 index 000000000..3dea9c109 --- /dev/null +++ b/Sources/_TestingInternals/Stubs.cpp @@ -0,0 +1,54 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023–2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +#include "Stubs.h" + +#if defined(__OpenBSD__) +#include + +#include +#include + +// This function has the `__constructor__` attribute so that it runs as early as +// possible, thus limiting the risk of some other code changing the current +// working directory before we've had a chance to call realpath(). +__attribute__((__constructor__(101))) +static void tryResolvingExecutablePath(void) { + // Call sysctl() to get the argument vector to the process. + int mib[2] = {CTL_VM, VM_PSSTRINGS}; + struct _ps_strings _ps; + size_t len = sizeof(_ps); + if (sysctl(mib, 2, &_ps, &len, NULL, 0) == -1) { + return nullptr; + } + + // Extract the first argument from the argument vector. + struct ps_strings *ps = static_cast(_ps.val); + if (ps->ps_nargvstr < 1) { + return nullptr; + } + const char *executablePath = ps->ps_argvstr[0]; + + // If the first argument looks path-like (because it contains a '/' character) + // then try to resolve it to an absolute path. Otherwise, return it verbatim. + // NOTE: we do not attempt to resolve $PATH-relative paths here because we + // don't expect test executables to be installed into e.g. /usr/local/bin. + if (strchr(executablePath, '/')) { + executablePath = realpath(executablePath, nullptr); + } else { + executablePath = strdup(executablePath); + } + executablePath.store(executablePath); +} + +const char *swt_getExecutablePath(void) { + return executablePath.load(); +} +#endif diff --git a/Sources/_TestingInternals/include/Stubs.h b/Sources/_TestingInternals/include/Stubs.h index 636ea9aff..e20cc38c9 100644 --- a/Sources/_TestingInternals/include/Stubs.h +++ b/Sources/_TestingInternals/include/Stubs.h @@ -126,6 +126,15 @@ static char *_Nullable *_Null_unspecified swt_environ(void) { } #endif +#if defined(__OpenBSD__) +/// Get the executable path. +/// +/// This function is provided on OpenBSD because the platform does not provide +/// an interface for getting the executable path, so we must simulate it in a +/// way that is not supported in Swift. +SWT_EXTERN const char *_Nullable swt_getExecutablePath(void); +#endif + #if !defined(__ANDROID__) #if __has_include() && defined(si_pid) /// Get the value of the `si_pid` field of a `siginfo_t` structure.