github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/command/formatter/tabwriter/tabwriter_test.go (about) 1 // Copyright 2009 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 tabwriter 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "strconv" 12 "testing" 13 ) 14 15 type buffer struct { 16 a []byte 17 } 18 19 func (b *buffer) init(n int) { b.a = make([]byte, 0, n) } 20 21 func (b *buffer) clear() { b.a = b.a[0:0] } 22 23 func (b *buffer) Write(buf []byte) (written int, err error) { 24 n := len(b.a) 25 m := len(buf) 26 if n+m <= cap(b.a) { 27 b.a = b.a[0 : n+m] 28 for i := 0; i < m; i++ { 29 b.a[n+i] = buf[i] 30 } 31 } else { 32 panic("buffer.Write: buffer too small") 33 } 34 return len(buf), nil 35 } 36 37 func (b *buffer) String() string { return string(b.a) } 38 39 func write(t *testing.T, testname string, w *Writer, src string) { 40 t.Helper() 41 written, err := io.WriteString(w, src) 42 if err != nil { 43 t.Errorf("--- test: %s\n--- src:\n%q\n--- write error: %v\n", testname, src, err) 44 } 45 if written != len(src) { 46 t.Errorf("--- test: %s\n--- src:\n%q\n--- written = %d, len(src) = %d\n", testname, src, written, len(src)) 47 } 48 } 49 50 func verify(t *testing.T, testname string, w *Writer, b *buffer, src, expected string) { 51 t.Helper() 52 err := w.Flush() 53 if err != nil { 54 t.Errorf("--- test: %s\n--- src:\n%q\n--- flush error: %v\n", testname, src, err) 55 } 56 57 res := b.String() 58 if res != expected { 59 t.Errorf("--- test: %s\n--- src:\n%q\n--- found:\n%q\n--- expected:\n%q\n", testname, src, res, expected) 60 } 61 } 62 63 func check(t *testing.T, testname string, minwidth, tabwidth, padding int, padchar byte, flags uint, src, expected string) { 64 t.Helper() 65 var b buffer 66 b.init(1000) 67 68 var w Writer 69 w.Init(&b, minwidth, tabwidth, padding, padchar, flags) 70 71 // write all at once 72 title := testname + " (written all at once)" 73 b.clear() 74 write(t, title, &w, src) 75 verify(t, title, &w, &b, src, expected) 76 77 // write byte-by-byte 78 title = testname + " (written byte-by-byte)" 79 b.clear() 80 for i := 0; i < len(src); i++ { 81 write(t, title, &w, src[i:i+1]) 82 } 83 verify(t, title, &w, &b, src, expected) 84 85 // write using Fibonacci slice sizes 86 title = testname + " (written in fibonacci slices)" 87 b.clear() 88 for i, d := 0, 0; i < len(src); { 89 write(t, title, &w, src[i:i+d]) 90 i, d = i+d, d+1 91 if i+d > len(src) { 92 d = len(src) - i 93 } 94 } 95 verify(t, title, &w, &b, src, expected) 96 } 97 98 var tests = []struct { 99 testname string 100 minwidth, tabwidth, padding int 101 padchar byte 102 flags uint 103 src, expected string 104 }{ 105 { 106 "1a", 107 8, 0, 1, '.', 0, 108 "", 109 "", 110 }, 111 112 { 113 "1a debug", 114 8, 0, 1, '.', Debug, 115 "", 116 "", 117 }, 118 119 { 120 "1b esc stripped", 121 8, 0, 1, '.', StripEscape, 122 "\xff\xff", 123 "", 124 }, 125 126 { 127 "1b esc", 128 8, 0, 1, '.', 0, 129 "\xff\xff", 130 "\xff\xff", 131 }, 132 133 { 134 "1c esc stripped", 135 8, 0, 1, '.', StripEscape, 136 "\xff\t\xff", 137 "\t", 138 }, 139 140 { 141 "1c esc", 142 8, 0, 1, '.', 0, 143 "\xff\t\xff", 144 "\xff\t\xff", 145 }, 146 147 { 148 "1d esc stripped", 149 8, 0, 1, '.', StripEscape, 150 "\xff\"foo\t\n\tbar\"\xff", 151 "\"foo\t\n\tbar\"", 152 }, 153 154 { 155 "1d esc", 156 8, 0, 1, '.', 0, 157 "\xff\"foo\t\n\tbar\"\xff", 158 "\xff\"foo\t\n\tbar\"\xff", 159 }, 160 161 { 162 "1e esc stripped", 163 8, 0, 1, '.', StripEscape, 164 "abc\xff\tdef", // unterminated escape 165 "abc\tdef", 166 }, 167 168 { 169 "1e esc", 170 8, 0, 1, '.', 0, 171 "abc\xff\tdef", // unterminated escape 172 "abc\xff\tdef", 173 }, 174 175 { 176 "2", 177 8, 0, 1, '.', 0, 178 "\n\n\n", 179 "\n\n\n", 180 }, 181 182 { 183 "3", 184 8, 0, 1, '.', 0, 185 "a\nb\nc", 186 "a\nb\nc", 187 }, 188 189 { 190 "4a", 191 8, 0, 1, '.', 0, 192 "\t", // '\t' terminates an empty cell on last line - nothing to print 193 "", 194 }, 195 196 { 197 "4b", 198 8, 0, 1, '.', AlignRight, 199 "\t", // '\t' terminates an empty cell on last line - nothing to print 200 "", 201 }, 202 203 { 204 "5", 205 8, 0, 1, '.', 0, 206 "*\t*", 207 "*.......*", 208 }, 209 210 { 211 "5b", 212 8, 0, 1, '.', 0, 213 "*\t*\n", 214 "*.......*\n", 215 }, 216 217 { 218 "5c", 219 8, 0, 1, '.', 0, 220 "*\t*\t", 221 "*.......*", 222 }, 223 224 { 225 "5c debug", 226 8, 0, 1, '.', Debug, 227 "*\t*\t", 228 "*.......|*", 229 }, 230 231 { 232 "5d", 233 8, 0, 1, '.', AlignRight, 234 "*\t*\t", 235 ".......**", 236 }, 237 238 { 239 "6", 240 8, 0, 1, '.', 0, 241 "\t\n", 242 "........\n", 243 }, 244 245 { 246 "7a", 247 8, 0, 1, '.', 0, 248 "a) foo", 249 "a) foo", 250 }, 251 252 { 253 "7b", 254 8, 0, 1, ' ', 0, 255 "b) foo\tbar", 256 "b) foo bar", 257 }, 258 259 { 260 "7c", 261 8, 0, 1, '.', 0, 262 "c) foo\tbar\t", 263 "c) foo..bar", 264 }, 265 266 { 267 "7d", 268 8, 0, 1, '.', 0, 269 "d) foo\tbar\n", 270 "d) foo..bar\n", 271 }, 272 273 { 274 "7e", 275 8, 0, 1, '.', 0, 276 "e) foo\tbar\t\n", 277 "e) foo..bar.....\n", 278 }, 279 280 { 281 "7f", 282 8, 0, 1, '.', FilterHTML, 283 "f) f<o\t<b>bar</b>\t\n", 284 "f) f<o..<b>bar</b>.....\n", 285 }, 286 287 { 288 "7g", 289 8, 0, 1, '.', FilterHTML, 290 "g) f<o\t<b>bar</b>\t non-terminated entity &", 291 "g) f<o..<b>bar</b>..... non-terminated entity &", 292 }, 293 294 { 295 "7g debug", 296 8, 0, 1, '.', FilterHTML | Debug, 297 "g) f<o\t<b>bar</b>\t non-terminated entity &", 298 "g) f<o..|<b>bar</b>.....| non-terminated entity &", 299 }, 300 301 { 302 "8", 303 8, 0, 1, '*', 0, 304 "Hello, world!\n", 305 "Hello, world!\n", 306 }, 307 308 { 309 "9a", 310 1, 0, 0, '.', 0, 311 "1\t2\t3\t4\n" + 312 "11\t222\t3333\t44444\n", 313 314 "1.2..3...4\n" + 315 "11222333344444\n", 316 }, 317 318 { 319 "9b", 320 1, 0, 0, '.', FilterHTML, 321 "1\t2<!---\f--->\t3\t4\n" + // \f inside HTML is ignored 322 "11\t222\t3333\t44444\n", 323 324 "1.2<!---\f--->..3...4\n" + 325 "11222333344444\n", 326 }, 327 328 { 329 "9c", 330 1, 0, 0, '.', 0, 331 "1\t2\t3\t4\f" + // \f causes a newline and flush 332 "11\t222\t3333\t44444\n", 333 334 "1234\n" + 335 "11222333344444\n", 336 }, 337 338 { 339 "9c debug", 340 1, 0, 0, '.', Debug, 341 "1\t2\t3\t4\f" + // \f causes a newline and flush 342 "11\t222\t3333\t44444\n", 343 344 "1|2|3|4\n" + 345 "---\n" + 346 "11|222|3333|44444\n", 347 }, 348 349 { 350 "10a", 351 5, 0, 0, '.', 0, 352 "1\t2\t3\t4\n", 353 "1....2....3....4\n", 354 }, 355 356 { 357 "10b", 358 5, 0, 0, '.', 0, 359 "1\t2\t3\t4\t\n", 360 "1....2....3....4....\n", 361 }, 362 363 { 364 "11", 365 8, 0, 1, '.', 0, 366 "本\tb\tc\n" + 367 "aa\t\u672c\u672c\u672c\tcccc\tddddd\n" + 368 "aaa\tbbbb\n", 369 370 "本......b.......c\n" + 371 "aa......本本本..cccc....ddddd\n" + 372 "aaa.....bbbb\n", 373 }, 374 375 { 376 "12a", 377 8, 0, 1, ' ', AlignRight, 378 "a\tè\tc\t\n" + 379 "aa\tèèè\tcccc\tddddd\t\n" + 380 "aaa\tèèèè\t\n", 381 382 " a è c\n" + 383 " aa èèè cccc ddddd\n" + 384 " aaa èèèè\n", 385 }, 386 387 { 388 "12b", 389 2, 0, 0, ' ', 0, 390 "a\tb\tc\n" + 391 "aa\tbbb\tcccc\n" + 392 "aaa\tbbbb\n", 393 394 "a b c\n" + 395 "aa bbbcccc\n" + 396 "aaabbbb\n", 397 }, 398 399 { 400 "12c", 401 8, 0, 1, '_', 0, 402 "a\tb\tc\n" + 403 "aa\tbbb\tcccc\n" + 404 "aaa\tbbbb\n", 405 406 "a_______b_______c\n" + 407 "aa______bbb_____cccc\n" + 408 "aaa_____bbbb\n", 409 }, 410 411 { 412 "13a", 413 4, 0, 1, '-', 0, 414 "4444\t日本語\t22\t1\t333\n" + 415 "999999999\t22\n" + 416 "7\t22\n" + 417 "\t\t\t88888888\n" + 418 "\n" + 419 "666666\t666666\t666666\t4444\n" + 420 "1\t1\t999999999\t0000000000\n", 421 422 "4444------日本語-22--1---333\n" + 423 "999999999-22\n" + 424 "7---------22\n" + 425 "------------------88888888\n" + 426 "\n" + 427 "666666-666666-666666----4444\n" + 428 "1------1------999999999-0000000000\n", 429 }, 430 431 { 432 "13b", 433 4, 0, 3, '.', 0, 434 "4444\t333\t22\t1\t333\n" + 435 "999999999\t22\n" + 436 "7\t22\n" + 437 "\t\t\t88888888\n" + 438 "\n" + 439 "666666\t666666\t666666\t4444\n" + 440 "1\t1\t999999999\t0000000000\n", 441 442 "4444........333...22...1...333\n" + 443 "999999999...22\n" + 444 "7...........22\n" + 445 "....................88888888\n" + 446 "\n" + 447 "666666...666666...666666......4444\n" + 448 "1........1........999999999...0000000000\n", 449 }, 450 451 { 452 "13c", 453 8, 8, 1, '\t', FilterHTML, 454 "4444\t333\t22\t1\t333\n" + 455 "999999999\t22\n" + 456 "7\t22\n" + 457 "\t\t\t88888888\n" + 458 "\n" + 459 "666666\t666666\t666666\t4444\n" + 460 "1\t1\t<font color=red attr=日本語>999999999</font>\t0000000000\n", 461 462 "4444\t\t333\t22\t1\t333\n" + 463 "999999999\t22\n" + 464 "7\t\t22\n" + 465 "\t\t\t\t88888888\n" + 466 "\n" + 467 "666666\t666666\t666666\t\t4444\n" + 468 "1\t1\t<font color=red attr=日本語>999999999</font>\t0000000000\n", 469 }, 470 471 { 472 "14", 473 1, 0, 2, ' ', AlignRight, 474 ".0\t.3\t2.4\t-5.1\t\n" + 475 "23.0\t12345678.9\t2.4\t-989.4\t\n" + 476 "5.1\t12.0\t2.4\t-7.0\t\n" + 477 ".0\t0.0\t332.0\t8908.0\t\n" + 478 ".0\t-.3\t456.4\t22.1\t\n" + 479 ".0\t1.2\t44.4\t-13.3\t\t", 480 481 " .0 .3 2.4 -5.1\n" + 482 " 23.0 12345678.9 2.4 -989.4\n" + 483 " 5.1 12.0 2.4 -7.0\n" + 484 " .0 0.0 332.0 8908.0\n" + 485 " .0 -.3 456.4 22.1\n" + 486 " .0 1.2 44.4 -13.3", 487 }, 488 489 { 490 "14 debug", 491 1, 0, 2, ' ', AlignRight | Debug, 492 ".0\t.3\t2.4\t-5.1\t\n" + 493 "23.0\t12345678.9\t2.4\t-989.4\t\n" + 494 "5.1\t12.0\t2.4\t-7.0\t\n" + 495 ".0\t0.0\t332.0\t8908.0\t\n" + 496 ".0\t-.3\t456.4\t22.1\t\n" + 497 ".0\t1.2\t44.4\t-13.3\t\t", 498 499 " .0| .3| 2.4| -5.1|\n" + 500 " 23.0| 12345678.9| 2.4| -989.4|\n" + 501 " 5.1| 12.0| 2.4| -7.0|\n" + 502 " .0| 0.0| 332.0| 8908.0|\n" + 503 " .0| -.3| 456.4| 22.1|\n" + 504 " .0| 1.2| 44.4| -13.3|", 505 }, 506 507 { 508 "15a", 509 4, 0, 0, '.', 0, 510 "a\t\tb", 511 "a.......b", 512 }, 513 514 { 515 "15b", 516 4, 0, 0, '.', DiscardEmptyColumns, 517 "a\t\tb", // htabs - do not discard column 518 "a.......b", 519 }, 520 521 { 522 "15c", 523 4, 0, 0, '.', DiscardEmptyColumns, 524 "a\v\vb", 525 "a...b", 526 }, 527 528 { 529 "15d", 530 4, 0, 0, '.', AlignRight | DiscardEmptyColumns, 531 "a\v\vb", 532 "...ab", 533 }, 534 535 { 536 "16a", 537 100, 100, 0, '\t', 0, 538 "a\tb\t\td\n" + 539 "a\tb\t\td\te\n" + 540 "a\n" + 541 "a\tb\tc\td\n" + 542 "a\tb\tc\td\te\n", 543 544 "a\tb\t\td\n" + 545 "a\tb\t\td\te\n" + 546 "a\n" + 547 "a\tb\tc\td\n" + 548 "a\tb\tc\td\te\n", 549 }, 550 551 { 552 "16b", 553 100, 100, 0, '\t', DiscardEmptyColumns, 554 "a\vb\v\vd\n" + 555 "a\vb\v\vd\ve\n" + 556 "a\n" + 557 "a\vb\vc\vd\n" + 558 "a\vb\vc\vd\ve\n", 559 560 "a\tb\td\n" + 561 "a\tb\td\te\n" + 562 "a\n" + 563 "a\tb\tc\td\n" + 564 "a\tb\tc\td\te\n", 565 }, 566 567 { 568 "16b debug", 569 100, 100, 0, '\t', DiscardEmptyColumns | Debug, 570 "a\vb\v\vd\n" + 571 "a\vb\v\vd\ve\n" + 572 "a\n" + 573 "a\vb\vc\vd\n" + 574 "a\vb\vc\vd\ve\n", 575 576 "a\t|b\t||d\n" + 577 "a\t|b\t||d\t|e\n" + 578 "a\n" + 579 "a\t|b\t|c\t|d\n" + 580 "a\t|b\t|c\t|d\t|e\n", 581 }, 582 583 { 584 "16c", 585 100, 100, 0, '\t', DiscardEmptyColumns, 586 "a\tb\t\td\n" + // hard tabs - do not discard column 587 "a\tb\t\td\te\n" + 588 "a\n" + 589 "a\tb\tc\td\n" + 590 "a\tb\tc\td\te\n", 591 592 "a\tb\t\td\n" + 593 "a\tb\t\td\te\n" + 594 "a\n" + 595 "a\tb\tc\td\n" + 596 "a\tb\tc\td\te\n", 597 }, 598 599 { 600 "16c debug", 601 100, 100, 0, '\t', DiscardEmptyColumns | Debug, 602 "a\tb\t\td\n" + // hard tabs - do not discard column 603 "a\tb\t\td\te\n" + 604 "a\n" + 605 "a\tb\tc\td\n" + 606 "a\tb\tc\td\te\n", 607 608 "a\t|b\t|\t|d\n" + 609 "a\t|b\t|\t|d\t|e\n" + 610 "a\n" + 611 "a\t|b\t|c\t|d\n" + 612 "a\t|b\t|c\t|d\t|e\n", 613 }, 614 } 615 616 func Test(t *testing.T) { 617 for _, e := range tests { 618 check(t, e.testname, e.minwidth, e.tabwidth, e.padding, e.padchar, e.flags, e.src, e.expected) 619 } 620 } 621 622 type panicWriter struct{} 623 624 func (panicWriter) Write([]byte) (int, error) { 625 panic("cannot write") 626 } 627 628 func wantPanicString(t *testing.T, want string) { 629 t.Helper() 630 if e := recover(); e != nil { 631 got, ok := e.(string) 632 switch { 633 case !ok: 634 t.Errorf("got %v (%T), want panic string", e, e) 635 case got != want: 636 t.Errorf("wrong panic message: got %q, want %q", got, want) 637 } 638 } 639 } 640 641 func TestPanicDuringFlush(t *testing.T) { 642 defer wantPanicString(t, "tabwriter: panic during Flush") 643 var p panicWriter 644 w := new(Writer) 645 w.Init(p, 0, 0, 5, ' ', 0) 646 io.WriteString(w, "a") 647 w.Flush() 648 t.Errorf("failed to panic during Flush") 649 } 650 651 func TestPanicDuringWrite(t *testing.T) { 652 defer wantPanicString(t, "tabwriter: panic during Write") 653 var p panicWriter 654 w := new(Writer) 655 w.Init(p, 0, 0, 5, ' ', 0) 656 io.WriteString(w, "a\n\n") // the second \n triggers a call to w.Write and thus a panic 657 t.Errorf("failed to panic during Write") 658 } 659 660 func BenchmarkTable(b *testing.B) { 661 for _, w := range [...]int{1, 10, 100} { 662 // Build a line with w cells. 663 line := bytes.Repeat([]byte("a\t"), w) 664 line = append(line, '\n') 665 for _, h := range [...]int{10, 1000, 100000} { 666 b.Run(fmt.Sprintf("%dx%d", w, h), func(b *testing.B) { 667 b.Run("new", func(b *testing.B) { 668 b.ReportAllocs() 669 for i := 0; i < b.N; i++ { 670 w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings 671 // Write the line h times. 672 for j := 0; j < h; j++ { 673 w.Write(line) 674 } 675 w.Flush() 676 } 677 }) 678 679 b.Run("reuse", func(b *testing.B) { 680 b.ReportAllocs() 681 w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings 682 for i := 0; i < b.N; i++ { 683 // Write the line h times. 684 for j := 0; j < h; j++ { 685 w.Write(line) 686 } 687 w.Flush() 688 } 689 }) 690 }) 691 } 692 } 693 } 694 695 func BenchmarkPyramid(b *testing.B) { 696 for _, x := range [...]int{10, 100, 1000} { 697 // Build a line with x cells. 698 line := bytes.Repeat([]byte("a\t"), x) 699 b.Run(strconv.Itoa(x), func(b *testing.B) { 700 b.ReportAllocs() 701 for i := 0; i < b.N; i++ { 702 w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings 703 // Write increasing prefixes of that line. 704 for j := 0; j < x; j++ { 705 w.Write(line[:j*2]) 706 w.Write([]byte{'\n'}) 707 } 708 w.Flush() 709 } 710 }) 711 } 712 } 713 714 func BenchmarkRagged(b *testing.B) { 715 var lines [8][]byte 716 for i, w := range [8]int{6, 2, 9, 5, 5, 7, 3, 8} { 717 // Build a line with w cells. 718 lines[i] = bytes.Repeat([]byte("a\t"), w) 719 } 720 for _, h := range [...]int{10, 100, 1000} { 721 b.Run(strconv.Itoa(h), func(b *testing.B) { 722 b.ReportAllocs() 723 for i := 0; i < b.N; i++ { 724 w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings 725 // Write the lines in turn h times. 726 for j := 0; j < h; j++ { 727 w.Write(lines[j%len(lines)]) 728 w.Write([]byte{'\n'}) 729 } 730 w.Flush() 731 } 732 }) 733 } 734 } 735 736 const codeSnippet = ` 737 some command 738 739 foo # aligned 740 barbaz # comments 741 742 but 743 mostly 744 single 745 cell 746 lines 747 ` 748 749 func BenchmarkCode(b *testing.B) { 750 b.ReportAllocs() 751 for i := 0; i < b.N; i++ { 752 w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings 753 // The code is small, so it's reasonable for the tabwriter user 754 // to write it all at once, or buffer the writes. 755 w.Write([]byte(codeSnippet)) 756 w.Flush() 757 } 758 }