Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion internal/submit/watch.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -504,7 +504,16 @@ const displayLineMax = 16 * 1024 * 1024
// non-nil error only on a genuine read failure (network drop, ctx cancel).
func streamDisplayAndParse(r io.Reader, out io.Writer, maxLine int) error {
scanner := bufio.NewScanner(r)
scanner.Buffer(make([]byte, 0, 64*1024), maxLine)
// bufio.Scanner's token cap is max(maxLine, cap(initialBuf)), so the initial
// buffer must never exceed maxLine or it would silently raise the effective
// cap above maxLine. Start at 64 KB (grows on demand) for the common case,
// but clamp it so maxLine stays authoritative — the display path relies on it
// (production passes 16 MB, so the clamp is a no-op there).
initCap := 64 * 1024
if maxLine < initCap {
initCap = maxLine
}
scanner.Buffer(make([]byte, 0, initCap), maxLine)
for scanner.Scan() {
// scanner strips the trailing '\n'; re-add it. errcheck-friendly: the
// write error is discarded because the exit code is the contract.
Expand Down
15 changes: 12 additions & 3 deletions internal/submit/watch_display_test.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,14 +33,23 @@ func (r *errAfterReader) Read(p []byte) (int, error) {
// watch returns a false exit 9 on a healthy run. The drain-past-ErrTooLong must
// keep pulling so the parser still resolves the summary.
func TestStreamDisplayAndParse_DrainsPastOversizedLineSoParserSeesBanner(t *testing.T) {
oversized := strings.Repeat("\rprocessing... ", 500) // ~7 KB, no '\n'
// A tqdm-style progress "line": 500 '\r'-redraws, no '\n', ~7.5 KB.
oversized := strings.Repeat("\rprocessing... ", 500)
// maxLine is authoritative in streamDisplayAndParse (the initial buffer is
// clamped to it), so a small cap genuinely trips bufio.ErrTooLong and fires
// the drain. Guard it: a cap >= the line would make this test vacuously pass
// WITHOUT exercising the drain. Production uses 16 MB.
const maxLine = 4096
if len(oversized) <= maxLine {
t.Fatalf("test setup: oversized line (%d B) must exceed maxLine (%d B) to trip ErrTooLong",
len(oversized), maxLine)
}
stream := strings.NewReader(oversized + realIngestorBanner)
parser := NewSummaryParser()
tee := io.TeeReader(stream, parserWriter{parser: parser})

var out bytes.Buffer
// Tiny cap so the oversized line trips ErrTooLong (production uses 16 MB).
if err := streamDisplayAndParse(tee, &out, 1024); err != nil {
if err := streamDisplayAndParse(tee, &out, maxLine); err != nil {
t.Fatalf("an over-long DISPLAY line must not be fatal; got: %v", err)
}
parser.FlushLine()
Expand Down
Loading