inspect: flush buffered TUI output in the finalizer · Entire
inspect: flush buffered TUI output in the finalizer
0983a81→main·
dipree·4w ago·4 files·+32 added/-12 removed
For TTY auto-synthesis we buffer dump/verdict output while the alt-screen TUI is active. Fold the buffer flush into the TUI post-run finalizer: it exits the TUI via PostRunComplete, waits for Bubble Tea to tear down, then flushes the buffer.
This preserves the intended output ordering without relying on a later sink to run after the blocking TUI wait, and adds a regression test that the finalizer exits and flushes without hanging.
Sessions
8cb113745bd8View transcript
Changes
4
cmd/entire/cli/review
Mcmd.go+1/-2
Mcmd_test.go+2/-2
Mpostrun_sinks.go+4/-8
Mtui_sink_test.go+25
1312 unmodified lines
1313
1314
1315
1316
1317
1316
1317
1318
1319
1312 unmodified lines
tui.FinalPhaseFinished(err)
},
})
sinks = append(sinks, tuiPostRunCompleteSink{tui: tui})
sinks = append(sinks, bufferFlushSink{buf: postRunOut, out: in.out})
sinks = append(sinks, tuiPostRunCompleteSink{tui: tui, buf: postRunOut, out: in.out})
return sinks
}
sinks = append(sinks, tuiPostRunCompleteSink{tui: tui})
Mcmd/entire/cli/review/cmd.go+1/-2
1185 unmodified lines
1186
1187
1188
1189
1190
1189
1190
1191
1192
1193
1185 unmodified lines
MasterName: testAgentName,
AutoSynthesis: true,
})
if len(sinks) != 5 {
t.Fatalf("len(sinks) = %d, want 5", len(sinks))
if len(sinks) != 4 {
t.Fatalf("len(sinks) = %d, want 4", len(sinks))
}
if _, ok := sinks[0].(*review.TUISink); !ok {
t.Fatalf("sink[0] = %T, want *TUISink", sinks[0])
}
Mcmd/entire/cli/review/cmd_test.go+2/-2
8 unmodified lines
9
10
11
12
13
14
15
16
2 unmodified lines
19
20
21
22
23
24
22
23
24
25
26
27
28
29
25
26
27
28
8 unmodified lines
type tuiPostRunCompleteSink struct {
tui *TUISink
buf *bytes.Buffer
out io.Writer
}
func (s tuiPostRunCompleteSink) AgentEvent(_ string, _ reviewtypes.Event) {}
2 unmodified lines
if s.tui != nil {
s.tui.PostRunComplete()
}
s.flushBuffer()
}
type bufferFlushSink struct {
buf *bytes.Buffer
out io.Writer
}
func (s bufferFlushSink) AgentEvent(_ string, _ reviewtypes.Event) {}
func (s bufferFlushSink) RunFinished(_ reviewtypes.RunSummary) {
func (s tuiPostRunCompleteSink) flushBuffer() {
if s.buf == nil || s.out == nil || s.buf.Len() == 0 {
return
}
Mcmd/entire/cli/review/postrun_sinks.go+4/-8
56 unmodified lines
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
56 unmodified lines
// TestTUISink_WaitBeforeStart_IsNoOp verifies that calling Wait before Start
// returns immediately without blocking.
func TestTUIPostRunCompleteSinkFlushesAfterExit(t *testing.T) {
t.Parallel()
var tuiOut bytes.Buffer
sink := NewTUISink([]string{"agent-a"}, func() {}, &tuiOut, bytes.NewReader(nil))
sink.Start()
sink.RunFinished(reviewtypes.RunSummary{})
var postRunOut bytes.Buffer
postRunBuf := bytes.NewBufferString("final verdict\n")
done := make(chan struct{})
go func() {
tuiPostRunCompleteSink{tui: sink, buf: postRunBuf, out: &postRunOut}.RunFinished(reviewtypes.RunSummary{})
close(done)
}()
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatal("post-run finalizer did not exit the TUI and flush output")
}
if got := postRunOut.String(); got != "final verdict\n" {
t.Fatalf("flushed output = %q, want final verdict", got)
}
}
func TestTUISink_WaitBeforeStart_IsNoOp(t *testing.T) {
t.Parallel()
var buf bytes.Buffer