go/src/bytes/buffer_test.go

731 lines
18 KiB
Go
Raw Normal View History

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package bytes_test
import (
. "bytes"
"fmt"
"io"
"math/rand"
"strconv"
"testing"
"unicode/utf8"
)
const N = 10000 // make this bigger for a larger (and slower) test
var testString string // test data for write tests
var testBytes []byte // test data; same as testString but as a slice.
type negativeReader struct{}
func (r *negativeReader) Read([]byte) (int, error) { return -1, nil }
func init() {
testBytes = make([]byte, N)
for i := 0; i < N; i++ {
testBytes[i] = 'a' + byte(i%26)
}
testString = string(testBytes)
}
// Verify that contents of buf match the string s.
func check(t *testing.T, testname string, buf *Buffer, s string) {
bytes := buf.Bytes()
str := buf.String()
if buf.Len() != len(bytes) {
t.Errorf("%s: buf.Len() == %d, len(buf.Bytes()) == %d", testname, buf.Len(), len(bytes))
}
if buf.Len() != len(str) {
t.Errorf("%s: buf.Len() == %d, len(buf.String()) == %d", testname, buf.Len(), len(str))
}
if buf.Len() != len(s) {
t.Errorf("%s: buf.Len() == %d, len(s) == %d", testname, buf.Len(), len(s))
}
if string(bytes) != s {
t.Errorf("%s: string(buf.Bytes()) == %q, s == %q", testname, string(bytes), s)
}
}
// Fill buf through n writes of string fus.
// The initial contents of buf corresponds to the string s;
// the result is the final contents of buf returned as a string.
func fillString(t *testing.T, testname string, buf *Buffer, s string, n int, fus string) string {
check(t, testname+" (fill 1)", buf, s)
for ; n > 0; n-- {
m, err := buf.WriteString(fus)
if m != len(fus) {
t.Errorf(testname+" (fill 2): m == %d, expected %d", m, len(fus))
}
if err != nil {
t.Errorf(testname+" (fill 3): err should always be nil, found err == %s", err)
}
s += fus
check(t, testname+" (fill 4)", buf, s)
}
return s
}
// Fill buf through n writes of byte slice fub.
// The initial contents of buf corresponds to the string s;
// the result is the final contents of buf returned as a string.
func fillBytes(t *testing.T, testname string, buf *Buffer, s string, n int, fub []byte) string {
check(t, testname+" (fill 1)", buf, s)
for ; n > 0; n-- {
m, err := buf.Write(fub)
if m != len(fub) {
t.Errorf(testname+" (fill 2): m == %d, expected %d", m, len(fub))
}
if err != nil {
t.Errorf(testname+" (fill 3): err should always be nil, found err == %s", err)
}
s += string(fub)
check(t, testname+" (fill 4)", buf, s)
}
return s
}
func TestNewBuffer(t *testing.T) {
buf := NewBuffer(testBytes)
check(t, "NewBuffer", buf, testString)
}
func TestNewBufferString(t *testing.T) {
buf := NewBufferString(testString)
check(t, "NewBufferString", buf, testString)
}
// Empty buf through repeated reads into fub.
// The initial contents of buf corresponds to the string s.
func empty(t *testing.T, testname string, buf *Buffer, s string, fub []byte) {
check(t, testname+" (empty 1)", buf, s)
for {
n, err := buf.Read(fub)
if n == 0 {
break
}
if err != nil {
t.Errorf(testname+" (empty 2): err should always be nil, found err == %s", err)
}
s = s[n:]
check(t, testname+" (empty 3)", buf, s)
}
check(t, testname+" (empty 4)", buf, "")
}
func TestBasicOperations(t *testing.T) {
var buf Buffer
for i := 0; i < 5; i++ {
check(t, "TestBasicOperations (1)", &buf, "")
buf.Reset()
check(t, "TestBasicOperations (2)", &buf, "")
buf.Truncate(0)
check(t, "TestBasicOperations (3)", &buf, "")
n, err := buf.Write(testBytes[0:1])
if want := 1; err != nil || n != want {
t.Errorf("Write: got (%d, %v), want (%d, %v)", n, err, want, nil)
}
check(t, "TestBasicOperations (4)", &buf, "a")
buf.WriteByte(testString[1])
check(t, "TestBasicOperations (5)", &buf, "ab")
n, err = buf.Write(testBytes[2:26])
if want := 24; err != nil || n != want {
t.Errorf("Write: got (%d, %v), want (%d, %v)", n, err, want, nil)
}
check(t, "TestBasicOperations (6)", &buf, testString[0:26])
buf.Truncate(26)
check(t, "TestBasicOperations (7)", &buf, testString[0:26])
buf.Truncate(20)
check(t, "TestBasicOperations (8)", &buf, testString[0:20])
empty(t, "TestBasicOperations (9)", &buf, testString[0:20], make([]byte, 5))
empty(t, "TestBasicOperations (10)", &buf, "", make([]byte, 100))
buf.WriteByte(testString[1])
c, err := buf.ReadByte()
if want := testString[1]; err != nil || c != want {
t.Errorf("ReadByte: got (%q, %v), want (%q, %v)", c, err, want, nil)
}
c, err = buf.ReadByte()
if err != io.EOF {
t.Errorf("ReadByte: got (%q, %v), want (%q, %v)", c, err, byte(0), io.EOF)
}
}
}
func TestLargeStringWrites(t *testing.T) {
var buf Buffer
limit := 30
if testing.Short() {
limit = 9
}
for i := 3; i < limit; i += 3 {
s := fillString(t, "TestLargeWrites (1)", &buf, "", 5, testString)
empty(t, "TestLargeStringWrites (2)", &buf, s, make([]byte, len(testString)/i))
}
check(t, "TestLargeStringWrites (3)", &buf, "")
}
func TestLargeByteWrites(t *testing.T) {
var buf Buffer
limit := 30
if testing.Short() {
limit = 9
}
for i := 3; i < limit; i += 3 {
s := fillBytes(t, "TestLargeWrites (1)", &buf, "", 5, testBytes)
empty(t, "TestLargeByteWrites (2)", &buf, s, make([]byte, len(testString)/i))
}
check(t, "TestLargeByteWrites (3)", &buf, "")
}
func TestLargeStringReads(t *testing.T) {
var buf Buffer
for i := 3; i < 30; i += 3 {
s := fillString(t, "TestLargeReads (1)", &buf, "", 5, testString[0:len(testString)/i])
empty(t, "TestLargeReads (2)", &buf, s, make([]byte, len(testString)))
}
check(t, "TestLargeStringReads (3)", &buf, "")
}
func TestLargeByteReads(t *testing.T) {
var buf Buffer
for i := 3; i < 30; i += 3 {
s := fillBytes(t, "TestLargeReads (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
empty(t, "TestLargeReads (2)", &buf, s, make([]byte, len(testString)))
}
check(t, "TestLargeByteReads (3)", &buf, "")
}
func TestMixedReadsAndWrites(t *testing.T) {
var buf Buffer
s := ""
for i := 0; i < 50; i++ {
wlen := rand.Intn(len(testString))
if i%2 == 0 {
s = fillString(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testString[0:wlen])
} else {
s = fillBytes(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testBytes[0:wlen])
}
rlen := rand.Intn(len(testString))
fub := make([]byte, rlen)
n, _ := buf.Read(fub)
s = s[n:]
}
empty(t, "TestMixedReadsAndWrites (2)", &buf, s, make([]byte, buf.Len()))
}
func TestCapWithPreallocatedSlice(t *testing.T) {
buf := NewBuffer(make([]byte, 10))
n := buf.Cap()
if n != 10 {
t.Errorf("expected 10, got %d", n)
}
}
func TestCapWithSliceAndWrittenData(t *testing.T) {
buf := NewBuffer(make([]byte, 0, 10))
buf.Write([]byte("test"))
n := buf.Cap()
if n != 10 {
t.Errorf("expected 10, got %d", n)
}
}
func TestNil(t *testing.T) {
var b *Buffer
if b.String() != "<nil>" {
t.Errorf("expected <nil>; got %q", b.String())
}
}
func TestReadFrom(t *testing.T) {
var buf Buffer
for i := 3; i < 30; i += 3 {
s := fillBytes(t, "TestReadFrom (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
var b Buffer
b.ReadFrom(&buf)
empty(t, "TestReadFrom (2)", &b, s, make([]byte, len(testString)))
}
}
type panicReader struct{ panic bool }
func (r panicReader) Read(p []byte) (int, error) {
if r.panic {
panic("oops")
}
return 0, io.EOF
}
// Make sure that an empty Buffer remains empty when
// it is "grown" before a Read that panics
func TestReadFromPanicReader(t *testing.T) {
// First verify non-panic behaviour
var buf Buffer
i, err := buf.ReadFrom(panicReader{})
if err != nil {
t.Fatal(err)
}
if i != 0 {
t.Fatalf("unexpected return from bytes.ReadFrom (1): got: %d, want %d", i, 0)
}
check(t, "TestReadFromPanicReader (1)", &buf, "")
// Confirm that when Reader panics, the empty buffer remains empty
var buf2 Buffer
defer func() {
recover()
check(t, "TestReadFromPanicReader (2)", &buf2, "")
}()
buf2.ReadFrom(panicReader{panic: true})
}
func TestReadFromNegativeReader(t *testing.T) {
var b Buffer
defer func() {
switch err := recover().(type) {
case nil:
t.Fatal("bytes.Buffer.ReadFrom didn't panic")
case error:
// this is the error string of errNegativeRead
wantError := "bytes.Buffer: reader returned negative count from Read"
if err.Error() != wantError {
t.Fatalf("recovered panic: got %v, want %v", err.Error(), wantError)
}
default:
t.Fatalf("unexpected panic value: %#v", err)
}
}()
b.ReadFrom(new(negativeReader))
}
func TestWriteTo(t *testing.T) {
var buf Buffer
for i := 3; i < 30; i += 3 {
s := fillBytes(t, "TestWriteTo (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
var b Buffer
buf.WriteTo(&b)
empty(t, "TestWriteTo (2)", &b, s, make([]byte, len(testString)))
}
}
func TestWriteAppend(t *testing.T) {
var got Buffer
var want []byte
for i := 0; i < 1000; i++ {
b := got.AvailableBuffer()
b = strconv.AppendInt(b, int64(i), 10)
want = strconv.AppendInt(want, int64(i), 10)
got.Write(b)
}
if !Equal(got.Bytes(), want) {
t.Fatalf("Bytes() = %q, want %q", got, want)
}
// With a sufficiently sized buffer, there should be no allocations.
n := testing.AllocsPerRun(100, func() {
got.Reset()
for i := 0; i < 1000; i++ {
b := got.AvailableBuffer()
b = strconv.AppendInt(b, int64(i), 10)
got.Write(b)
}
})
if n > 0 {
t.Errorf("allocations occurred while appending")
}
}
func TestRuneIO(t *testing.T) {
const NRune = 1000
// Built a test slice while we write the data
b := make([]byte, utf8.UTFMax*NRune)
var buf Buffer
n := 0
for r := rune(0); r < NRune; r++ {
size := utf8.EncodeRune(b[n:], r)
nbytes, err := buf.WriteRune(r)
if err != nil {
t.Fatalf("WriteRune(%U) error: %s", r, err)
}
if nbytes != size {
t.Fatalf("WriteRune(%U) expected %d, got %d", r, size, nbytes)
}
n += size
}
b = b[0:n]
// Check the resulting bytes
if !Equal(buf.Bytes(), b) {
t.Fatalf("incorrect result from WriteRune: %q not %q", buf.Bytes(), b)
}
p := make([]byte, utf8.UTFMax)
// Read it back with ReadRune
for r := rune(0); r < NRune; r++ {
size := utf8.EncodeRune(p, r)
nr, nbytes, err := buf.ReadRune()
if nr != r || nbytes != size || err != nil {
t.Fatalf("ReadRune(%U) got %U,%d not %U,%d (err=%s)", r, nr, nbytes, r, size, err)
}
}
// Check that UnreadRune works
buf.Reset()
// check at EOF
if err := buf.UnreadRune(); err == nil {
t.Fatal("UnreadRune at EOF: got no error")
}
if _, _, err := buf.ReadRune(); err == nil {
t.Fatal("ReadRune at EOF: got no error")
}
if err := buf.UnreadRune(); err == nil {
t.Fatal("UnreadRune after ReadRune at EOF: got no error")
}
// check not at EOF
buf.Write(b)
for r := rune(0); r < NRune; r++ {
r1, size, _ := buf.ReadRune()
if err := buf.UnreadRune(); err != nil {
t.Fatalf("UnreadRune(%U) got error %q", r, err)
}
r2, nbytes, err := buf.ReadRune()
if r1 != r2 || r1 != r || nbytes != size || err != nil {
t.Fatalf("ReadRune(%U) after UnreadRune got %U,%d not %U,%d (err=%s)", r, r2, nbytes, r, size, err)
}
}
}
func TestWriteInvalidRune(t *testing.T) {
// Invalid runes, including negative ones, should be written as
// utf8.RuneError.
for _, r := range []rune{-1, utf8.MaxRune + 1} {
var buf Buffer
buf.WriteRune(r)
check(t, fmt.Sprintf("TestWriteInvalidRune (%d)", r), &buf, "\uFFFD")
}
}
func TestNext(t *testing.T) {
b := []byte{0, 1, 2, 3, 4}
tmp := make([]byte, 5)
for i := 0; i <= 5; i++ {
for j := i; j <= 5; j++ {
for k := 0; k <= 6; k++ {
// 0 <= i <= j <= 5; 0 <= k <= 6
// Check that if we start with a buffer
// of length j at offset i and ask for
// Next(k), we get the right bytes.
buf := NewBuffer(b[0:j])
n, _ := buf.Read(tmp[0:i])
if n != i {
t.Fatalf("Read %d returned %d", i, n)
}
bb := buf.Next(k)
want := k
if want > j-i {
want = j - i
}
if len(bb) != want {
t.Fatalf("in %d,%d: len(Next(%d)) == %d", i, j, k, len(bb))
}
for l, v := range bb {
if v != byte(l+i) {
t.Fatalf("in %d,%d: Next(%d)[%d] = %d, want %d", i, j, k, l, v, l+i)
}
}
}
}
}
}
var readBytesTests = []struct {
buffer string
delim byte
expected []string
err error
}{
{"", 0, []string{""}, io.EOF},
{"a\x00", 0, []string{"a\x00"}, nil},
{"abbbaaaba", 'b', []string{"ab", "b", "b", "aaab"}, nil},
{"hello\x01world", 1, []string{"hello\x01"}, nil},
{"foo\nbar", 0, []string{"foo\nbar"}, io.EOF},
{"alpha\nbeta\ngamma\n", '\n', []string{"alpha\n", "beta\n", "gamma\n"}, nil},
{"alpha\nbeta\ngamma", '\n', []string{"alpha\n", "beta\n", "gamma"}, io.EOF},
}
func TestReadBytes(t *testing.T) {
for _, test := range readBytesTests {
buf := NewBufferString(test.buffer)
var err error
for _, expected := range test.expected {
var bytes []byte
bytes, err = buf.ReadBytes(test.delim)
if string(bytes) != expected {
t.Errorf("expected %q, got %q", expected, bytes)
}
if err != nil {
break
}
}
if err != test.err {
t.Errorf("expected error %v, got %v", test.err, err)
}
}
}
func TestReadString(t *testing.T) {
for _, test := range readBytesTests {
buf := NewBufferString(test.buffer)
var err error
for _, expected := range test.expected {
var s string
s, err = buf.ReadString(test.delim)
if s != expected {
t.Errorf("expected %q, got %q", expected, s)
}
if err != nil {
break
}
}
if err != test.err {
t.Errorf("expected error %v, got %v", test.err, err)
}
}
}
func BenchmarkReadString(b *testing.B) {
const n = 32 << 10
data := make([]byte, n)
data[n-1] = 'x'
b.SetBytes(int64(n))
for i := 0; i < b.N; i++ {
buf := NewBuffer(data)
_, err := buf.ReadString('x')
if err != nil {
b.Fatal(err)
}
}
}
func TestGrow(t *testing.T) {
x := []byte{'x'}
y := []byte{'y'}
tmp := make([]byte, 72)
for _, growLen := range []int{0, 100, 1000, 10000, 100000} {
for _, startLen := range []int{0, 100, 1000, 10000, 100000} {
xBytes := Repeat(x, startLen)
buf := NewBuffer(xBytes)
// If we read, this affects buf.off, which is good to test.
readBytes, _ := buf.Read(tmp)
yBytes := Repeat(y, growLen)
allocs := testing.AllocsPerRun(100, func() {
buf.Grow(growLen)
buf.Write(yBytes)
})
// Check no allocation occurs in write, as long as we're single-threaded.
if allocs != 0 {
t.Errorf("allocation occurred during write")
}
// Check that buffer has correct data.
if !Equal(buf.Bytes()[0:startLen-readBytes], xBytes[readBytes:]) {
t.Errorf("bad initial data at %d %d", startLen, growLen)
}
if !Equal(buf.Bytes()[startLen-readBytes:startLen-readBytes+growLen], yBytes) {
t.Errorf("bad written data at %d %d", startLen, growLen)
}
}
}
}
func TestGrowOverflow(t *testing.T) {
defer func() {
if err := recover(); err != ErrTooLarge {
t.Errorf("after too-large Grow, recover() = %v; want %v", err, ErrTooLarge)
}
}()
buf := NewBuffer(make([]byte, 1))
const maxInt = int(^uint(0) >> 1)
buf.Grow(maxInt)
}
// Was a bug: used to give EOF reading empty slice at EOF.
func TestReadEmptyAtEOF(t *testing.T) {
b := new(Buffer)
slice := make([]byte, 0)
n, err := b.Read(slice)
if err != nil {
t.Errorf("read error: %v", err)
}
if n != 0 {
t.Errorf("wrong count; got %d want 0", n)
}
}
func TestUnreadByte(t *testing.T) {
b := new(Buffer)
// check at EOF
if err := b.UnreadByte(); err == nil {
t.Fatal("UnreadByte at EOF: got no error")
}
if _, err := b.ReadByte(); err == nil {
t.Fatal("ReadByte at EOF: got no error")
}
if err := b.UnreadByte(); err == nil {
t.Fatal("UnreadByte after ReadByte at EOF: got no error")
}
// check not at EOF
b.WriteString("abcdefghijklmnopqrstuvwxyz")
// after unsuccessful read
if n, err := b.Read(nil); n != 0 || err != nil {
t.Fatalf("Read(nil) = %d,%v; want 0,nil", n, err)
}
if err := b.UnreadByte(); err == nil {
t.Fatal("UnreadByte after Read(nil): got no error")
}
// after successful read
if _, err := b.ReadBytes('m'); err != nil {
t.Fatalf("ReadBytes: %v", err)
}
if err := b.UnreadByte(); err != nil {
t.Fatalf("UnreadByte: %v", err)
}
c, err := b.ReadByte()
if err != nil {
t.Fatalf("ReadByte: %v", err)
}
if c != 'm' {
t.Errorf("ReadByte = %q; want %q", c, 'm')
}
}
// Tests that we occasionally compact. Issue 5154.
func TestBufferGrowth(t *testing.T) {
var b Buffer
buf := make([]byte, 1024)
b.Write(buf[0:1])
var cap0 int
for i := 0; i < 5<<10; i++ {
b.Write(buf)
b.Read(buf)
if i == 0 {
cap0 = b.Cap()
}
}
cap1 := b.Cap()
// (*Buffer).grow allows for 2x capacity slop before sliding,
// so set our error threshold at 3x.
if cap1 > cap0*3 {
t.Errorf("buffer cap = %d; too big (grew from %d)", cap1, cap0)
}
}
func BenchmarkWriteByte(b *testing.B) {
const n = 4 << 10
b.SetBytes(n)
buf := NewBuffer(make([]byte, n))
for i := 0; i < b.N; i++ {
buf.Reset()
for i := 0; i < n; i++ {
buf.WriteByte('x')
}
}
}
func BenchmarkWriteRune(b *testing.B) {
const n = 4 << 10
const r = '☺'
b.SetBytes(int64(n * utf8.RuneLen(r)))
buf := NewBuffer(make([]byte, n*utf8.UTFMax))
for i := 0; i < b.N; i++ {
buf.Reset()
for i := 0; i < n; i++ {
buf.WriteRune(r)
}
}
}
// From Issue 5154.
func BenchmarkBufferNotEmptyWriteRead(b *testing.B) {
buf := make([]byte, 1024)
for i := 0; i < b.N; i++ {
var b Buffer
b.Write(buf[0:1])
for i := 0; i < 5<<10; i++ {
b.Write(buf)
b.Read(buf)
}
}
}
// Check that we don't compact too often. From Issue 5154.
func BenchmarkBufferFullSmallReads(b *testing.B) {
buf := make([]byte, 1024)
for i := 0; i < b.N; i++ {
var b Buffer
b.Write(buf)
for b.Len()+20 < b.Cap() {
b.Write(buf[:10])
}
for i := 0; i < 5<<10; i++ {
b.Read(buf[:1])
b.Write(buf[:1])
}
}
}
bytes: rely on runtime.growslice for growing Rather than naively making a slice of capacity 2*c+n, rely on the append(..., make(...)) pattern to allocate a slice that aligns up to the closest size class. Performance: name old time/op new time/op delta BufferWriteBlock/N4096 3.03µs ± 6% 2.04µs ± 6% -32.60% (p=0.000 n=10+10) BufferWriteBlock/N65536 47.8µs ± 6% 28.1µs ± 2% -41.32% (p=0.000 n=9+8) BufferWriteBlock/N1048576 844µs ± 7% 510µs ± 5% -39.59% (p=0.000 n=8+9) name old alloc/op new alloc/op delta BufferWriteBlock/N4096 12.3kB ± 0% 7.2kB ± 0% -41.67% (p=0.000 n=10+10) BufferWriteBlock/N65536 258kB ± 0% 130kB ± 0% -49.60% (p=0.000 n=10+10) BufferWriteBlock/N1048576 4.19MB ± 0% 2.10MB ± 0% -49.98% (p=0.000 n=10+8) name old allocs/op new allocs/op delta BufferWriteBlock/N4096 3.00 ± 0% 3.00 ± 0% ~ (all equal) BufferWriteBlock/N65536 7.00 ± 0% 7.00 ± 0% ~ (all equal) BufferWriteBlock/N1048576 11.0 ± 0% 11.0 ± 0% ~ (all equal) The performance is faster since the growth rate is capped at 2x, while previously it could grow by amounts potentially much greater than 2x, leading to significant amounts of memory waste and extra copying. Credit goes to Martin Möhrmann for suggesting the append(b, make([]T, n)...) pattern. Fixes #42984 Updates #51462 Change-Id: I7b23f75dddbf53f8b8b93485bb1a1fff9649b96b Reviewed-on: https://go-review.googlesource.com/c/go/+/349994 Trust: Joseph Tsai <joetsai@digital-static.net> Trust: Josh Bleecher Snyder <josharian@gmail.com> Reviewed-by: Bryan Mills <bcmills@google.com> Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
2021-09-14 14:26:11 -07:00
func BenchmarkBufferWriteBlock(b *testing.B) {
block := make([]byte, 1024)
for _, n := range []int{1 << 12, 1 << 16, 1 << 20} {
b.Run(fmt.Sprintf("N%d", n), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var bb Buffer
for bb.Len() < n {
bb.Write(block)
}
}
})
}
}
func BenchmarkBufferAppendNoCopy(b *testing.B) {
var bb Buffer
bb.Grow(16 << 20)
b.SetBytes(int64(bb.Available()))
b.ReportAllocs()
for i := 0; i < b.N; i++ {
bb.Reset()
b := bb.AvailableBuffer()
b = b[:cap(b)] // use max capacity to simulate a large append operation
bb.Write(b) // should be nearly infinitely fast
}
}