1// Copyright 2017 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package strings_test
6
7import (
8	"bytes"
9	. "strings"
10	"testing"
11	"unicode/utf8"
12)
13
14func check(t *testing.T, b *Builder, want string) {
15	t.Helper()
16	got := b.String()
17	if got != want {
18		t.Errorf("String: got %#q; want %#q", got, want)
19		return
20	}
21	if n := b.Len(); n != len(got) {
22		t.Errorf("Len: got %d; but len(String()) is %d", n, len(got))
23	}
24	if n := b.Cap(); n < len(got) {
25		t.Errorf("Cap: got %d; but len(String()) is %d", n, len(got))
26	}
27}
28
29func TestBuilder(t *testing.T) {
30	var b Builder
31	check(t, &b, "")
32	n, err := b.WriteString("hello")
33	if err != nil || n != 5 {
34		t.Errorf("WriteString: got %d,%s; want 5,nil", n, err)
35	}
36	check(t, &b, "hello")
37	if err = b.WriteByte(' '); err != nil {
38		t.Errorf("WriteByte: %s", err)
39	}
40	check(t, &b, "hello ")
41	n, err = b.WriteString("world")
42	if err != nil || n != 5 {
43		t.Errorf("WriteString: got %d,%s; want 5,nil", n, err)
44	}
45	check(t, &b, "hello world")
46}
47
48func TestBuilderString(t *testing.T) {
49	var b Builder
50	b.WriteString("alpha")
51	check(t, &b, "alpha")
52	s1 := b.String()
53	b.WriteString("beta")
54	check(t, &b, "alphabeta")
55	s2 := b.String()
56	b.WriteString("gamma")
57	check(t, &b, "alphabetagamma")
58	s3 := b.String()
59
60	// Check that subsequent operations didn't change the returned strings.
61	if want := "alpha"; s1 != want {
62		t.Errorf("first String result is now %q; want %q", s1, want)
63	}
64	if want := "alphabeta"; s2 != want {
65		t.Errorf("second String result is now %q; want %q", s2, want)
66	}
67	if want := "alphabetagamma"; s3 != want {
68		t.Errorf("third String result is now %q; want %q", s3, want)
69	}
70}
71
72func TestBuilderReset(t *testing.T) {
73	var b Builder
74	check(t, &b, "")
75	b.WriteString("aaa")
76	s := b.String()
77	check(t, &b, "aaa")
78	b.Reset()
79	check(t, &b, "")
80
81	// Ensure that writing after Reset doesn't alter
82	// previously returned strings.
83	b.WriteString("bbb")
84	check(t, &b, "bbb")
85	if want := "aaa"; s != want {
86		t.Errorf("previous String result changed after Reset: got %q; want %q", s, want)
87	}
88}
89
90func TestBuilderGrow(t *testing.T) {
91	for _, growLen := range []int{0, 100, 1000, 10000, 100000} {
92		p := bytes.Repeat([]byte{'a'}, growLen)
93		allocs := testing.AllocsPerRun(100, func() {
94			var b Builder
95			b.Grow(growLen) // should be only alloc, when growLen > 0
96			if b.Cap() < growLen {
97				t.Fatalf("growLen=%d: Cap() is lower than growLen", growLen)
98			}
99			b.Write(p)
100			if b.String() != string(p) {
101				t.Fatalf("growLen=%d: bad data written after Grow", growLen)
102			}
103		})
104		wantAllocs := 1
105		if growLen == 0 {
106			wantAllocs = 0
107		}
108		if g, w := int(allocs), wantAllocs; g != w {
109			t.Errorf("growLen=%d: got %d allocs during Write; want %v", growLen, g, w)
110		}
111	}
112	// when growLen < 0, should panic
113	var a Builder
114	n := -1
115	defer func() {
116		if r := recover(); r == nil {
117			t.Errorf("a.Grow(%d) should panic()", n)
118		}
119	}()
120	a.Grow(n)
121}
122
123func TestBuilderWrite2(t *testing.T) {
124	const s0 = "hello 世界"
125	for _, tt := range []struct {
126		name string
127		fn   func(b *Builder) (int, error)
128		n    int
129		want string
130	}{
131		{
132			"Write",
133			func(b *Builder) (int, error) { return b.Write([]byte(s0)) },
134			len(s0),
135			s0,
136		},
137		{
138			"WriteRune",
139			func(b *Builder) (int, error) { return b.WriteRune('a') },
140			1,
141			"a",
142		},
143		{
144			"WriteRuneWide",
145			func(b *Builder) (int, error) { return b.WriteRune('世') },
146			3,
147			"世",
148		},
149		{
150			"WriteString",
151			func(b *Builder) (int, error) { return b.WriteString(s0) },
152			len(s0),
153			s0,
154		},
155	} {
156		t.Run(tt.name, func(t *testing.T) {
157			var b Builder
158			n, err := tt.fn(&b)
159			if err != nil {
160				t.Fatalf("first call: got %s", err)
161			}
162			if n != tt.n {
163				t.Errorf("first call: got n=%d; want %d", n, tt.n)
164			}
165			check(t, &b, tt.want)
166
167			n, err = tt.fn(&b)
168			if err != nil {
169				t.Fatalf("second call: got %s", err)
170			}
171			if n != tt.n {
172				t.Errorf("second call: got n=%d; want %d", n, tt.n)
173			}
174			check(t, &b, tt.want+tt.want)
175		})
176	}
177}
178
179func TestBuilderWriteByte(t *testing.T) {
180	var b Builder
181	if err := b.WriteByte('a'); err != nil {
182		t.Error(err)
183	}
184	if err := b.WriteByte(0); err != nil {
185		t.Error(err)
186	}
187	check(t, &b, "a\x00")
188}
189
190func TestBuilderAllocs(t *testing.T) {
191	// Issue 23382; verify that copyCheck doesn't force the
192	// Builder to escape and be heap allocated.
193	n := testing.AllocsPerRun(10000, func() {
194		var b Builder
195		b.Grow(5)
196		b.WriteString("abcde")
197		_ = b.String()
198	})
199	if n != 1 {
200		t.Errorf("Builder allocs = %v; want 1", n)
201	}
202}
203
204func TestBuilderCopyPanic(t *testing.T) {
205	tests := []struct {
206		name      string
207		fn        func()
208		wantPanic bool
209	}{
210		{
211			name:      "String",
212			wantPanic: false,
213			fn: func() {
214				var a Builder
215				a.WriteByte('x')
216				b := a
217				_ = b.String() // appease vet
218			},
219		},
220		{
221			name:      "Len",
222			wantPanic: false,
223			fn: func() {
224				var a Builder
225				a.WriteByte('x')
226				b := a
227				b.Len()
228			},
229		},
230		{
231			name:      "Cap",
232			wantPanic: false,
233			fn: func() {
234				var a Builder
235				a.WriteByte('x')
236				b := a
237				b.Cap()
238			},
239		},
240		{
241			name:      "Reset",
242			wantPanic: false,
243			fn: func() {
244				var a Builder
245				a.WriteByte('x')
246				b := a
247				b.Reset()
248				b.WriteByte('y')
249			},
250		},
251		{
252			name:      "Write",
253			wantPanic: true,
254			fn: func() {
255				var a Builder
256				a.Write([]byte("x"))
257				b := a
258				b.Write([]byte("y"))
259			},
260		},
261		{
262			name:      "WriteByte",
263			wantPanic: true,
264			fn: func() {
265				var a Builder
266				a.WriteByte('x')
267				b := a
268				b.WriteByte('y')
269			},
270		},
271		{
272			name:      "WriteString",
273			wantPanic: true,
274			fn: func() {
275				var a Builder
276				a.WriteString("x")
277				b := a
278				b.WriteString("y")
279			},
280		},
281		{
282			name:      "WriteRune",
283			wantPanic: true,
284			fn: func() {
285				var a Builder
286				a.WriteRune('x')
287				b := a
288				b.WriteRune('y')
289			},
290		},
291		{
292			name:      "Grow",
293			wantPanic: true,
294			fn: func() {
295				var a Builder
296				a.Grow(1)
297				b := a
298				b.Grow(2)
299			},
300		},
301	}
302	for _, tt := range tests {
303		didPanic := make(chan bool)
304		go func() {
305			defer func() { didPanic <- recover() != nil }()
306			tt.fn()
307		}()
308		if got := <-didPanic; got != tt.wantPanic {
309			t.Errorf("%s: panicked = %v; want %v", tt.name, got, tt.wantPanic)
310		}
311	}
312}
313
314func TestBuilderWriteInvalidRune(t *testing.T) {
315	// Invalid runes, including negative ones, should be written as
316	// utf8.RuneError.
317	for _, r := range []rune{-1, utf8.MaxRune + 1} {
318		var b Builder
319		b.WriteRune(r)
320		check(t, &b, "\uFFFD")
321	}
322}
323
324var someBytes = []byte("some bytes sdljlk jsklj3lkjlk djlkjw")
325
326var sinkS string
327
328func benchmarkBuilder(b *testing.B, f func(b *testing.B, numWrite int, grow bool)) {
329	b.Run("1Write_NoGrow", func(b *testing.B) {
330		b.ReportAllocs()
331		f(b, 1, false)
332	})
333	b.Run("3Write_NoGrow", func(b *testing.B) {
334		b.ReportAllocs()
335		f(b, 3, false)
336	})
337	b.Run("3Write_Grow", func(b *testing.B) {
338		b.ReportAllocs()
339		f(b, 3, true)
340	})
341}
342
343func BenchmarkBuildString_Builder(b *testing.B) {
344	benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) {
345		for i := 0; i < b.N; i++ {
346			var buf Builder
347			if grow {
348				buf.Grow(len(someBytes) * numWrite)
349			}
350			for i := 0; i < numWrite; i++ {
351				buf.Write(someBytes)
352			}
353			sinkS = buf.String()
354		}
355	})
356}
357
358func BenchmarkBuildString_WriteString(b *testing.B) {
359	someString := string(someBytes)
360	benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) {
361		for i := 0; i < b.N; i++ {
362			var buf Builder
363			if grow {
364				buf.Grow(len(someString) * numWrite)
365			}
366			for i := 0; i < numWrite; i++ {
367				buf.WriteString(someString)
368			}
369			sinkS = buf.String()
370		}
371	})
372}
373
374func BenchmarkBuildString_ByteBuffer(b *testing.B) {
375	benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) {
376		for i := 0; i < b.N; i++ {
377			var buf bytes.Buffer
378			if grow {
379				buf.Grow(len(someBytes) * numWrite)
380			}
381			for i := 0; i < numWrite; i++ {
382				buf.Write(someBytes)
383			}
384			sinkS = buf.String()
385		}
386	})
387}
388
389func TestBuilderGrowSizeclasses(t *testing.T) {
390	s := Repeat("a", 19)
391	allocs := testing.AllocsPerRun(100, func() {
392		var b Builder
393		b.Grow(18)
394		b.WriteString(s)
395		_ = b.String()
396	})
397	if allocs > 1 {
398		t.Fatalf("unexpected amount of allocations: %v, want: 1", allocs)
399	}
400}
401