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