fix(cli): make stream completion idempotent
All checks were successful
Security Scan / security (push) Successful in 20s

This commit is contained in:
Virgil 2026-04-02 05:30:17 +00:00
parent 323f408601
commit e259ce323b
2 changed files with 21 additions and 6 deletions

View file

@ -39,6 +39,7 @@ type Stream struct {
wrap int wrap int
col int // current column position (visible characters) col int // current column position (visible characters)
done chan struct{} done chan struct{}
once sync.Once
mu sync.Mutex mu sync.Mutex
} }
@ -107,12 +108,14 @@ func (s *Stream) WriteFrom(r io.Reader) error {
// Done signals that no more text will arrive. // Done signals that no more text will arrive.
func (s *Stream) Done() { func (s *Stream) Done() {
s.once.Do(func() {
s.mu.Lock() s.mu.Lock()
if s.col > 0 { if s.col > 0 {
fmt.Fprintln(s.out) // ensure trailing newline fmt.Fprintln(s.out) // ensure trailing newline
} }
s.mu.Unlock() s.mu.Unlock()
close(s.done) close(s.done)
})
} }
// Wait blocks until Done is called. // Wait blocks until Done is called.

View file

@ -153,6 +153,18 @@ func TestStream_Good(t *testing.T) {
assert.Equal(t, "text\n", buf.String()) // no double newline assert.Equal(t, "text\n", buf.String()) // no double newline
}) })
t.Run("Done is idempotent", func(t *testing.T) {
var buf bytes.Buffer
s := NewStream(WithStreamOutput(&buf))
s.Write("text")
s.Done()
s.Done()
s.Wait()
assert.Equal(t, "text\n", buf.String())
})
t.Run("word wrap uses visible width", func(t *testing.T) { t.Run("word wrap uses visible width", func(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
s := NewStream(WithWordWrap(4), WithStreamOutput(&buf)) s := NewStream(WithWordWrap(4), WithStreamOutput(&buf))