1
2
3
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
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
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
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",
162 "abc\tdef",
163 },
164
165 {
166 "1e esc",
167 8, 0, 1, '.', 0,
168 "abc\xff\tdef",
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",
190 "",
191 },
192
193 {
194 "4b",
195 8, 0, 1, '.', AlignRight,
196 "\t",
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" +
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" +
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" +
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",
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" +
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" +
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 (cannot write)")
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 (cannot write)")
649 var p panicWriter
650 w := new(Writer)
651 w.Init(p, 0, 0, 5, ' ', 0)
652 io.WriteString(w, "a\n\n")
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
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)
667
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)
678 for i := 0; i < b.N; i++ {
679
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
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)
699
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
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)
721
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)
749
750
751 w.Write([]byte(codeSnippet))
752 w.Flush()
753 }
754 }
755
View as plain text