internal/runtime/cgroup: lineReader fuzz test

The original unit test is converted to a fuzz test, to be more confident
on future refactors. All sub-tests are converted to seed corpus.

Change-Id: Id0c167ed47729a00ea0614d17746ddcc284697d3
Reviewed-on: https://go-review.googlesource.com/c/go/+/723581
Auto-Submit: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
胡玮文 2025-11-24 02:29:24 +08:00 committed by Gopher Robot
parent ac3e0ae51a
commit 67851547d8

View file

@ -11,131 +11,134 @@ import (
"testing" "testing"
) )
func TestLineReader(t *testing.T) { type nextLine struct {
type nextLine struct { line string
line string incomplete bool // next call before this line should return incomplete
incomplete bool // next call before this line should return incomplete }
}
complete := func(s string) nextLine {
return nextLine{line: s}
}
incomplete := func(s string) nextLine {
return nextLine{line: s, incomplete: true}
}
const scratchSize = 8 func complete(s string) nextLine {
return nextLine{line: s}
}
func incomplete(s string) nextLine {
return nextLine{line: s, incomplete: true}
}
tests := []struct { const scratchSize = 8
name string
contents string var readerTests = []struct {
want []nextLine name string
}{ contents string
{ want []nextLine
name: "empty", }{
contents: "", {
name: "empty",
contents: "",
},
{
name: "single",
contents: "1234\n",
want: []nextLine{
complete("1234"),
}, },
{ },
name: "single", {
contents: "1234\n", name: "single-incomplete",
want: []nextLine{ contents: "1234",
complete("1234"), want: []nextLine{
}, incomplete("1234"),
}, },
{ },
name: "single-incomplete", {
contents: "1234", name: "single-exact",
want: []nextLine{ contents: "1234567\n",
incomplete("1234"), want: []nextLine{
}, complete("1234567"),
}, },
{ },
name: "single-exact", {
contents: "1234567\n", name: "single-exact-incomplete",
want: []nextLine{ contents: "12345678",
complete("1234567"), want: []nextLine{
}, incomplete("12345678"),
}, },
{ },
name: "single-exact-incomplete", {
contents: "12345678", name: "multi",
want: []nextLine{ contents: `1234
incomplete("12345678"),
},
},
{
name: "multi",
contents: `1234
5678 5678
`, `,
want: []nextLine{ want: []nextLine{
complete("1234"), complete("1234"),
complete("5678"), complete("5678"),
},
}, },
{ },
name: "multi-short", {
contents: `12 name: "multi-short",
contents: `12
34 34
56 56
78 78
`, `,
want: []nextLine{ want: []nextLine{
complete("12"), complete("12"),
complete("34"), complete("34"),
complete("56"), complete("56"),
complete("78"), complete("78"),
},
}, },
{ },
name: "multi-notrailingnewline", {
contents: `1234 name: "multi-notrailingnewline",
contents: `1234
5678`, 5678`,
want: []nextLine{ want: []nextLine{
complete("1234"), complete("1234"),
incomplete("5678"), incomplete("5678"),
},
}, },
{ },
name: "middle-too-long", {
contents: `1234 name: "middle-too-long",
contents: `1234
1234567890 1234567890
5678 5678
`, `,
want: []nextLine{ want: []nextLine{
complete("1234"), complete("1234"),
incomplete("12345678"), incomplete("12345678"),
complete("5678"), complete("5678"),
},
}, },
{ },
// Multiple reads required to find newline. {
name: "middle-way-too-long", // Multiple reads required to find newline.
contents: `1234 name: "middle-way-too-long",
contents: `1234
12345678900000000000000000000000000000000000000000000000000 12345678900000000000000000000000000000000000000000000000000
5678 5678
`, `,
want: []nextLine{ want: []nextLine{
complete("1234"), complete("1234"),
incomplete("12345678"), incomplete("12345678"),
complete("5678"), complete("5678"),
},
}, },
},
}
func readString(contents string) func(fd int, b []byte) (int, uintptr) {
r := strings.NewReader(contents)
return func(fd int, b []byte) (int, uintptr) {
n, err := r.Read(b)
if err != nil && err != io.EOF {
const dummyErrno = 42
return n, dummyErrno
}
return n, 0
} }
}
for _, tc := range tests { func TestLineReader(t *testing.T) {
for _, tc := range readerTests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
r := strings.NewReader(tc.contents)
read := func(fd int, b []byte) (int, uintptr) {
n, err := r.Read(b)
if err != nil && err != io.EOF {
const dummyErrno = 42
return n, dummyErrno
}
return n, 0
}
var scratch [scratchSize]byte var scratch [scratchSize]byte
l := cgroup.NewLineReader(0, scratch[:], read) l := cgroup.NewLineReader(0, scratch[:], readString(tc.contents))
var got []nextLine var got []nextLine
for { for {
@ -168,3 +171,39 @@ func TestLineReader(t *testing.T) {
}) })
} }
} }
func FuzzLineReader(f *testing.F) {
for _, tc := range readerTests {
f.Add(tc.contents)
}
f.Fuzz(func(t *testing.T, input string) {
scratch := make([]byte, scratchSize)
reader := cgroup.NewLineReader(0, scratch, readString(input))
for expected := range strings.Lines(input) {
err := reader.Next()
line := reader.Line()
var expectedErr error
if len(expected) > scratchSize {
expected = expected[:scratchSize]
expectedErr = cgroup.ErrIncompleteLine
} else if expected[len(expected)-1] == '\n' {
expected = expected[:len(expected)-1]
} else {
expectedErr = cgroup.ErrIncompleteLine
}
if err != expectedErr {
t.Fatalf("got err %v, want %v", err, expectedErr)
}
if string(line) != expected {
t.Fatalf("got %q, want %q", string(line), expected)
}
}
err := reader.Next()
if err != cgroup.ErrEOF {
t.Fatalf("got %v, want EOF", err)
}
})
}