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
5package tabwriter_test
6
7import (
8	"bytes"
9	"fmt"
10	"io"
11	"testing"
12	. "text/tabwriter"
13)
14
15type buffer struct {
16	a []byte
17}
18
19func (b *buffer) init(n int) { b.a = make([]byte, 0, n) }
20
21func (b *buffer) clear() { b.a = b.a[0:0] }
22
23func (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
37func (b *buffer) String() string { return string(b.a) }
38
39func 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
49func 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
61func 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
95var 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&lt;o\t<b>bar</b>\t\n",
281		"f) f&lt;o..<b>bar</b>.....\n",
282	},
283
284	{
285		"7g",
286		8, 0, 1, '.', FilterHTML,
287		"g) f&lt;o\t<b>bar</b>\t non-terminated entity &amp",
288		"g) f&lt;o..<b>bar</b>..... non-terminated entity &amp",
289	},
290
291	{
292		"7g debug",
293		8, 0, 1, '.', FilterHTML | Debug,
294		"g) f&lt;o\t<b>bar</b>\t non-terminated entity &amp",
295		"g) f&lt;o..|<b>bar</b>.....| non-terminated entity &amp",
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
613func 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
619type panicWriter struct{}
620
621func (panicWriter) Write([]byte) (int, error) {
622	panic("cannot write")
623}
624
625func 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
637func 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
647func 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") // the second \n triggers a call to w.Write and thus a panic
653	t.Errorf("failed to panic during Write")
654}
655
656func 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
691func 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
710func 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
732const codeSnippet = `
733some command
734
735foo	# aligned
736barbaz	# comments
737
738but
739mostly
740single
741cell
742lines
743`
744
745func 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}
755