Preserve child working directory in environment overlays

This commit is contained in:
2026-11-02 11:36:40 +00:00
parent 31f9e48219
commit a2d75c462c
4 changed files with 106 additions and 6 deletions

View File

@@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"io"
"os"
"os/exec"
"sort"
"strings"
@@ -151,14 +150,15 @@ func newCommand(ctx context.Context, invocation Invocation) (*exec.Cmd, error) {
command.Dir = invocation.Dir
command.WaitDelay = time.Second
if len(invocation.Env) != 0 || len(invocation.UnsetEnv) != 0 {
command.Env = environmentEntries(invocation.Env, invocation.UnsetEnv)
// Dir contributes PWD on POSIX before explicit overrides are applied.
command.Env = environmentEntries(command.Environ(), invocation.Env, invocation.UnsetEnv)
}
return command, nil
}
func environmentEntries(overlay map[string]string, unset []string) []string {
environment := make(map[string]string, len(os.Environ())+len(overlay))
for _, entry := range os.Environ() {
func environmentEntries(base []string, overlay map[string]string, unset []string) []string {
environment := make(map[string]string, len(base)+len(overlay))
for _, entry := range base {
key, value, ok := strings.Cut(entry, "=")
if ok {
environment[key] = value

View File

@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
@@ -79,6 +80,50 @@ func TestRunCanRemoveInheritedEnvironment(t *testing.T) {
}
}
func TestRunDirectoryEnvironmentPreservesExplicitPWDSelection(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("directory-derived PWD is a POSIX behavior")
}
parent := mustGetwd(t)
t.Setenv("PWD", parent)
workdir := t.TempDir()
for _, test := range []struct {
name, override, want string
unset bool
present bool
}{
{name: "directory", want: workdir, present: true},
{name: "override", override: "selected PWD", want: "selected PWD", present: true},
{name: "removed", unset: true},
{name: "override after removal", unset: true, override: "selected PWD", want: "selected PWD", present: true},
} {
t.Run(test.name, func(t *testing.T) {
invocation := Invocation{
Args: []string{helperExecutable(t), "-test.run=TestProcessHelper", "--", "pwd"},
Dir: workdir,
Env: map[string]string{"RELEASEKIT_PROCESS_HELPER": "1"},
}
if test.override != "" {
invocation.Env["PWD"] = test.override
}
if test.unset {
invocation.UnsetEnv = []string{"PWD"}
}
var stdout bytes.Buffer
invocation.Stdout = &stdout
if err := Run(t.Context(), invocation); err != nil {
t.Fatal(err)
}
if got, want := stdout.String(), fmt.Sprintf("%s|%t", test.want, test.present); got != want {
t.Fatalf("child PWD = %q, want %q", got, want)
}
if os.Getenv("PWD") != parent || mustGetwd(t) != parent {
t.Fatal("child invocation changed the parent's directory or PWD")
}
})
}
}
func TestRunReportsMissingExecutable(t *testing.T) {
err := Run(context.Background(), Invocation{Args: []string{"releasekit-command-that-does-not-exist"}})
if err == nil || !strings.Contains(err.Error(), "releasekit-command-that-does-not-exist") {
@@ -378,6 +423,10 @@ func TestProcessHelper(t *testing.T) {
}
mode := args[len(args)-1]
switch mode {
case "pwd":
value, present := os.LookupEnv("PWD")
fmt.Fprintf(os.Stdout, "%s|%t", value, present)
os.Exit(0)
case "success":
workdir, err := os.Getwd()
if err != nil {