diff options
Diffstat (limited to 'src/strings/builder_test.go')
-rw-r--r-- | src/strings/builder_test.go | 371 |
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() + } + }) +} |