Skip to content

Commit 3e3223a

Browse files
committed
parser/gotest: Create LimitedLineReader
1 parent 80a51f2 commit 3e3223a

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package reader
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"io"
7+
)
8+
9+
// LimitedLineReader reads lines from an io.Reader object with a configurable
10+
// line size limit. Lines exceeding the limit will be truncated, but read
11+
// completely from the underlying io.Reader.
12+
type LimitedLineReader struct {
13+
r *bufio.Reader
14+
limit int
15+
}
16+
17+
// NewLimitedLineReader returns a LimitedLineReader to read lines from r with a
18+
// maximum line size of limit.
19+
func NewLimitedLineReader(r io.Reader, limit int) *LimitedLineReader {
20+
return &LimitedLineReader{r: bufio.NewReader(r), limit: limit}
21+
}
22+
23+
// ReadLine returns the next line from the underlying reader. The length of the
24+
// line will not exceed the configured limit. ReadLine either returns a line or
25+
// it returns an error, never both.
26+
func (r *LimitedLineReader) ReadLine() (string, error) {
27+
line, isPrefix, err := r.r.ReadLine()
28+
if err != nil {
29+
return "", err
30+
}
31+
32+
if !isPrefix {
33+
return string(line), nil
34+
}
35+
36+
// Line is incomplete, keep reading until we reach the end of the line.
37+
var buf bytes.Buffer
38+
buf.Write(line) // ignore err, always nil
39+
for isPrefix {
40+
line, isPrefix, err = r.r.ReadLine()
41+
if err != nil {
42+
return "", err
43+
}
44+
45+
if buf.Len() >= r.limit {
46+
// Stop writing to buf if we exceed the limit. We continue reading
47+
// however to make sure we consume the entire line.
48+
continue
49+
}
50+
51+
buf.Write(line) // ignore err, always nil
52+
}
53+
54+
if buf.Len() > r.limit {
55+
buf.Truncate(r.limit)
56+
}
57+
return buf.String(), nil
58+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package reader
2+
3+
import (
4+
"bufio"
5+
"io"
6+
"strings"
7+
"testing"
8+
)
9+
10+
const testingLimit = 4 * 1024 * 1024
11+
12+
func TestLimitedLineReader(t *testing.T) {
13+
tests := []struct {
14+
desc string
15+
inputSize int
16+
}{
17+
{"small size", 128},
18+
{"under buf size", 4095},
19+
{"buf size", 4096},
20+
{"multiple of buf size ", 4096 * 2},
21+
{"not multiple of buf size", 10 * 1024},
22+
{"bufio.MaxScanTokenSize", bufio.MaxScanTokenSize},
23+
{"over bufio.MaxScanTokenSize", bufio.MaxScanTokenSize + 1},
24+
{"under limit", testingLimit - 1},
25+
{"at limit", testingLimit},
26+
{"just over limit", testingLimit + 1},
27+
{"over limit", testingLimit + 128},
28+
}
29+
30+
for _, test := range tests {
31+
t.Run(test.desc, func(t *testing.T) {
32+
line1 := string(make([]byte, test.inputSize))
33+
line2 := "other line"
34+
input := strings.NewReader(strings.Join([]string{line1, line2}, "\n"))
35+
r := NewLimitedLineReader(input, testingLimit)
36+
37+
got, err := r.ReadLine()
38+
if err != nil {
39+
t.Fatalf("ReadLine() returned error %v", err)
40+
}
41+
42+
want := line1
43+
if len(line1) > testingLimit {
44+
want = want[:testingLimit]
45+
}
46+
if got != want {
47+
t.Fatalf("ReadLine() returned incorrect line, got len %d want len %d", len(got), len(want))
48+
}
49+
50+
got, err = r.ReadLine()
51+
if err != nil {
52+
t.Fatalf("ReadLine() returned error %v", err)
53+
}
54+
want = line2
55+
if got != want {
56+
t.Fatalf("ReadLine() returned incorrect line, got len %d want len %d", len(got), len(want))
57+
}
58+
59+
got, err = r.ReadLine()
60+
if err != io.EOF {
61+
t.Fatalf("ReadLine() returned unexpected error, got %v want %v\n", err, io.EOF)
62+
}
63+
if got != "" {
64+
t.Fatalf("ReadLine() returned unexpected line, got %v want nothing\n", got)
65+
}
66+
})
67+
}
68+
}

0 commit comments

Comments
 (0)