blob: df557082a797ce6f1a5f5d45e986584ecfb8a9dd [file] [log] [blame]
// Copyright 2017 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 strings_test
import (
"bytes"
"errors"
"io"
"runtime"
. "strings"
"testing"
"testing/iotest"
)
func check(t *testing.T, b *Builder, want string) {
t.Helper()
got := b.String()
if got != want {
t.Errorf("String: got %#q; want %#q", got, want)
return
}
if n := b.Len(); n != len(got) {
t.Errorf("Len: got %d; but len(String()) is %d", n, len(got))
}
}
func TestBuilder(t *testing.T) {
var b Builder
check(t, &b, "")
n, err := b.WriteString("hello")
if err != nil || n != 5 {
t.Errorf("WriteString: got %d,%s; want 5,nil", n, err)
}
check(t, &b, "hello")
if err = b.WriteByte(' '); err != nil {
t.Errorf("WriteByte: %s", err)
}
check(t, &b, "hello ")
n, err = b.WriteString("world")
if err != nil || n != 5 {
t.Errorf("WriteString: got %d,%s; want 5,nil", n, err)
}
check(t, &b, "hello world")
}
func TestBuilderString(t *testing.T) {
var b Builder
b.WriteString("alpha")
check(t, &b, "alpha")
s1 := b.String()
b.WriteString("beta")
check(t, &b, "alphabeta")
s2 := b.String()
b.WriteString("gamma")
check(t, &b, "alphabetagamma")
s3 := b.String()
// Check that subsequent operations didn't change the returned strings.
if want := "alpha"; s1 != want {
t.Errorf("first String result is now %q; want %q", s1, want)
}
if want := "alphabeta"; s2 != want {
t.Errorf("second String result is now %q; want %q", s2, want)
}
if want := "alphabetagamma"; s3 != want {
t.Errorf("third String result is now %q; want %q", s3, want)
}
}
func TestBuilderReset(t *testing.T) {
var b Builder
check(t, &b, "")
b.WriteString("aaa")
s := b.String()
check(t, &b, "aaa")
b.Reset()
check(t, &b, "")
// Ensure that writing after Reset doesn't alter
// previously returned strings.
b.WriteString("bbb")
check(t, &b, "bbb")
if want := "aaa"; s != want {
t.Errorf("previous String result changed after Reset: got %q; want %q", s, want)
}
}
func TestBuilderGrow(t *testing.T) {
for _, growLen := range []int{0, 100, 1000, 10000, 100000} {
var b Builder
b.Grow(growLen)
p := bytes.Repeat([]byte{'a'}, growLen)
allocs := numAllocs(func() { b.Write(p) })
if allocs > 0 {
t.Errorf("growLen=%d: allocation occurred during write", growLen)
}
if b.String() != string(p) {
t.Errorf("growLen=%d: bad data written after Grow", growLen)
}
}
}
func TestBuilderWrite2(t *testing.T) {
const s0 = "hello 世界"
for _, tt := range []struct {
name string
fn func(b *Builder) (int, error)
n int
want string
}{
{
"Write",
func(b *Builder) (int, error) { return b.Write([]byte(s0)) },
len(s0),
s0,
},
{
"WriteRune",
func(b *Builder) (int, error) { return b.WriteRune('a') },
1,
"a",
},
{
"WriteRuneWide",
func(b *Builder) (int, error) { return b.WriteRune('世') },
3,
"世",
},
{
"WriteString",
func(b *Builder) (int, error) { return b.WriteString(s0) },
len(s0),
s0,
},
} {
t.Run(tt.name, func(t *testing.T) {
var b Builder
n, err := tt.fn(&b)
if err != nil {
t.Fatalf("first call: got %s", err)
}
if n != tt.n {
t.Errorf("first call: got n=%d; want %d", n, tt.n)
}
check(t, &b, tt.want)
n, err = tt.fn(&b)
if err != nil {
t.Fatalf("second call: got %s", err)
}
if n != tt.n {
t.Errorf("second call: got n=%d; want %d", n, tt.n)
}
check(t, &b, tt.want+tt.want)
})
}
}
func TestBuilderWriteByte(t *testing.T) {
var b Builder
if err := b.WriteByte('a'); err != nil {
t.Error(err)
}
if err := b.WriteByte(0); err != nil {
t.Error(err)
}
check(t, &b, "a\x00")
}
func TestBuilderReadFrom(t *testing.T) {
for _, tt := range []struct {
name string
fn func(io.Reader) io.Reader
}{
{"Reader", func(r io.Reader) io.Reader { return r }},
{"DataErrReader", iotest.DataErrReader},
{"OneByteReader", iotest.OneByteReader},
} {
t.Run(tt.name, func(t *testing.T) {
var b Builder
r := tt.fn(NewReader("hello"))
n, err := b.ReadFrom(r)
if err != nil {
t.Fatalf("first call: got %s", err)
}
if n != 5 {
t.Errorf("first call: got n=%d; want 5", n)
}
check(t, &b, "hello")
r = tt.fn(NewReader(" world"))
n, err = b.ReadFrom(r)
if err != nil {
t.Fatalf("first call: got %s", err)
}
if n != 6 {
t.Errorf("first call: got n=%d; want 6", n)
}
check(t, &b, "hello world")
})
}
}
var errRead = errors.New("boom")
// errorReader sends reads to the underlying reader
// but returns errRead instead of io.EOF.
type errorReader struct {
r io.Reader
}
func (r errorReader) Read(b []byte) (int, error) {
n, err := r.r.Read(b)
if err == io.EOF {
err = errRead
}
return n, err
}
func TestBuilderReadFromError(t *testing.T) {
var b Builder
r := errorReader{NewReader("hello")}
n, err := b.ReadFrom(r)
if n != 5 {
t.Errorf("got n=%d; want 5", n)
}
if err != errRead {
t.Errorf("got err=%q; want %q", err, errRead)
}
check(t, &b, "hello")
}
type negativeReader struct{}
func (r negativeReader) Read([]byte) (int, error) { return -1, nil }
func TestBuilderReadFromNegativeReader(t *testing.T) {
var b Builder
defer func() {
switch err := recover().(type) {
case nil:
t.Fatal("ReadFrom didn't panic")
case error:
wantErr := "strings.Builder: reader returned negative count from Read"
if err.Error() != wantErr {
t.Fatalf("recovered panic: got %v; want %v", err.Error(), wantErr)
}
default:
t.Fatalf("unexpected panic value: %#v", err)
}
}()
b.ReadFrom(negativeReader{})
}
func TestBuilderAllocs(t *testing.T) {
var b Builder
b.Grow(5)
var s string
allocs := numAllocs(func() {
b.WriteString("hello")
s = b.String()
})
if want := "hello"; s != want {
t.Errorf("String: got %#q; want %#q", s, want)
}
if allocs > 0 {
t.Fatalf("got %d alloc(s); want 0", allocs)
}
}
func numAllocs(fn func()) uint64 {
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
var m1, m2 runtime.MemStats
runtime.ReadMemStats(&m1)
fn()
runtime.ReadMemStats(&m2)
return m2.Mallocs - m1.Mallocs
}