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