Skip to content

Commit 2aeaedd

Browse files
committed
test: restore stdout via a deferred finish so a failed capture cannot wedge the run
The stdout capture around the seeded-arm pack restored the real stdout only on the normal return path; a t.Fatal or panic inside the window would leave os.Stdout on an unread pipe and block every later write once the pipe filled, hanging the run silently. Register an idempotent finish func via defer that restores stdout, closes the write end, and joins the reader goroutine on every exit path.
1 parent c76358c commit 2aeaedd

1 file changed

Lines changed: 34 additions & 11 deletions

File tree

integration_test.go

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os/exec"
1313
"path/filepath"
1414
"strings"
15+
"sync"
1516
"testing"
1617

1718
"github.com/tinfoilsh/modelwrap"
@@ -410,35 +411,57 @@ func TestSeededWrapMatchesColdWrap(t *testing.T) {
410411
return ref
411412
}
412413

413-
// packMWPCaptured packs like packMWP but also captures everything the
414-
// packer (and its hf subprocess) writes to stdout, so a missing
415-
// seeding engagement can be attributed below. The output is re-emitted
416-
// to keep the run log intact.
417-
packMWPCaptured := func(work, model string) (string, string) {
414+
// captureStdout redirects os.Stdout until the returned finish func is
415+
// called and returns everything written in between, re-emitting it to
416+
// the real stdout so the run log stays intact. finish is idempotent
417+
// and must be deferred: t.Fatal or a panic inside the capture window
418+
// then restores stdout instead of wedging the test binary once the
419+
// unread pipe fills. Swap and restore happen on this goroutine with
420+
// Pack running synchronously in between, and no writer outlives Pack
421+
// (it waits on its hf subprocess, whose dup of the pipe fd closes with
422+
// it), so the reader always reaches EOF once the write end closes.
423+
captureStdout := func() func() string {
418424
t.Helper()
419425
orig := os.Stdout
420426
r, w, err := os.Pipe()
421427
if err != nil {
422428
t.Fatal(err)
423429
}
424430
os.Stdout = w
425-
captured := make(chan string)
431+
captured := make(chan string, 1)
426432
go func() {
427433
var buf bytes.Buffer
428434
_, _ = io.Copy(&buf, r)
435+
r.Close()
429436
captured <- buf.String()
430437
}()
438+
var out string
439+
var once sync.Once
440+
return func() string {
441+
once.Do(func() {
442+
os.Stdout = orig
443+
w.Close()
444+
out = <-captured
445+
fmt.Print(out)
446+
})
447+
return out
448+
}
449+
}
450+
451+
// packMWPCaptured packs like packMWP but also captures everything the
452+
// packer (and its hf subprocess) writes to stdout, so a missing
453+
// seeding engagement can be attributed below.
454+
packMWPCaptured := func(work, model string) (string, string) {
455+
t.Helper()
456+
finish := captureStdout()
457+
defer finish()
431458
ref, packErr := wrap.Pack(wrap.Options{
432459
Model: model,
433460
CacheDir: filepath.Join(work, "cache"),
434461
OutputDir: filepath.Join(work, "output"),
435462
Verify: true,
436463
})
437-
w.Close()
438-
os.Stdout = orig
439-
out := <-captured
440-
r.Close()
441-
fmt.Print(out)
464+
out := finish()
442465
if packErr != nil {
443466
t.Fatalf("packing %s: %v", model, packErr)
444467
}

0 commit comments

Comments
 (0)