| // 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" | |
| . "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) { | |
| 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) | |
| } | |
| } | |
| // when growLen < 0, should panic | |
| var a Builder | |
| n := -1 | |
| defer func() { | |
| if r := recover(); r == nil { | |
| t.Errorf("a.Grow(%d) should panic()", n) | |
| } | |
| }() | |
| a.Grow(n) | |
| } | |
| 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) { | |
| // 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() | |
| } | |
| }) | |
| } |