Transfer completed Go output without copies

This commit is contained in:
2026-10-09 01:44:33 +00:00
parent 39196a525c
commit 50d3573257
7 changed files with 226 additions and 21 deletions

View File

@@ -1,6 +1,7 @@
package gotool
import (
"bytes"
"context"
"encoding/json"
"errors"
@@ -62,6 +63,14 @@ func runGoPreparationFixture() int {
case "fail":
fmt.Fprintln(os.Stderr, "fixture dependency failure")
return 23
case "output":
fmt.Fprintln(os.Stdout, "fixture stdout")
fmt.Fprintln(os.Stderr, "fixture stderr")
return 0
case "large-fail":
_, _ = fmt.Fprint(os.Stdout, strings.Repeat("x", 8<<20))
fmt.Fprintln(os.Stderr, "fixture large failure")
return 23
case "wait":
for {
time.Sleep(time.Second)
@@ -295,7 +304,7 @@ func TestPersistentSettingsPreserveCachesAndNativeCTools(t *testing.T) {
func readSettings(t *testing.T, tool *Tool, mode Mode, target Target, names ...string) map[string]string {
t.Helper()
args := append([]string{"env", "-json"}, names...)
output, err := tool.Run(context.Background(), t.TempDir(), mode, target, nil, args...)
output, err := tool.Output(context.Background(), t.TempDir(), mode, target, nil, args...)
if err != nil {
t.Fatal(err)
}
@@ -306,6 +315,122 @@ func readSettings(t *testing.T, tool *Tool, mode Mode, target Target, names ...s
return settings
}
func TestOutputReturnsAndForwardsCombinedOutput(t *testing.T) {
repository, _ := installGoPreparationFixture(t)
t.Setenv("RELEASEKIT_GO_FIXTURE_MODE", "output")
tool := fixtureTool()
var forwarded bytes.Buffer
output, err := tool.Output(context.Background(), repository, Native, Target{}, &forwarded, "version")
if err != nil {
t.Fatalf("Output() error = %v", err)
}
if !bytes.Contains(output, []byte("fixture stdout")) || !bytes.Contains(output, []byte("fixture stderr")) {
t.Fatalf("combined output = %q", output)
}
if !bytes.Equal(forwarded.Bytes(), output) {
t.Fatalf("forwarded output differs from capture: forwarded %d bytes, captured %d", forwarded.Len(), len(output))
}
}
func TestRunForwardsOutputWithoutReturningCapture(t *testing.T) {
repository, _ := installGoPreparationFixture(t)
t.Setenv("RELEASEKIT_GO_FIXTURE_MODE", "output")
tool := fixtureTool()
var forwarded bytes.Buffer
if err := tool.Run(context.Background(), repository, Native, Target{}, &forwarded, "version"); err != nil {
t.Fatalf("Run() error = %v", err)
}
if !bytes.Contains(forwarded.Bytes(), []byte("fixture stdout")) || !bytes.Contains(forwarded.Bytes(), []byte("fixture stderr")) {
t.Fatalf("forwarded output = %q", forwarded.String())
}
}
func TestRunPreservesFailureDiagnosticsAndCause(t *testing.T) {
repository, _ := installGoPreparationFixture(t)
t.Setenv("RELEASEKIT_GO_FIXTURE_MODE", "fail")
tool := fixtureTool()
err := tool.Run(context.Background(), repository, Native, Target{}, nil, "version")
var exitError *exec.ExitError
if !errors.As(err, &exitError) || exitError.ExitCode() != 23 || !strings.Contains(err.Error(), "fixture dependency failure") {
t.Fatalf("Run() error = %v", err)
}
}
func TestOutputReturnsLargeFailureDiagnosticsAndCause(t *testing.T) {
repository, _ := installGoPreparationFixture(t)
t.Setenv("RELEASEKIT_GO_FIXTURE_MODE", "large-fail")
tool := fixtureTool()
output, err := tool.Output(context.Background(), repository, Native, Target{}, nil, "version")
var exitError *exec.ExitError
wantSize := (8 << 20) + len("fixture large failure\n")
if len(output) != wantSize {
t.Fatalf("captured output = %d bytes, want %d", len(output), wantSize)
}
if !errors.As(err, &exitError) || exitError.ExitCode() != 23 || !strings.Contains(err.Error(), "fixture large failure") {
t.Fatalf("Output() error = %v", err)
}
}
func TestOutputReturnsCancellation(t *testing.T) {
repository, recordPath := installGoPreparationFixture(t)
t.Setenv("RELEASEKIT_GO_FIXTURE_MODE", "wait")
tool := fixtureTool()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
done := make(chan error, 1)
go func() {
_, err := tool.Output(ctx, repository, Native, Target{}, nil, "version")
done <- err
}()
deadline := time.Now().Add(5 * time.Second)
for {
if _, err := os.Stat(recordPath); err == nil {
break
}
if time.Now().After(deadline) {
t.Fatal("Go fixture did not start")
}
time.Sleep(10 * time.Millisecond)
}
cancel()
select {
case err := <-done:
if !errors.Is(err, context.Canceled) {
t.Fatalf("Output() error = %v; want cancellation", err)
}
case <-time.After(5 * time.Second):
t.Fatal("Go fixture did not stop after cancellation")
}
}
func TestOutputPreservesForwardingWriterFailure(t *testing.T) {
repository, _ := installGoPreparationFixture(t)
t.Setenv("RELEASEKIT_GO_FIXTURE_MODE", "output")
tool := fixtureTool()
writerErr := errors.New("fixture output writer failed")
output, err := tool.Output(context.Background(), repository, Native, Target{}, failingOutputWriter{err: writerErr}, "version")
if !errors.Is(err, writerErr) || !strings.Contains(err.Error(), "go version") {
t.Fatalf("Output() error = %v; want wrapped writer error and command", err)
}
if len(output) == 0 {
t.Fatal("Output() returned no captured bytes after forwarding failed")
}
}
type failingOutputWriter struct{ err error }
func (writer failingOutputWriter) Write([]byte) (int, error) { return 0, writer.err }
func fixtureTool() *Tool {
return &Tool{host: Target{OS: runtime.GOOS, Arch: runtime.GOARCH}, settings: map[string]string{}}
}
func clearGoSettings(t *testing.T) {
t.Helper()
for _, key := range []string{