Skip to content

Commit 44922df

Browse files
a7medeviCharlesHu
authored andcommitted
Fix Environment.updating for custom and rawBytes (#199)
1 parent 6b35166 commit 44922df

File tree

2 files changed

+152
-1
lines changed

2 files changed

+152
-1
lines changed

Sources/Subprocess/Configuration.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,32 @@ public struct Environment: Sendable, Hashable {
422422
/// Keys with `nil` values in `newValue` will be removed from existing
423423
/// `Environment` before passing to child process.
424424
public func updating(_ newValue: [Key: String?]) -> Self {
425-
return .init(config: .inherit(newValue))
425+
switch config {
426+
case .inherit(var overrides):
427+
for (key, value) in newValue {
428+
overrides[key] = value
429+
}
430+
return .init(config: .inherit(overrides))
431+
case .custom(var environment):
432+
for (key, value) in newValue {
433+
environment[key] = value
434+
}
435+
return .init(config: .custom(environment))
436+
#if !os(Windows)
437+
case .rawBytes(var rawBytesArray):
438+
let overriddenKeys = newValue.keys.map { Array("\($0)=".utf8) }
439+
rawBytesArray.removeAll {
440+
overriddenKeys.contains(where: $0.starts)
441+
}
442+
443+
for (key, value) in newValue {
444+
if let value {
445+
rawBytesArray.append(Array("\(key)=\(value)\0".utf8))
446+
}
447+
}
448+
return .init(config: .rawBytes(rawBytesArray))
449+
#endif
450+
}
426451
}
427452
/// Use custom environment variables
428453
public static func custom(_ newValue: [Key: String]) -> Self {

Tests/SubprocessTests/IntegrationTests.swift

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,132 @@ extension SubprocessIntegrationTests {
435435
)
436436
#endif
437437
}
438+
439+
@Test func testEnvironmentCustomUpdating() async throws {
440+
#if os(Windows)
441+
let pathValue = ProcessInfo.processInfo.environment["Path"] ?? ProcessInfo.processInfo.environment["PATH"] ?? ProcessInfo.processInfo.environment["path"]
442+
let customPath = "C:\\Custom\\Path"
443+
let setup = TestSetup(
444+
executable: .name("cmd.exe"),
445+
arguments: ["/c", "echo %CUSTOMPATH%"],
446+
environment: .custom([
447+
"Path": try #require(pathValue),
448+
"ComSpec": try #require(ProcessInfo.processInfo.environment["ComSpec"]),
449+
]).updating([
450+
"CUSTOMPATH": customPath
451+
])
452+
)
453+
#else
454+
let customPath = "/custom/path"
455+
let setup = TestSetup(
456+
executable: .path("/bin/sh"),
457+
arguments: ["-c", "printenv CUSTOMPATH"],
458+
environment: .custom([
459+
"PATH": "/bin:/usr/bin"
460+
]).updating([
461+
"CUSTOMPATH": customPath
462+
])
463+
)
464+
#endif
465+
let result = try await _run(
466+
setup,
467+
input: .none,
468+
output: .string(limit: 32),
469+
error: .discarded
470+
)
471+
#expect(result.terminationStatus.isSuccess)
472+
let output = result.standardOutput?
473+
.trimmingNewLineAndQuotes()
474+
#expect(
475+
output == customPath
476+
)
477+
}
478+
479+
@Test func testEnvironmentCustomUpdatingUnsetValue() async throws {
480+
#if os(Windows)
481+
let pathValue = ProcessInfo.processInfo.environment["Path"] ?? ProcessInfo.processInfo.environment["PATH"] ?? ProcessInfo.processInfo.environment["path"]
482+
let setup = TestSetup(
483+
executable: .name("cmd.exe"),
484+
arguments: ["/c", "echo %REMOVEME%"],
485+
environment: .custom([
486+
"Path": try #require(pathValue),
487+
"ComSpec": try #require(ProcessInfo.processInfo.environment["ComSpec"]),
488+
"REMOVEME": "value",
489+
]).updating([
490+
"REMOVEME": nil
491+
])
492+
)
493+
#else
494+
let setup = TestSetup(
495+
executable: .path("/bin/sh"),
496+
arguments: ["-c", "printenv REMOVEME"],
497+
environment: .custom([
498+
"PATH": "/bin:/usr/bin",
499+
"REMOVEME": "value",
500+
]).updating([
501+
"REMOVEME": nil
502+
])
503+
)
504+
#endif
505+
let result = try await _run(
506+
setup,
507+
input: .none,
508+
output: .string(limit: 32),
509+
error: .discarded
510+
)
511+
#if os(Windows)
512+
#expect(result.standardOutput?.trimmingNewLineAndQuotes() == "%REMOVEME%")
513+
#else
514+
#expect(result.terminationStatus == .exited(1))
515+
#endif
516+
}
517+
518+
#if !os(Windows)
519+
@Test func testEnvironmentRawBytesUpdating() async throws {
520+
let customValue = "rawbytes_value"
521+
let setup = TestSetup(
522+
executable: .path("/bin/sh"),
523+
arguments: ["-c", "printenv CUSTOMVAR"],
524+
environment: .custom([
525+
Array("PATH=/bin:/usr/bin\0".utf8)
526+
]).updating([
527+
"CUSTOMVAR": customValue
528+
])
529+
)
530+
let result = try await _run(
531+
setup,
532+
input: .none,
533+
output: .string(limit: 32),
534+
error: .discarded
535+
)
536+
#expect(result.terminationStatus.isSuccess)
537+
let output = result.standardOutput?
538+
.trimmingNewLineAndQuotes()
539+
#expect(
540+
output == customValue
541+
)
542+
}
543+
544+
@Test func testEnvironmentRawBytesUpdatingUnsetValue() async throws {
545+
let setup = TestSetup(
546+
executable: .path("/bin/sh"),
547+
arguments: ["-c", "printenv REMOVEME"],
548+
environment: .custom([
549+
Array("PATH=/bin:/usr/bin\0".utf8),
550+
Array("REMOVEME=value\0".utf8),
551+
]).updating([
552+
"REMOVEME": nil
553+
])
554+
)
555+
let result = try await _run(
556+
setup,
557+
input: .none,
558+
output: .string(limit: 32),
559+
error: .discarded
560+
)
561+
#expect(result.terminationStatus == .exited(1))
562+
}
563+
#endif
438564
}
439565

440566
// MARK: - Working Directory Tests

0 commit comments

Comments
 (0)