github.com/pgavlin/text@v0.0.0-20240419000839-8438d0a47805/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 text_test 6 7 import ( 8 "bytes" 9 "testing" 10 "unicode/utf8" 11 12 . "github.com/pgavlin/text" 13 ) 14 15 func check(t *testing.T, b *Builder[string], 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[string] 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[string] 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 // Check that subsequent operations didn't change the returned strings. 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[string] 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 // Ensure that writing after Reset doesn't alter 83 // previously returned strings. 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 p := bytes.Repeat([]byte{'a'}, growLen) 94 allocs := testing.AllocsPerRun(100, func() { 95 var b Builder[string] 96 b.Grow(growLen) // should be only alloc, when growLen > 0 97 if b.Cap() < growLen { 98 t.Fatalf("growLen=%d: Cap() is lower than growLen", growLen) 99 } 100 b.Write(p) 101 if b.String() != string(p) { 102 t.Fatalf("growLen=%d: bad data written after Grow", growLen) 103 } 104 }) 105 wantAllocs := 1 106 if growLen == 0 { 107 wantAllocs = 0 108 } 109 if g, w := int(allocs), wantAllocs; g != w { 110 t.Errorf("growLen=%d: got %d allocs during Write; want %v", growLen, g, w) 111 } 112 } 113 // when growLen < 0, should panic 114 var a Builder[string] 115 n := -1 116 defer func() { 117 if r := recover(); r == nil { 118 t.Errorf("a.Grow(%d) should panic()", n) 119 } 120 }() 121 a.Grow(n) 122 } 123 124 func TestBuilderWrite2(t *testing.T) { 125 const s0 = "hello 世界" 126 for _, tt := range []struct { 127 name string 128 fn func(b *Builder[string]) (int, error) 129 n int 130 want string 131 }{ 132 { 133 "Write", 134 func(b *Builder[string]) (int, error) { return b.Write([]byte(s0)) }, 135 len(s0), 136 s0, 137 }, 138 { 139 "WriteRune", 140 func(b *Builder[string]) (int, error) { return b.WriteRune('a') }, 141 1, 142 "a", 143 }, 144 { 145 "WriteRuneWide", 146 func(b *Builder[string]) (int, error) { return b.WriteRune('世') }, 147 3, 148 "世", 149 }, 150 { 151 "WriteString", 152 func(b *Builder[string]) (int, error) { return b.WriteString(s0) }, 153 len(s0), 154 s0, 155 }, 156 } { 157 t.Run(tt.name, func(t *testing.T) { 158 var b Builder[string] 159 n, err := tt.fn(&b) 160 if err != nil { 161 t.Fatalf("first call: got %s", err) 162 } 163 if n != tt.n { 164 t.Errorf("first call: got n=%d; want %d", n, tt.n) 165 } 166 check(t, &b, tt.want) 167 168 n, err = tt.fn(&b) 169 if err != nil { 170 t.Fatalf("second call: got %s", err) 171 } 172 if n != tt.n { 173 t.Errorf("second call: got n=%d; want %d", n, tt.n) 174 } 175 check(t, &b, tt.want+tt.want) 176 }) 177 } 178 } 179 180 func TestBuilderWriteByte(t *testing.T) { 181 var b Builder[string] 182 if err := b.WriteByte('a'); err != nil { 183 t.Error(err) 184 } 185 if err := b.WriteByte(0); err != nil { 186 t.Error(err) 187 } 188 check(t, &b, "a\x00") 189 } 190 191 func TestBuilderAllocs(t *testing.T) { 192 // Issue 23382; verify that copyCheck doesn't force the 193 // Builder to escape and be heap allocated. 194 n := testing.AllocsPerRun(10000, func() { 195 var b Builder[string] 196 b.Grow(5) 197 b.WriteString("abcde") 198 _ = b.String() 199 }) 200 if n != 1 { 201 t.Errorf("Builder allocs = %v; want 1", n) 202 } 203 } 204 205 func TestBuilderCopyPanic(t *testing.T) { 206 tests := []struct { 207 name string 208 fn func() 209 wantPanic bool 210 }{ 211 { 212 name: "String", 213 wantPanic: false, 214 fn: func() { 215 var a Builder[string] 216 a.WriteByte('x') 217 b := a 218 _ = b.String() // appease vet 219 }, 220 }, 221 { 222 name: "Len", 223 wantPanic: false, 224 fn: func() { 225 var a Builder[string] 226 a.WriteByte('x') 227 b := a 228 b.Len() 229 }, 230 }, 231 { 232 name: "Cap", 233 wantPanic: false, 234 fn: func() { 235 var a Builder[string] 236 a.WriteByte('x') 237 b := a 238 b.Cap() 239 }, 240 }, 241 { 242 name: "Reset", 243 wantPanic: false, 244 fn: func() { 245 var a Builder[string] 246 a.WriteByte('x') 247 b := a 248 b.Reset() 249 b.WriteByte('y') 250 }, 251 }, 252 { 253 name: "Write", 254 wantPanic: true, 255 fn: func() { 256 var a Builder[string] 257 a.Write([]byte("x")) 258 b := a 259 b.Write([]byte("y")) 260 }, 261 }, 262 { 263 name: "WriteByte", 264 wantPanic: true, 265 fn: func() { 266 var a Builder[string] 267 a.WriteByte('x') 268 b := a 269 b.WriteByte('y') 270 }, 271 }, 272 { 273 name: "WriteString", 274 wantPanic: true, 275 fn: func() { 276 var a Builder[string] 277 a.WriteString("x") 278 b := a 279 b.WriteString("y") 280 }, 281 }, 282 { 283 name: "WriteRune", 284 wantPanic: true, 285 fn: func() { 286 var a Builder[string] 287 a.WriteRune('x') 288 b := a 289 b.WriteRune('y') 290 }, 291 }, 292 { 293 name: "Grow", 294 wantPanic: true, 295 fn: func() { 296 var a Builder[string] 297 a.Grow(1) 298 b := a 299 b.Grow(2) 300 }, 301 }, 302 } 303 for _, tt := range tests { 304 didPanic := make(chan bool) 305 go func() { 306 defer func() { didPanic <- recover() != nil }() 307 tt.fn() 308 }() 309 if got := <-didPanic; got != tt.wantPanic { 310 t.Errorf("%s: panicked = %v; want %v", tt.name, got, tt.wantPanic) 311 } 312 } 313 } 314 315 func TestBuilderWriteInvalidRune(t *testing.T) { 316 // Invalid runes, including negative ones, should be written as 317 // utf8.RuneError. 318 for _, r := range []rune{-1, utf8.MaxRune + 1} { 319 var b Builder[string] 320 b.WriteRune(r) 321 check(t, &b, "\uFFFD") 322 } 323 } 324 325 var someBytes = []byte("some bytes sdljlk jsklj3lkjlk djlkjw") 326 327 var sinkS string 328 329 func benchmarkBuilder(b *testing.B, f func(b *testing.B, numWrite int, grow bool)) { 330 b.Run("1Write_NoGrow", func(b *testing.B) { 331 b.ReportAllocs() 332 f(b, 1, false) 333 }) 334 b.Run("3Write_NoGrow", func(b *testing.B) { 335 b.ReportAllocs() 336 f(b, 3, false) 337 }) 338 b.Run("3Write_Grow", func(b *testing.B) { 339 b.ReportAllocs() 340 f(b, 3, true) 341 }) 342 } 343 344 func BenchmarkBuildString_Builder(b *testing.B) { 345 benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) { 346 for i := 0; i < b.N; i++ { 347 var buf Builder[string] 348 if grow { 349 buf.Grow(len(someBytes) * numWrite) 350 } 351 for i := 0; i < numWrite; i++ { 352 buf.Write(someBytes) 353 } 354 sinkS = buf.String() 355 } 356 }) 357 } 358 359 func BenchmarkBuildString_ByteBuffer(b *testing.B) { 360 benchmarkBuilder(b, func(b *testing.B, numWrite int, grow bool) { 361 for i := 0; i < b.N; i++ { 362 var buf bytes.Buffer 363 if grow { 364 buf.Grow(len(someBytes) * numWrite) 365 } 366 for i := 0; i < numWrite; i++ { 367 buf.Write(someBytes) 368 } 369 sinkS = buf.String() 370 } 371 }) 372 }