123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- // 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"
- "runtime"
- . "strings"
- "testing"
- "unicode/utf8"
- )
- 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))
- }
- if n := b.Cap(); n < len(got) {
- t.Errorf("Cap: 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) {
- if runtime.Compiler == "gccgo" {
- t.Skip("skip for gccgo until escape analysis improves")
- }
- for _, growLen := range []int{0, 100, 1000, 10000, 100000} {
- p := bytes.Repeat([]byte{'a'}, growLen)
- allocs := testing.AllocsPerRun(100, func() {
- var b Builder
- b.Grow(growLen) // should be only alloc, when growLen > 0
- if b.Cap() < growLen {
- t.Fatalf("growLen=%d: Cap() is lower than growLen", growLen)
- }
- b.Write(p)
- if b.String() != string(p) {
- t.Fatalf("growLen=%d: bad data written after Grow", growLen)
- }
- })
- wantAllocs := 1
- if growLen == 0 {
- wantAllocs = 0
- }
- if g, w := int(allocs), wantAllocs; g != w {
- t.Errorf("growLen=%d: got %d allocs during Write; want %v", growLen, g, w)
- }
- }
- }
- 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 TestBuilderAllocs(t *testing.T) {
- if runtime.Compiler == "gccgo" {
- t.Skip("skip for gccgo until escape analysis improves")
- }
- // Issue 23382; verify that copyCheck doesn't force the
- // Builder to escape and be heap allocated.
- n := testing.AllocsPerRun(10000, func() {
- var b Builder
- b.Grow(5)
- b.WriteString("abcde")
- _ = b.String()
- })
- if n != 1 {
- t.Errorf("Builder allocs = %v; want 1", n)
- }
- }
- func TestBuilderCopyPanic(t *testing.T) {
- tests := []struct {
- name string
- fn func()
- wantPanic bool
- }{
- {
- name: "String",
- wantPanic: false,
- fn: func() {
- var a Builder
- a.WriteByte('x')
- b := a
- _ = b.String() // appease vet
- },
- },
- {
- name: "Len",
- wantPanic: false,
- fn: func() {
- var a Builder
- a.WriteByte('x')
- b := a
- b.Len()
- },
- },
- {
- name: "Cap",
- wantPanic: false,
- fn: func() {
- var a Builder
- a.WriteByte('x')
- b := a
- b.Cap()
- },
- },
- {
- name: "Reset",
- wantPanic: false,
- fn: func() {
- var a Builder
- a.WriteByte('x')
- b := a
- b.Reset()
- b.WriteByte('y')
- },
- },
- {
- name: "Write",
- wantPanic: true,
- fn: func() {
- var a Builder
- a.Write([]byte("x"))
- b := a
- b.Write([]byte("y"))
- },
- },
- {
- name: "WriteByte",
- wantPanic: true,
- fn: func() {
- var a Builder
- a.WriteByte('x')
- b := a
- b.WriteByte('y')
- },
- },
- {
- name: "WriteString",
- wantPanic: true,
- fn: func() {
- var a Builder
- a.WriteString("x")
- b := a
- b.WriteString("y")
- },
- },
- {
- name: "WriteRune",
- wantPanic: true,
- fn: func() {
- var a Builder
- a.WriteRune('x')
- b := a
- b.WriteRune('y')
- },
- },
- {
- name: "Grow",
- wantPanic: true,
- fn: func() {
- var a Builder
- a.Grow(1)
- b := a
- b.Grow(2)
- },
- },
- }
- for _, tt := range tests {
- didPanic := make(chan bool)
- go func() {
- defer func() { didPanic <- recover() != nil }()
- tt.fn()
- }()
- if got := <-didPanic; got != tt.wantPanic {
- t.Errorf("%s: panicked = %v; want %v", tt.name, got, tt.wantPanic)
- }
- }
- }
- func TestBuilderWriteInvalidRune(t *testing.T) {
- // Invalid runes, including negative ones, should be written as
- // utf8.RuneError.
- for _, r := range []rune{-1, utf8.MaxRune + 1} {
- var b Builder
- b.WriteRune(r)
- check(t, &b, "\uFFFD")
- }
- }
- var someBytes = []byte("some bytes sdljlk jsklj3lkjlk djlkjw")
- var sinkS string
- func benchmarkBuilder(b *testing.B, f func(b *testing.B, numWrite int, grow bool)) {
- b.Run("1Write_NoGrow", func(b *testing.B) {
- b.ReportAllocs()
- f(b, 1, false)
- })
- b.Run("3Write_NoGrow", func(b *testing.B) {
- b.ReportAllocs()
- f(b, 3, false)
- })
- b.Run("3Write_Grow", func(b *testing.B) {
- b.ReportAllocs()
- f(b, 3, true)
- })
- }
- func BenchmarkBuildString_Builder(b *testing.B) {
- benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) {
- for i := 0; i < b.N; i++ {
- var buf Builder
- if grow {
- buf.Grow(len(someBytes) * numWrite)
- }
- for i := 0; i < numWrite; i++ {
- buf.Write(someBytes)
- }
- sinkS = buf.String()
- }
- })
- }
- func BenchmarkBuildString_ByteBuffer(b *testing.B) {
- benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) {
- for i := 0; i < b.N; i++ {
- var buf bytes.Buffer
- if grow {
- buf.Grow(len(someBytes) * numWrite)
- }
- for i := 0; i < numWrite; i++ {
- buf.Write(someBytes)
- }
- sinkS = buf.String()
- }
- })
- }
|