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