github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/runtime/string_test.go (about) 1 // Copyright 2012 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 runtime_test 6 7 import ( 8 "runtime" 9 "strconv" 10 "strings" 11 "testing" 12 "unicode/utf8" 13 ) 14 15 // Strings and slices that don't escape and fit into tmpBuf are stack allocated, 16 // which defeats using AllocsPerRun to test other optimizations. 17 const sizeNoStack = 100 18 19 func BenchmarkCompareStringEqual(b *testing.B) { 20 bytes := []byte("Hello Gophers!") 21 s1, s2 := string(bytes), string(bytes) 22 for i := 0; i < b.N; i++ { 23 if s1 != s2 { 24 b.Fatal("s1 != s2") 25 } 26 } 27 } 28 29 func BenchmarkCompareStringIdentical(b *testing.B) { 30 s1 := "Hello Gophers!" 31 s2 := s1 32 for i := 0; i < b.N; i++ { 33 if s1 != s2 { 34 b.Fatal("s1 != s2") 35 } 36 } 37 } 38 39 func BenchmarkCompareStringSameLength(b *testing.B) { 40 s1 := "Hello Gophers!" 41 s2 := "Hello, Gophers" 42 for i := 0; i < b.N; i++ { 43 if s1 == s2 { 44 b.Fatal("s1 == s2") 45 } 46 } 47 } 48 49 func BenchmarkCompareStringDifferentLength(b *testing.B) { 50 s1 := "Hello Gophers!" 51 s2 := "Hello, Gophers!" 52 for i := 0; i < b.N; i++ { 53 if s1 == s2 { 54 b.Fatal("s1 == s2") 55 } 56 } 57 } 58 59 func BenchmarkCompareStringBigUnaligned(b *testing.B) { 60 bytes := make([]byte, 0, 1<<20) 61 for len(bytes) < 1<<20 { 62 bytes = append(bytes, "Hello Gophers!"...) 63 } 64 s1, s2 := string(bytes), "hello"+string(bytes) 65 for i := 0; i < b.N; i++ { 66 if s1 != s2[len("hello"):] { 67 b.Fatal("s1 != s2") 68 } 69 } 70 b.SetBytes(int64(len(s1))) 71 } 72 73 func BenchmarkCompareStringBig(b *testing.B) { 74 bytes := make([]byte, 0, 1<<20) 75 for len(bytes) < 1<<20 { 76 bytes = append(bytes, "Hello Gophers!"...) 77 } 78 s1, s2 := string(bytes), string(bytes) 79 for i := 0; i < b.N; i++ { 80 if s1 != s2 { 81 b.Fatal("s1 != s2") 82 } 83 } 84 b.SetBytes(int64(len(s1))) 85 } 86 87 func BenchmarkConcatStringAndBytes(b *testing.B) { 88 s1 := []byte("Gophers!") 89 for i := 0; i < b.N; i++ { 90 _ = "Hello " + string(s1) 91 } 92 } 93 94 var escapeString string 95 96 func BenchmarkSliceByteToString(b *testing.B) { 97 buf := []byte{'!'} 98 for n := 0; n < 8; n++ { 99 b.Run(strconv.Itoa(len(buf)), func(b *testing.B) { 100 for i := 0; i < b.N; i++ { 101 escapeString = string(buf) 102 } 103 }) 104 buf = append(buf, buf...) 105 } 106 } 107 108 var stringdata = []struct{ name, data string }{ 109 {"ASCII", "01234567890"}, 110 {"Japanese", "日本語日本語日本語"}, 111 {"MixedLength", "$Ѐࠀက퀀𐀀\U00040000\U0010FFFF"}, 112 } 113 114 var sinkInt int 115 116 func BenchmarkRuneCount(b *testing.B) { 117 // Each sub-benchmark counts the runes in a string in a different way. 118 b.Run("lenruneslice", func(b *testing.B) { 119 for _, sd := range stringdata { 120 b.Run(sd.name, func(b *testing.B) { 121 for i := 0; i < b.N; i++ { 122 sinkInt += len([]rune(sd.data)) 123 } 124 }) 125 } 126 }) 127 b.Run("rangeloop", func(b *testing.B) { 128 for _, sd := range stringdata { 129 b.Run(sd.name, func(b *testing.B) { 130 for i := 0; i < b.N; i++ { 131 n := 0 132 for range sd.data { 133 n++ 134 } 135 sinkInt += n 136 } 137 }) 138 } 139 }) 140 b.Run("utf8.RuneCountInString", func(b *testing.B) { 141 for _, sd := range stringdata { 142 b.Run(sd.name, func(b *testing.B) { 143 for i := 0; i < b.N; i++ { 144 sinkInt += utf8.RuneCountInString(sd.data) 145 } 146 }) 147 } 148 }) 149 } 150 151 func BenchmarkRuneIterate(b *testing.B) { 152 b.Run("range", func(b *testing.B) { 153 for _, sd := range stringdata { 154 b.Run(sd.name, func(b *testing.B) { 155 for i := 0; i < b.N; i++ { 156 for range sd.data { 157 } 158 } 159 }) 160 } 161 }) 162 b.Run("range1", func(b *testing.B) { 163 for _, sd := range stringdata { 164 b.Run(sd.name, func(b *testing.B) { 165 for i := 0; i < b.N; i++ { 166 for range sd.data { 167 } 168 } 169 }) 170 } 171 }) 172 b.Run("range2", func(b *testing.B) { 173 for _, sd := range stringdata { 174 b.Run(sd.name, func(b *testing.B) { 175 for i := 0; i < b.N; i++ { 176 for range sd.data { 177 } 178 } 179 }) 180 } 181 }) 182 } 183 184 func BenchmarkArrayEqual(b *testing.B) { 185 a1 := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 186 a2 := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 187 b.ResetTimer() 188 for i := 0; i < b.N; i++ { 189 if a1 != a2 { 190 b.Fatal("not equal") 191 } 192 } 193 } 194 195 func TestStringW(t *testing.T) { 196 strings := []string{ 197 "hello", 198 "a\u5566\u7788b", 199 } 200 201 for _, s := range strings { 202 var b []uint16 203 for _, c := range s { 204 b = append(b, uint16(c)) 205 if c != rune(uint16(c)) { 206 t.Errorf("bad test: stringW can't handle >16 bit runes") 207 } 208 } 209 b = append(b, 0) 210 r := runtime.GostringW(b) 211 if r != s { 212 t.Errorf("gostringW(%v) = %s, want %s", b, r, s) 213 } 214 } 215 } 216 217 func TestLargeStringConcat(t *testing.T) { 218 output := runTestProg(t, "testprog", "stringconcat") 219 want := "panic: " + strings.Repeat("0", 1<<10) + strings.Repeat("1", 1<<10) + 220 strings.Repeat("2", 1<<10) + strings.Repeat("3", 1<<10) 221 if !strings.HasPrefix(output, want) { 222 t.Fatalf("output does not start with %q:\n%s", want, output) 223 } 224 } 225 226 func TestConcatTempString(t *testing.T) { 227 s := "bytes" 228 b := []byte(s) 229 n := testing.AllocsPerRun(1000, func() { 230 if "prefix "+string(b)+" suffix" != "prefix bytes suffix" { 231 t.Fatalf("strings are not equal: '%v' and '%v'", "prefix "+string(b)+" suffix", "prefix bytes suffix") 232 } 233 }) 234 if n != 0 { 235 t.Fatalf("want 0 allocs, got %v", n) 236 } 237 } 238 239 func TestCompareTempString(t *testing.T) { 240 s := strings.Repeat("x", sizeNoStack) 241 b := []byte(s) 242 n := testing.AllocsPerRun(1000, func() { 243 if string(b) != s { 244 t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s) 245 } 246 if string(b) < s { 247 t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s) 248 } 249 if string(b) > s { 250 t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s) 251 } 252 if string(b) == s { 253 } else { 254 t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s) 255 } 256 if string(b) <= s { 257 } else { 258 t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s) 259 } 260 if string(b) >= s { 261 } else { 262 t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s) 263 } 264 }) 265 if n != 0 { 266 t.Fatalf("want 0 allocs, got %v", n) 267 } 268 } 269 270 func TestStringIndexHaystack(t *testing.T) { 271 // See issue 25864. 272 haystack := []byte("hello") 273 needle := "ll" 274 n := testing.AllocsPerRun(1000, func() { 275 if strings.Index(string(haystack), needle) != 2 { 276 t.Fatalf("needle not found") 277 } 278 }) 279 if n != 0 { 280 t.Fatalf("want 0 allocs, got %v", n) 281 } 282 } 283 284 func TestStringIndexNeedle(t *testing.T) { 285 // See issue 25864. 286 haystack := "hello" 287 needle := []byte("ll") 288 n := testing.AllocsPerRun(1000, func() { 289 if strings.Index(haystack, string(needle)) != 2 { 290 t.Fatalf("needle not found") 291 } 292 }) 293 if n != 0 { 294 t.Fatalf("want 0 allocs, got %v", n) 295 } 296 } 297 298 func TestStringOnStack(t *testing.T) { 299 s := "" 300 for i := 0; i < 3; i++ { 301 s = "a" + s + "b" + s + "c" 302 } 303 304 if want := "aaabcbabccbaabcbabccc"; s != want { 305 t.Fatalf("want: '%v', got '%v'", want, s) 306 } 307 } 308 309 func TestIntString(t *testing.T) { 310 // Non-escaping result of intstring. 311 s := "" 312 for i := rune(0); i < 4; i++ { 313 s += string(i+'0') + string(i+'0'+1) 314 } 315 if want := "01122334"; s != want { 316 t.Fatalf("want '%v', got '%v'", want, s) 317 } 318 319 // Escaping result of intstring. 320 var a [4]string 321 for i := rune(0); i < 4; i++ { 322 a[i] = string(i + '0') 323 } 324 s = a[0] + a[1] + a[2] + a[3] 325 if want := "0123"; s != want { 326 t.Fatalf("want '%v', got '%v'", want, s) 327 } 328 } 329 330 func TestIntStringAllocs(t *testing.T) { 331 unknown := '0' 332 n := testing.AllocsPerRun(1000, func() { 333 s1 := string(unknown) 334 s2 := string(unknown + 1) 335 if s1 == s2 { 336 t.Fatalf("bad") 337 } 338 }) 339 if n != 0 { 340 t.Fatalf("want 0 allocs, got %v", n) 341 } 342 } 343 344 func TestRangeStringCast(t *testing.T) { 345 s := strings.Repeat("x", sizeNoStack) 346 n := testing.AllocsPerRun(1000, func() { 347 for i, c := range []byte(s) { 348 if c != s[i] { 349 t.Fatalf("want '%c' at pos %v, got '%c'", s[i], i, c) 350 } 351 } 352 }) 353 if n != 0 { 354 t.Fatalf("want 0 allocs, got %v", n) 355 } 356 } 357 358 func isZeroed(b []byte) bool { 359 for _, x := range b { 360 if x != 0 { 361 return false 362 } 363 } 364 return true 365 } 366 367 func isZeroedR(r []rune) bool { 368 for _, x := range r { 369 if x != 0 { 370 return false 371 } 372 } 373 return true 374 } 375 376 func TestString2Slice(t *testing.T) { 377 // Make sure we don't return slices that expose 378 // an unzeroed section of stack-allocated temp buf 379 // between len and cap. See issue 14232. 380 s := "foož" 381 b := ([]byte)(s) 382 if !isZeroed(b[len(b):cap(b)]) { 383 t.Errorf("extra bytes not zeroed") 384 } 385 r := ([]rune)(s) 386 if !isZeroedR(r[len(r):cap(r)]) { 387 t.Errorf("extra runes not zeroed") 388 } 389 } 390 391 const intSize = 32 << (^uint(0) >> 63) 392 393 type atoi64Test struct { 394 in string 395 out int64 396 ok bool 397 } 398 399 var atoi64tests = []atoi64Test{ 400 {"", 0, false}, 401 {"0", 0, true}, 402 {"-0", 0, true}, 403 {"1", 1, true}, 404 {"-1", -1, true}, 405 {"12345", 12345, true}, 406 {"-12345", -12345, true}, 407 {"012345", 12345, true}, 408 {"-012345", -12345, true}, 409 {"12345x", 0, false}, 410 {"-12345x", 0, false}, 411 {"98765432100", 98765432100, true}, 412 {"-98765432100", -98765432100, true}, 413 {"20496382327982653440", 0, false}, 414 {"-20496382327982653440", 0, false}, 415 {"9223372036854775807", 1<<63 - 1, true}, 416 {"-9223372036854775807", -(1<<63 - 1), true}, 417 {"9223372036854775808", 0, false}, 418 {"-9223372036854775808", -1 << 63, true}, 419 {"9223372036854775809", 0, false}, 420 {"-9223372036854775809", 0, false}, 421 } 422 423 func TestAtoi(t *testing.T) { 424 switch intSize { 425 case 32: 426 for i := range atoi32tests { 427 test := &atoi32tests[i] 428 out, ok := runtime.Atoi(test.in) 429 if test.out != int32(out) || test.ok != ok { 430 t.Errorf("atoi(%q) = (%v, %v) want (%v, %v)", 431 test.in, out, ok, test.out, test.ok) 432 } 433 } 434 case 64: 435 for i := range atoi64tests { 436 test := &atoi64tests[i] 437 out, ok := runtime.Atoi(test.in) 438 if test.out != int64(out) || test.ok != ok { 439 t.Errorf("atoi(%q) = (%v, %v) want (%v, %v)", 440 test.in, out, ok, test.out, test.ok) 441 } 442 } 443 } 444 } 445 446 type atoi32Test struct { 447 in string 448 out int32 449 ok bool 450 } 451 452 var atoi32tests = []atoi32Test{ 453 {"", 0, false}, 454 {"0", 0, true}, 455 {"-0", 0, true}, 456 {"1", 1, true}, 457 {"-1", -1, true}, 458 {"12345", 12345, true}, 459 {"-12345", -12345, true}, 460 {"012345", 12345, true}, 461 {"-012345", -12345, true}, 462 {"12345x", 0, false}, 463 {"-12345x", 0, false}, 464 {"987654321", 987654321, true}, 465 {"-987654321", -987654321, true}, 466 {"2147483647", 1<<31 - 1, true}, 467 {"-2147483647", -(1<<31 - 1), true}, 468 {"2147483648", 0, false}, 469 {"-2147483648", -1 << 31, true}, 470 {"2147483649", 0, false}, 471 {"-2147483649", 0, false}, 472 } 473 474 func TestAtoi32(t *testing.T) { 475 for i := range atoi32tests { 476 test := &atoi32tests[i] 477 out, ok := runtime.Atoi32(test.in) 478 if test.out != out || test.ok != ok { 479 t.Errorf("atoi32(%q) = (%v, %v) want (%v, %v)", 480 test.in, out, ok, test.out, test.ok) 481 } 482 } 483 } 484 485 func TestParseByteCount(t *testing.T) { 486 for _, test := range []struct { 487 in string 488 out int64 489 ok bool 490 }{ 491 // Good numeric inputs. 492 {"1", 1, true}, 493 {"12345", 12345, true}, 494 {"012345", 12345, true}, 495 {"98765432100", 98765432100, true}, 496 {"9223372036854775807", 1<<63 - 1, true}, 497 498 // Good trivial suffix inputs. 499 {"1B", 1, true}, 500 {"12345B", 12345, true}, 501 {"012345B", 12345, true}, 502 {"98765432100B", 98765432100, true}, 503 {"9223372036854775807B", 1<<63 - 1, true}, 504 505 // Good binary suffix inputs. 506 {"1KiB", 1 << 10, true}, 507 {"05KiB", 5 << 10, true}, 508 {"1MiB", 1 << 20, true}, 509 {"10MiB", 10 << 20, true}, 510 {"1GiB", 1 << 30, true}, 511 {"100GiB", 100 << 30, true}, 512 {"1TiB", 1 << 40, true}, 513 {"99TiB", 99 << 40, true}, 514 515 // Good zero inputs. 516 // 517 // -0 is an edge case, but no harm in supporting it. 518 {"-0", 0, true}, 519 {"0", 0, true}, 520 {"0B", 0, true}, 521 {"0KiB", 0, true}, 522 {"0MiB", 0, true}, 523 {"0GiB", 0, true}, 524 {"0TiB", 0, true}, 525 526 // Bad inputs. 527 {"", 0, false}, 528 {"-1", 0, false}, 529 {"a12345", 0, false}, 530 {"a12345B", 0, false}, 531 {"12345x", 0, false}, 532 {"0x12345", 0, false}, 533 534 // Bad numeric inputs. 535 {"9223372036854775808", 0, false}, 536 {"9223372036854775809", 0, false}, 537 {"18446744073709551615", 0, false}, 538 {"20496382327982653440", 0, false}, 539 {"18446744073709551616", 0, false}, 540 {"18446744073709551617", 0, false}, 541 {"9999999999999999999999", 0, false}, 542 543 // Bad trivial suffix inputs. 544 {"9223372036854775808B", 0, false}, 545 {"9223372036854775809B", 0, false}, 546 {"18446744073709551615B", 0, false}, 547 {"20496382327982653440B", 0, false}, 548 {"18446744073709551616B", 0, false}, 549 {"18446744073709551617B", 0, false}, 550 {"9999999999999999999999B", 0, false}, 551 552 // Bad binary suffix inputs. 553 {"1Ki", 0, false}, 554 {"05Ki", 0, false}, 555 {"10Mi", 0, false}, 556 {"100Gi", 0, false}, 557 {"99Ti", 0, false}, 558 {"22iB", 0, false}, 559 {"B", 0, false}, 560 {"iB", 0, false}, 561 {"KiB", 0, false}, 562 {"MiB", 0, false}, 563 {"GiB", 0, false}, 564 {"TiB", 0, false}, 565 {"-120KiB", 0, false}, 566 {"-891MiB", 0, false}, 567 {"-704GiB", 0, false}, 568 {"-42TiB", 0, false}, 569 {"99999999999999999999KiB", 0, false}, 570 {"99999999999999999MiB", 0, false}, 571 {"99999999999999GiB", 0, false}, 572 {"99999999999TiB", 0, false}, 573 {"555EiB", 0, false}, 574 575 // Mistaken SI suffix inputs. 576 {"0KB", 0, false}, 577 {"0MB", 0, false}, 578 {"0GB", 0, false}, 579 {"0TB", 0, false}, 580 {"1KB", 0, false}, 581 {"05KB", 0, false}, 582 {"1MB", 0, false}, 583 {"10MB", 0, false}, 584 {"1GB", 0, false}, 585 {"100GB", 0, false}, 586 {"1TB", 0, false}, 587 {"99TB", 0, false}, 588 {"1K", 0, false}, 589 {"05K", 0, false}, 590 {"10M", 0, false}, 591 {"100G", 0, false}, 592 {"99T", 0, false}, 593 {"99999999999999999999KB", 0, false}, 594 {"99999999999999999MB", 0, false}, 595 {"99999999999999GB", 0, false}, 596 {"99999999999TB", 0, false}, 597 {"99999999999TiB", 0, false}, 598 {"555EB", 0, false}, 599 } { 600 out, ok := runtime.ParseByteCount(test.in) 601 if test.out != out || test.ok != ok { 602 t.Errorf("parseByteCount(%q) = (%v, %v) want (%v, %v)", 603 test.in, out, ok, test.out, test.ok) 604 } 605 } 606 }