| | |
| | |
| | |
| |
|
| | package strings_test |
| |
|
| | import ( |
| | "bytes" |
| | "internal/asan" |
| | . "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() |
| |
|
| | |
| | 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, "") |
| |
|
| | |
| | |
| | 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} { |
| | if asan.Enabled { |
| | t.Logf("skipping allocs check for growLen %d: extra allocs with -asan; see #70079", growLen) |
| | continue |
| | } |
| | p := bytes.Repeat([]byte{'a'}, growLen) |
| | allocs := testing.AllocsPerRun(100, func() { |
| | var b Builder |
| | b.Grow(growLen) |
| | 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) |
| | } |
| | } |
| | |
| | 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) { |
| | if asan.Enabled { |
| | t.Skip("test allocates more with -asan; see #70079") |
| | } |
| | |
| | |
| | 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() |
| | }, |
| | }, |
| | { |
| | 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) { |
| | |
| | |
| | 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_WriteString(b *testing.B) { |
| | someString := string(someBytes) |
| | 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(someString) * numWrite) |
| | } |
| | for i := 0; i < numWrite; i++ { |
| | buf.WriteString(someString) |
| | } |
| | 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() |
| | } |
| | }) |
| | } |
| |
|
| | func TestBuilderGrowSizeclasses(t *testing.T) { |
| | if asan.Enabled { |
| | t.Skip("test allocates more with -asan; see #70079") |
| | } |
| | s := Repeat("a", 19) |
| | allocs := testing.AllocsPerRun(100, func() { |
| | var b Builder |
| | b.Grow(18) |
| | b.WriteString(s) |
| | _ = b.String() |
| | }) |
| | if allocs > 1 { |
| | t.Fatalf("unexpected amount of allocations: %v, want: 1", allocs) |
| | } |
| | } |
| |
|