Skip to content

Commit 956bad4

Browse files
authored
Add internal function to get the entire environment block. (#341)
This PR adds `Environment.get()` in place of `ProcessInfo.environment` so that we aren't dependent on Foundation for this functionality. ### 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 32c0a7d commit 956bad4

File tree

5 files changed

+112
-1
lines changed

5 files changed

+112
-1
lines changed

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ extension ExitTest {
273273

274274
// Inherit the environment from the parent process and make any necessary
275275
// platform-specific changes.
276-
var childEnvironment = ProcessInfo.processInfo.environment
276+
var childEnvironment = Environment.get()
277277
#if SWT_TARGET_OS_APPLE
278278
// We need to remove Xcode's environment variables from the child
279279
// environment to avoid accidentally accidentally recursing.

Sources/Testing/Support/Environment.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,82 @@ enum Environment {
2626
static let simulatedEnvironment = Locked<[String: String]>()
2727
#endif
2828

29+
/// Split a string containing an environment variable's name and value into
30+
/// two strings.
31+
///
32+
/// - Parameters:
33+
/// - row: The environment variable, of the form `"KEY=VALUE"`.
34+
///
35+
/// - Returns: The name and value of the environment variable, or `nil` if it
36+
/// could not be parsed.
37+
private static func _splitEnvironmentVariable(_ row: String) -> (key: String, value: String)? {
38+
row.firstIndex(of: "=").map { equalsIndex in
39+
let key = String(row[..<equalsIndex])
40+
let value = String(row[equalsIndex...].dropFirst())
41+
return (key, value)
42+
}
43+
}
44+
45+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
46+
/// Get all environment variables from a POSIX environment block.
47+
///
48+
/// - Parameters:
49+
/// - environ: The environment block, i.e. the global `environ` variable.
50+
///
51+
/// - Returns: A dictionary of environment variables.
52+
private static func _get(fromEnviron environ: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>) -> [String: String] {
53+
var result = [String: String]()
54+
55+
for i in 0... {
56+
guard let rowp = environ[i] else {
57+
break
58+
}
59+
60+
if let row = String(validatingUTF8: rowp),
61+
let (key, value) = _splitEnvironmentVariable(row) {
62+
result[key] = value
63+
}
64+
}
65+
66+
return result
67+
}
68+
#endif
69+
70+
/// Get all environment variables in the current process.
71+
///
72+
/// - Returns: A copy of the current process' environment dictionary.
73+
static func get() -> [String: String] {
74+
#if SWT_NO_ENVIRONMENT_VARIABLES
75+
simulatedEnvironment.rawValue
76+
#elseif SWT_TARGET_OS_APPLE
77+
_get(fromEnviron: _NSGetEnviron()!.pointee!)
78+
#elseif os(Linux)
79+
_get(fromEnviron: swt_environ())
80+
#elseif os(WASI)
81+
_get(fromEnviron: __wasilibc_get_environ())
82+
#elseif os(Windows)
83+
guard let environ = GetEnvironmentStringsW() else {
84+
return [:]
85+
}
86+
defer {
87+
FreeEnvironmentStringsW(environ)
88+
}
89+
90+
var result = [String: String]()
91+
var rowp = environ
92+
while rowp.pointee != 0 {
93+
defer {
94+
rowp += wcslen(rowp) + 1
95+
}
96+
if let row = String.decodeCString(rowp, as: UTF16.self)?.result,
97+
let (key, value) = _splitEnvironmentVariable(row) {
98+
result[key] = value
99+
}
100+
}
101+
return result
102+
#endif
103+
}
104+
29105
/// Get the environment variable with the specified name.
30106
///
31107
/// - Parameters:

Sources/TestingInternals/include/Includes.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@
6767
#include <limits.h>
6868
#endif
6969

70+
#if __has_include(<crt_externs.h>)
71+
#include <crt_externs.h>
72+
#endif
73+
74+
#if __has_include(<wasi/libc-environ.h>)
75+
#include <wasi/libc-environ.h>
76+
#endif
77+
7078
#if defined(_WIN32)
7179
#define WIN32_LEAN_AND_MEAN
7280
#define NOMINMAX

Sources/TestingInternals/include/Stubs.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,22 @@ static LANGID swt_MAKELANGID(int p, int s) {
7474
}
7575
#endif
7676

77+
#if defined(__linux__)
78+
/// The environment block.
79+
///
80+
/// By POSIX convention, the environment block variable is declared in client
81+
/// code rather than in a header.
82+
SWT_EXTERN char *_Nullable *_Null_unspecified environ;
83+
84+
/// Get the environment block.
85+
///
86+
/// This function is provided because directly accessing `environ` from Swift
87+
/// triggers concurrency warnings about accessing shared mutable state.
88+
static char *_Nullable *_Null_unspecified swt_environ(void) {
89+
return environ;
90+
}
91+
#endif
92+
7793
SWT_ASSUME_NONNULL_END
7894

7995
#endif

Tests/TestingTests/Support/EnvironmentTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ private import TestingInternals
1515
struct EnvironmentTests {
1616
var name = "SWT_ENVIRONMENT_VARIABLE_FOR_TESTING"
1717

18+
@Test("Get whole environment block")
19+
func getWholeEnvironment() throws {
20+
let value = "\(UInt64.random(in: 0 ... .max))"
21+
try #require(Environment.setVariable(value, named: name))
22+
defer {
23+
Environment.setVariable(nil, named: name)
24+
}
25+
let env = Environment.get()
26+
#expect(env[name] == value)
27+
}
28+
1829
@Test("Read environment variable")
1930
func readEnvironmentVariable() throws {
2031
let value = "\(UInt64.random(in: 0 ... .max))"

0 commit comments

Comments
 (0)