Hardened subprocess stdio wiring and failure diagnostics

This commit is contained in:
2026-05-04 12:37:20 -05:00
parent 4550987bc0
commit 4a593f9dcb
5 changed files with 275 additions and 22 deletions

View File

@@ -59,12 +59,17 @@ func TestRunFailureReturnsUsefulError(t *testing.T) {
t.Fatalf("os.Executable() error = %v", err)
}
dir := t.TempDir()
stdoutPath := filepath.Join(dir, "stdout.log")
stderrPath := filepath.Join(dir, "stderr.log")
req := RunRequest{
Executable: exe,
Args: []string{"-test.run=TestSubprocessHelper", "--", "fail"},
EnvOverrides: map[string]string{
"GO_WANT_SUBPROCESS_HELPER": "1",
},
StdoutLogPath: stdoutPath,
StderrLogPath: stderrPath,
}
res, err := Run(context.Background(), req)
@@ -80,6 +85,41 @@ func TestRunFailureReturnsUsefulError(t *testing.T) {
if !strings.Contains(err.Error(), exe) {
t.Fatalf("error = %q, want executable context", err.Error())
}
if !strings.Contains(err.Error(), stdoutPath) || !strings.Contains(err.Error(), stderrPath) {
t.Fatalf("error = %q, want stdout/stderr log paths", err.Error())
}
}
func TestRunFailureRedactsSensitiveTail(t *testing.T) {
exe, err := os.Executable()
if err != nil {
t.Fatalf("os.Executable() error = %v", err)
}
secretValue := "super-secret-value"
dir := t.TempDir()
req := RunRequest{
Executable: exe,
Args: []string{"-test.run=TestSubprocessHelper", "--", "failsecret"},
EnvOverrides: map[string]string{
"GO_WANT_SUBPROCESS_HELPER": "1",
"API_KEY": secretValue,
"SUBPROCESS_HELPER_ENV_KEY": "API_KEY",
},
StdoutLogPath: filepath.Join(dir, "stdout.log"),
StderrLogPath: filepath.Join(dir, "stderr.log"),
}
_, err = Run(context.Background(), req)
if err == nil {
t.Fatal("Run() error = nil, want non-nil")
}
if strings.Contains(err.Error(), secretValue) {
t.Fatalf("error leaked secret value: %q", err.Error())
}
if !strings.Contains(err.Error(), "<redacted>") {
t.Fatalf("error = %q, want redacted stderr tail marker", err.Error())
}
}
func TestRunTimeout(t *testing.T) {
@@ -170,6 +210,43 @@ func TestRunEnvOverridesWinOverInheritedValues(t *testing.T) {
}
}
func TestRunSharedStdoutStderrLogPath(t *testing.T) {
exe, err := os.Executable()
if err != nil {
t.Fatalf("os.Executable() error = %v", err)
}
dir := t.TempDir()
sharedLogPath := filepath.Join(dir, "shared.log")
req := RunRequest{
Executable: exe,
Args: []string{"-test.run=TestSubprocessHelper", "--", "success"},
EnvOverrides: map[string]string{
"GO_WANT_SUBPROCESS_HELPER": "1",
"SUBPROCESS_HELPER_STDOUT": "shared-out",
"SUBPROCESS_HELPER_STDERR": "shared-err",
},
StdoutLogPath: sharedLogPath,
StderrLogPath: sharedLogPath,
}
res, err := Run(context.Background(), req)
if err != nil {
t.Fatalf("Run() error = %v", err)
}
if res.ExitCode != 0 {
t.Fatalf("ExitCode = %d, want 0", res.ExitCode)
}
data, err := os.ReadFile(sharedLogPath)
if err != nil {
t.Fatalf("read shared log: %v", err)
}
text := string(data)
if !strings.Contains(text, "shared-out") || !strings.Contains(text, "shared-err") {
t.Fatalf("shared log = %q, want both stdout and stderr content", text)
}
}
func TestWriteYAMLAtomic(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "config.generated.yml")
@@ -248,6 +325,10 @@ func TestSubprocessHelper(t *testing.T) {
case "fail":
_, _ = os.Stderr.WriteString("intentional failure\n")
os.Exit(3)
case "failsecret":
key := os.Getenv("SUBPROCESS_HELPER_ENV_KEY")
_, _ = os.Stderr.WriteString("secret:" + os.Getenv(key) + "\n")
os.Exit(4)
case "sleep":
time.Sleep(500 * time.Millisecond)
os.Exit(0)