github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/strings/builder_test.go (about) 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 5 package strings_test 6 7 import ( 8 "bytes" 9 . "strings" 10 "testing" 11 "unicode/utf8" 12 ) 13 14 func 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 29 func 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 48 func 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 72 func 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 90 func 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 123 func 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 179 func 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 190 func 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 204 func 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 314 func 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 324 var someBytes = []byte("some bytes sdljlk jsklj3lkjlk djlkjw") 325 326 var sinkS string 327 328 func 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 343 func 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 358 func BenchmarkBuildString_ByteBuffer(b *testing.B) { 359 benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) { 360 for i := 0; i < b.N; i++ { 361 var buf bytes.Buffer 362 if grow { 363 buf.Grow(len(someBytes) * numWrite) 364 } 365 for i := 0; i < numWrite; i++ { 366 buf.Write(someBytes) 367 } 368 sinkS = buf.String() 369 } 370 }) 371 }