// 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" ) 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)) } } 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} { var b Builder b.Grow(growLen) p := bytes.Repeat([]byte{'a'}, growLen) allocs := numAllocs(func() { b.Write(p) }) if allocs > 0 { t.Errorf("growLen=%d: allocation occurred during write", growLen) } if b.String() != string(p) { t.Errorf("growLen=%d: bad data written after Grow", growLen) } } } 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) { var b Builder b.Grow(5) var s string allocs := numAllocs(func() { b.WriteString("hello") s = b.String() }) if want := "hello"; s != want { t.Errorf("String: got %#q; want %#q", s, want) } if allocs > 0 { t.Fatalf("got %d alloc(s); want 0", allocs) } // 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 numAllocs(fn func()) uint64 { defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) var m1, m2 runtime.MemStats runtime.ReadMemStats(&m1) fn() runtime.ReadMemStats(&m2) return m2.Mallocs - m1.Mallocs } 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: "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) } } }