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