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