Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/Testing/ExitTests/ExitTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ extension ExitTest {

// Inherit the environment from the parent process and make any necessary
// platform-specific changes.
var childEnvironment = ProcessInfo.processInfo.environment
var childEnvironment = Environment.get()
#if SWT_TARGET_OS_APPLE
// We need to remove Xcode's environment variables from the child
// environment to avoid accidentally accidentally recursing.
Expand Down
76 changes: 76 additions & 0 deletions Sources/Testing/Support/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,82 @@ enum Environment {
static let simulatedEnvironment = Locked<[String: String]>()
#endif

/// Split a string containing an environment variable's name and value into
/// two strings.
///
/// - Parameters:
/// - row: The environment variable, of the form `"KEY=VALUE"`.
///
/// - Returns: The name and value of the environment variable, or `nil` if it
/// could not be parsed.
private static func _splitEnvironmentVariable(_ row: String) -> (key: String, value: String)? {
row.firstIndex(of: "=").map { equalsIndex in
let key = String(row[..<equalsIndex])
let value = String(row[equalsIndex...].dropFirst())
return (key, value)
}
}

#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
/// Get all environment variables from a POSIX environment block.
///
/// - Parameters:
/// - environ: The environment block, i.e. the global `environ` variable.
///
/// - Returns: A dictionary of environment variables.
private static func _get(fromEnviron environ: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>) -> [String: String] {
var result = [String: String]()

for i in 0... {
guard let rowp = environ[i] else {
break
}

if let row = String(validatingUTF8: rowp),
let (key, value) = _splitEnvironmentVariable(row) {
result[key] = value
}
}

return result
}
#endif

/// Get all environment variables in the current process.
///
/// - Returns: A copy of the current process' environment dictionary.
static func get() -> [String: String] {
#if SWT_NO_ENVIRONMENT_VARIABLES
simulatedEnvironment.rawValue
#elseif SWT_TARGET_OS_APPLE
_get(fromEnviron: _NSGetEnviron()!.pointee!)
#elseif os(Linux)
_get(fromEnviron: swt_environ())
#elseif os(WASI)
_get(fromEnviron: __wasilibc_get_environ())
#elseif os(Windows)
guard let environ = GetEnvironmentStringsW() else {
return [:]
}
defer {
FreeEnvironmentStringsW(environ)
}

var result = [String: String]()
var rowp = environ
while rowp.pointee != 0 {
defer {
rowp += wcslen(rowp) + 1
}
if let row = String.decodeCString(rowp, as: UTF16.self)?.result,
let (key, value) = _splitEnvironmentVariable(row) {
result[key] = value
}
}
return result
#endif
}

/// Get the environment variable with the specified name.
///
/// - Parameters:
Expand Down
8 changes: 8 additions & 0 deletions Sources/TestingInternals/include/Includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@
#include <limits.h>
#endif

#if __has_include(<crt_externs.h>)
#include <crt_externs.h>
#endif

#if __has_include(<wasi/libc-environ.h>)
#include <wasi/libc-environ.h>
#endif

#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
Expand Down
16 changes: 16 additions & 0 deletions Sources/TestingInternals/include/Stubs.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ static LANGID swt_MAKELANGID(int p, int s) {
}
#endif

#if defined(__linux__)
/// The environment block.
///
/// By POSIX convention, the environment block variable is declared in client
/// code rather than in a header.
SWT_EXTERN char *_Nullable *_Null_unspecified environ;

/// Get the environment block.
///
/// This function is provided because directly accessing `environ` from Swift
/// triggers concurrency warnings about accessing shared mutable state.
static char *_Nullable *_Null_unspecified swt_environ(void) {
return environ;
}
#endif

SWT_ASSUME_NONNULL_END

#endif
11 changes: 11 additions & 0 deletions Tests/TestingTests/Support/EnvironmentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ private import TestingInternals
struct EnvironmentTests {
var name = "SWT_ENVIRONMENT_VARIABLE_FOR_TESTING"

@Test("Get whole environment block")
func getWholeEnvironment() throws {
let value = "\(UInt64.random(in: 0 ... .max))"
try #require(Environment.setVariable(value, named: name))
defer {
Environment.setVariable(nil, named: name)
}
let env = Environment.get()
#expect(env[name] == value)
}

@Test("Read environment variable")
func readEnvironmentVariable() throws {
let value = "\(UInt64.random(in: 0 ... .max))"
Expand Down