summaryrefslogtreecommitdiffstats
path: root/src/strings/builder_test.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/strings/builder_test.go
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/strings/builder_test.go')
-rw-r--r--src/strings/builder_test.go371
1 files changed, 371 insertions, 0 deletions
diff --git a/src/strings/builder_test.go b/src/strings/builder_test.go
new file mode 100644
index 0000000..dbc2c19
--- /dev/null
+++ b/src/strings/builder_test.go
@@ -0,0 +1,371 @@
+// 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()
+ }
+ })
+}