From e6def4ed7bb7eea62c01f3b0a63312797427994e Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 7 Oct 2026 00:45:23 +0000 Subject: [PATCH] Separate Go discovery configuration from diagnostics --- docs/internal/gotool.md | 4 ++++ docs/roadmap/remediation.md | 11 +++++++++-- internal/gotool/gotool.go | 12 ++++++------ internal/gotool/gotool_test.go | 36 ++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/docs/internal/gotool.md b/docs/internal/gotool.md index a3b4624..5f7030b 100644 --- a/docs/internal/gotool.md +++ b/docs/internal/gotool.md @@ -20,6 +20,10 @@ and native cgo/C-tool configuration into its settings. This captures values supplied by either the process environment or the persistent Go environment file. +Configuration discovery parses JSON from stdout. Stderr is captured separately +and included in command-failure diagnostics, so a successful query can emit +warnings while supplying valid configuration. + ## Command environments Every Go command receives `GOENV=off`, so removed persistent values cannot diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index 0983128..db543af 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -2,7 +2,7 @@ Date: 2026-10-06. Reviewed source: `55333b0`. -Status: Two findings require remediation. +Status: R1 resolved; R2 requires remediation. ## Scope and assessment @@ -30,7 +30,7 @@ repairs within those decisions. ## R1 — Medium: Go discovery treats successful diagnostics as invalid configuration -Status: Open. +Status: Resolved. Category: Correctness and an accidental environment prerequisite. Introduced by: `0809312`. @@ -67,6 +67,13 @@ the settings. A failing command retains useful stderr and its cause. Existing ambient/persistent flag, host-target, cache, and architecture-tuning regressions continue to pass. These checks add no warning-free configuration requirement. +**Resolution (2026-10-07):** Discovery captures stdout and stderr separately, +parses configuration from stdout, and preserves diagnostic output and subprocess +causes on failure. A subprocess fixture proves successful JSON discovery accepts +stderr diagnostics and preserves host/cache settings; another verifies failure +diagnostics and exit-error identity. The success regression failed before the +fix. Go-environment, candidate-validation, and build regression checks pass. + ## R2 — Medium: Wrapper regression testing repeats the project suite inside itself Status: Open. diff --git a/internal/gotool/gotool.go b/internal/gotool/gotool.go index 51436f7..477a45f 100644 --- a/internal/gotool/gotool.go +++ b/internal/gotool/gotool.go @@ -187,18 +187,18 @@ func discoveryEnvironment() map[string]string { } func discover(ctx context.Context, directory string, environment map[string]string, args ...string) ([]byte, error) { - captured := &combinedWriter{} + var configuration, diagnostics bytes.Buffer err := process.Run(ctx, process.Invocation{ Args: args, Dir: directory, Env: environment, - Stdout: captured, - Stderr: captured, + Stdout: &configuration, + Stderr: &diagnostics, }) - contents := captured.Bytes() + contents := configuration.Bytes() if err != nil { - if len(contents) != 0 { - return contents, fmt.Errorf("%s: %w: %s", strings.Join(args, " "), err, strings.TrimSpace(string(contents))) + if output := strings.TrimSpace(configuration.String() + diagnostics.String()); output != "" { + return contents, fmt.Errorf("%s: %w: %s", strings.Join(args, " "), err, output) } return contents, fmt.Errorf("%s: %w", strings.Join(args, " "), err) } diff --git a/internal/gotool/gotool_test.go b/internal/gotool/gotool_test.go index 1e003d9..fd5a23b 100644 --- a/internal/gotool/gotool_test.go +++ b/internal/gotool/gotool_test.go @@ -47,6 +47,18 @@ func runGoPreparationFixture() int { return 11 } switch os.Getenv("RELEASEKIT_GO_FIXTURE_MODE") { + case "discovery": + settings := make(map[string]string) + for _, key := range os.Args[3:] { + settings[key] = os.Getenv(key) + } + settings["GOHOSTOS"] = runtime.GOOS + settings["GOHOSTARCH"] = runtime.GOARCH + fmt.Fprintln(os.Stderr, "fixture discovery diagnostic") + if err := json.NewEncoder(os.Stdout).Encode(settings); err != nil { + return 12 + } + return 0 case "fail": fmt.Fprintln(os.Stderr, "fixture dependency failure") return 23 @@ -59,6 +71,30 @@ func runGoPreparationFixture() int { } } +func TestDiscoveryAcceptsDiagnosticsWithValidConfiguration(t *testing.T) { + repository, _ := installGoPreparationFixture(t) + t.Setenv("RELEASEKIT_GO_FIXTURE_MODE", "discovery") + cache := filepath.Join(t.TempDir(), "cache") + t.Setenv("GOCACHE", cache) + tool, err := New(context.Background(), repository) + if err != nil { + t.Fatalf("New() rejected successful discovery: %v", err) + } + if tool.HostTarget() != (Target{OS: runtime.GOOS, Arch: runtime.GOARCH}) || tool.settings["GOCACHE"] != cache { + t.Fatalf("discovered host=%v cache=%q", tool.HostTarget(), tool.settings["GOCACHE"]) + } +} + +func TestDiscoveryPreservesFailureDiagnosticsAndCause(t *testing.T) { + repository, _ := installGoPreparationFixture(t) + t.Setenv("RELEASEKIT_GO_FIXTURE_MODE", "fail") + _, err := New(context.Background(), repository) + var exitError *exec.ExitError + if !errors.As(err, &exitError) || exitError.ExitCode() != 23 || !strings.Contains(err.Error(), "fixture dependency failure") { + t.Fatalf("New() error = %v", err) + } +} + func mustGetwd() string { directory, err := os.Getwd() if err != nil {