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

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