Check scanner.Err() after scan loop

This commit is contained in:
Daisuke Maki 2026-02-18 09:15:58 +09:00
parent 77f66d5150
commit 8d5a0f672c
2 changed files with 78 additions and 0 deletions

View file

@ -3,6 +3,7 @@ package peco
import (
"bufio"
"context"
"fmt"
"io"
"sync"
"time"
@ -109,6 +110,12 @@ func (s *Source) Setup(ctx context.Context, state *Peco) {
}
}()
// scanErr captures any I/O error from the scanner goroutine.
// The goroutine writes to scanErr before closing the lines channel,
// and the outer loop reads it after lines is closed, so no mutex
// is needed.
var scanErr error
lines := make(chan string)
go func() {
var scanned int
@ -129,6 +136,7 @@ func (s *Source) Setup(ctx context.Context, state *Peco) {
}
scanned++
}
scanErr = scanner.Err()
}()
state.Hub().SendStatusMsg(ctx, "Waiting for input...", 0)
@ -156,6 +164,10 @@ func (s *Source) Setup(ctx context.Context, state *Peco) {
}
}
if scanErr != nil {
state.Hub().SendStatusMsg(ctx, fmt.Sprintf("Error reading input: %s", scanErr), 0)
}
if pdebug.Enabled {
pdebug.Printf("Read all %d lines from source", readCount)
}

View file

@ -1,12 +1,15 @@
package peco
import (
"errors"
"io"
"strings"
"sync"
"testing"
"time"
"context"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -77,3 +80,66 @@ func TestSource(t *testing.T) {
}
}
}
// errorAfterReader returns data from the underlying reader, then once
// the underlying reader is exhausted it returns a specified error.
type errorAfterReader struct {
io.Reader
err error
hitEOF bool
}
func (r *errorAfterReader) Read(p []byte) (int, error) {
if r.hitEOF {
return 0, r.err
}
n, err := r.Reader.Read(p)
if err == io.EOF {
r.hitEOF = true
if n > 0 {
return n, nil
}
return 0, r.err
}
return n, err
}
func TestSourceScannerErr(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ig := newIDGen()
go ig.Run(ctx)
simulatedErr := errors.New("simulated I/O error")
r := &errorAfterReader{
Reader: strings.NewReader("line1\nline2\n"),
err: simulatedErr,
}
s := NewSource("-", r, false, ig, 0, false, false)
p := New()
rh := &recordingHub{}
p.hub = rh
s.Setup(ctx, p)
// Verify lines read before the error are present
require.Equal(t, 2, s.Size(), "should have read 2 lines before the error")
l0, err := s.LineAt(0)
require.NoError(t, err)
require.Equal(t, "line1", l0.DisplayString())
l1, err := s.LineAt(1)
require.NoError(t, err)
require.Equal(t, "line2", l1.DisplayString())
// Verify the scanner error was reported via SendStatusMsg
msgs := rh.getStatusMsgs()
found := false
for _, msg := range msgs {
if strings.Contains(msg, "simulated I/O error") {
found = true
break
}
}
require.True(t, found, "expected scanner error in status messages, got: %v", msgs)
}