| // Copyright 2009 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package tabwriter_test | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "io" | |
| "testing" | |
| . "text/tabwriter" | |
| ) | |
| type buffer struct { | |
| a []byte | |
| } | |
| func (b *buffer) init(n int) { b.a = make([]byte, 0, n) } | |
| func (b *buffer) clear() { b.a = b.a[0:0] } | |
| func (b *buffer) Write(buf []byte) (written int, err error) { | |
| n := len(b.a) | |
| m := len(buf) | |
| if n+m <= cap(b.a) { | |
| b.a = b.a[0 : n+m] | |
| for i := 0; i < m; i++ { | |
| b.a[n+i] = buf[i] | |
| } | |
| } else { | |
| panic("buffer.Write: buffer too small") | |
| } | |
| return len(buf), nil | |
| } | |
| func (b *buffer) String() string { return string(b.a) } | |
| func write(t *testing.T, testname string, w *Writer, src string) { | |
| written, err := io.WriteString(w, src) | |
| if err != nil { | |
| t.Errorf("--- test: %s\n--- src:\n%q\n--- write error: %v\n", testname, src, err) | |
| } | |
| if written != len(src) { | |
| t.Errorf("--- test: %s\n--- src:\n%q\n--- written = %d, len(src) = %d\n", testname, src, written, len(src)) | |
| } | |
| } | |
| func verify(t *testing.T, testname string, w *Writer, b *buffer, src, expected string) { | |
| err := w.Flush() | |
| if err != nil { | |
| t.Errorf("--- test: %s\n--- src:\n%q\n--- flush error: %v\n", testname, src, err) | |
| } | |
| res := b.String() | |
| if res != expected { | |
| t.Errorf("--- test: %s\n--- src:\n%q\n--- found:\n%q\n--- expected:\n%q\n", testname, src, res, expected) | |
| } | |
| } | |
| func check(t *testing.T, testname string, minwidth, tabwidth, padding int, padchar byte, flags uint, src, expected string) { | |
| var b buffer | |
| b.init(1000) | |
| var w Writer | |
| w.Init(&b, minwidth, tabwidth, padding, padchar, flags) | |
| // write all at once | |
| title := testname + " (written all at once)" | |
| b.clear() | |
| write(t, title, &w, src) | |
| verify(t, title, &w, &b, src, expected) | |
| // write byte-by-byte | |
| title = testname + " (written byte-by-byte)" | |
| b.clear() | |
| for i := 0; i < len(src); i++ { | |
| write(t, title, &w, src[i:i+1]) | |
| } | |
| verify(t, title, &w, &b, src, expected) | |
| // write using Fibonacci slice sizes | |
| title = testname + " (written in fibonacci slices)" | |
| b.clear() | |
| for i, d := 0, 0; i < len(src); { | |
| write(t, title, &w, src[i:i+d]) | |
| i, d = i+d, d+1 | |
| if i+d > len(src) { | |
| d = len(src) - i | |
| } | |
| } | |
| verify(t, title, &w, &b, src, expected) | |
| } | |
| var tests = []struct { | |
| testname string | |
| minwidth, tabwidth, padding int | |
| padchar byte | |
| flags uint | |
| src, expected string | |
| }{ | |
| { | |
| "1a", | |
| 8, 0, 1, '.', 0, | |
| "", | |
| "", | |
| }, | |
| { | |
| "1a debug", | |
| 8, 0, 1, '.', Debug, | |
| "", | |
| "", | |
| }, | |
| { | |
| "1b esc stripped", | |
| 8, 0, 1, '.', StripEscape, | |
| "\xff\xff", | |
| "", | |
| }, | |
| { | |
| "1b esc", | |
| 8, 0, 1, '.', 0, | |
| "\xff\xff", | |
| "\xff\xff", | |
| }, | |
| { | |
| "1c esc stripped", | |
| 8, 0, 1, '.', StripEscape, | |
| "\xff\t\xff", | |
| "\t", | |
| }, | |
| { | |
| "1c esc", | |
| 8, 0, 1, '.', 0, | |
| "\xff\t\xff", | |
| "\xff\t\xff", | |
| }, | |
| { | |
| "1d esc stripped", | |
| 8, 0, 1, '.', StripEscape, | |
| "\xff\"foo\t\n\tbar\"\xff", | |
| "\"foo\t\n\tbar\"", | |
| }, | |
| { | |
| "1d esc", | |
| 8, 0, 1, '.', 0, | |
| "\xff\"foo\t\n\tbar\"\xff", | |
| "\xff\"foo\t\n\tbar\"\xff", | |
| }, | |
| { | |
| "1e esc stripped", | |
| 8, 0, 1, '.', StripEscape, | |
| "abc\xff\tdef", // unterminated escape | |
| "abc\tdef", | |
| }, | |
| { | |
| "1e esc", | |
| 8, 0, 1, '.', 0, | |
| "abc\xff\tdef", // unterminated escape | |
| "abc\xff\tdef", | |
| }, | |
| { | |
| "2", | |
| 8, 0, 1, '.', 0, | |
| "\n\n\n", | |
| "\n\n\n", | |
| }, | |
| { | |
| "3", | |
| 8, 0, 1, '.', 0, | |
| "a\nb\nc", | |
| "a\nb\nc", | |
| }, | |
| { | |
| "4a", | |
| 8, 0, 1, '.', 0, | |
| "\t", // '\t' terminates an empty cell on last line - nothing to print | |
| "", | |
| }, | |
| { | |
| "4b", | |
| 8, 0, 1, '.', AlignRight, | |
| "\t", // '\t' terminates an empty cell on last line - nothing to print | |
| "", | |
| }, | |
| { | |
| "5", | |
| 8, 0, 1, '.', 0, | |
| "*\t*", | |
| "*.......*", | |
| }, | |
| { | |
| "5b", | |
| 8, 0, 1, '.', 0, | |
| "*\t*\n", | |
| "*.......*\n", | |
| }, | |
| { | |
| "5c", | |
| 8, 0, 1, '.', 0, | |
| "*\t*\t", | |
| "*.......*", | |
| }, | |
| { | |
| "5c debug", | |
| 8, 0, 1, '.', Debug, | |
| "*\t*\t", | |
| "*.......|*", | |
| }, | |
| { | |
| "5d", | |
| 8, 0, 1, '.', AlignRight, | |
| "*\t*\t", | |
| ".......**", | |
| }, | |
| { | |
| "6", | |
| 8, 0, 1, '.', 0, | |
| "\t\n", | |
| "........\n", | |
| }, | |
| { | |
| "7a", | |
| 8, 0, 1, '.', 0, | |
| "a) foo", | |
| "a) foo", | |
| }, | |
| { | |
| "7b", | |
| 8, 0, 1, ' ', 0, | |
| "b) foo\tbar", | |
| "b) foo bar", | |
| }, | |
| { | |
| "7c", | |
| 8, 0, 1, '.', 0, | |
| "c) foo\tbar\t", | |
| "c) foo..bar", | |
| }, | |
| { | |
| "7d", | |
| 8, 0, 1, '.', 0, | |
| "d) foo\tbar\n", | |
| "d) foo..bar\n", | |
| }, | |
| { | |
| "7e", | |
| 8, 0, 1, '.', 0, | |
| "e) foo\tbar\t\n", | |
| "e) foo..bar.....\n", | |
| }, | |
| { | |
| "7f", | |
| 8, 0, 1, '.', FilterHTML, | |
| "f) f<o\t<b>bar</b>\t\n", | |
| "f) f<o..<b>bar</b>.....\n", | |
| }, | |
| { | |
| "7g", | |
| 8, 0, 1, '.', FilterHTML, | |
| "g) f<o\t<b>bar</b>\t non-terminated entity &", | |
| "g) f<o..<b>bar</b>..... non-terminated entity &", | |
| }, | |
| { | |
| "7g debug", | |
| 8, 0, 1, '.', FilterHTML | Debug, | |
| "g) f<o\t<b>bar</b>\t non-terminated entity &", | |
| "g) f<o..|<b>bar</b>.....| non-terminated entity &", | |
| }, | |
| { | |
| "8", | |
| 8, 0, 1, '*', 0, | |
| "Hello, world!\n", | |
| "Hello, world!\n", | |
| }, | |
| { | |
| "9a", | |
| 1, 0, 0, '.', 0, | |
| "1\t2\t3\t4\n" + | |
| "11\t222\t3333\t44444\n", | |
| "1.2..3...4\n" + | |
| "11222333344444\n", | |
| }, | |
| { | |
| "9b", | |
| 1, 0, 0, '.', FilterHTML, | |
| "1\t2<!---\f--->\t3\t4\n" + // \f inside HTML is ignored | |
| "11\t222\t3333\t44444\n", | |
| "1.2<!---\f--->..3...4\n" + | |
| "11222333344444\n", | |
| }, | |
| { | |
| "9c", | |
| 1, 0, 0, '.', 0, | |
| "1\t2\t3\t4\f" + // \f causes a newline and flush | |
| "11\t222\t3333\t44444\n", | |
| "1234\n" + | |
| "11222333344444\n", | |
| }, | |
| { | |
| "9c debug", | |
| 1, 0, 0, '.', Debug, | |
| "1\t2\t3\t4\f" + // \f causes a newline and flush | |
| "11\t222\t3333\t44444\n", | |
| "1|2|3|4\n" + | |
| "---\n" + | |
| "11|222|3333|44444\n", | |
| }, | |
| { | |
| "10a", | |
| 5, 0, 0, '.', 0, | |
| "1\t2\t3\t4\n", | |
| "1....2....3....4\n", | |
| }, | |
| { | |
| "10b", | |
| 5, 0, 0, '.', 0, | |
| "1\t2\t3\t4\t\n", | |
| "1....2....3....4....\n", | |
| }, | |
| { | |
| "11", | |
| 8, 0, 1, '.', 0, | |
| "本\tb\tc\n" + | |
| "aa\t\u672c\u672c\u672c\tcccc\tddddd\n" + | |
| "aaa\tbbbb\n", | |
| "本.......b.......c\n" + | |
| "aa......本本本.....cccc....ddddd\n" + | |
| "aaa.....bbbb\n", | |
| }, | |
| { | |
| "12a", | |
| 8, 0, 1, ' ', AlignRight, | |
| "a\tè\tc\t\n" + | |
| "aa\tèèè\tcccc\tddddd\t\n" + | |
| "aaa\tèèèè\t\n", | |
| " a è c\n" + | |
| " aa èèè cccc ddddd\n" + | |
| " aaa èèèè\n", | |
| }, | |
| { | |
| "12b", | |
| 2, 0, 0, ' ', 0, | |
| "a\tb\tc\n" + | |
| "aa\tbbb\tcccc\n" + | |
| "aaa\tbbbb\n", | |
| "a b c\n" + | |
| "aa bbbcccc\n" + | |
| "aaabbbb\n", | |
| }, | |
| { | |
| "12c", | |
| 8, 0, 1, '_', 0, | |
| "a\tb\tc\n" + | |
| "aa\tbbb\tcccc\n" + | |
| "aaa\tbbbb\n", | |
| "a_______b_______c\n" + | |
| "aa______bbb_____cccc\n" + | |
| "aaa_____bbbb\n", | |
| }, | |
| { | |
| "13a", | |
| 4, 0, 1, '-', 0, | |
| "4444\t日本語\t22\t1\t333\n" + | |
| "999999999\t22\n" + | |
| "7\t22\n" + | |
| "\t\t\t88888888\n" + | |
| "\n" + | |
| "666666\t666666\t666666\t4444\n" + | |
| "1\t1\t999999999\t0000000000\n", | |
| "4444------日本語-22--1---333\n" + | |
| "999999999-22\n" + | |
| "7---------22\n" + | |
| "------------------88888888\n" + | |
| "\n" + | |
| "666666-666666-666666----4444\n" + | |
| "1------1------999999999-0000000000\n", | |
| }, | |
| { | |
| "13b", | |
| 4, 0, 3, '.', 0, | |
| "4444\t333\t22\t1\t333\n" + | |
| "999999999\t22\n" + | |
| "7\t22\n" + | |
| "\t\t\t88888888\n" + | |
| "\n" + | |
| "666666\t666666\t666666\t4444\n" + | |
| "1\t1\t999999999\t0000000000\n", | |
| "4444........333...22...1...333\n" + | |
| "999999999...22\n" + | |
| "7...........22\n" + | |
| "....................88888888\n" + | |
| "\n" + | |
| "666666...666666...666666......4444\n" + | |
| "1........1........999999999...0000000000\n", | |
| }, | |
| { | |
| "13c", | |
| 8, 8, 1, '\t', FilterHTML, | |
| "4444\t333\t22\t1\t333\n" + | |
| "999999999\t22\n" + | |
| "7\t22\n" + | |
| "\t\t\t88888888\n" + | |
| "\n" + | |
| "666666\t666666\t666666\t4444\n" + | |
| "1\t1\t<font color=red attr=日本語>999999999</font>\t0000000000\n", | |
| "4444\t\t333\t22\t1\t333\n" + | |
| "999999999\t22\n" + | |
| "7\t\t22\n" + | |
| "\t\t\t\t88888888\n" + | |
| "\n" + | |
| "666666\t666666\t666666\t\t4444\n" + | |
| "1\t1\t<font color=red attr=日本語>999999999</font>\t0000000000\n", | |
| }, | |
| { | |
| "14", | |
| 1, 0, 2, ' ', AlignRight, | |
| ".0\t.3\t2.4\t-5.1\t\n" + | |
| "23.0\t12345678.9\t2.4\t-989.4\t\n" + | |
| "5.1\t12.0\t2.4\t-7.0\t\n" + | |
| ".0\t0.0\t332.0\t8908.0\t\n" + | |
| ".0\t-.3\t456.4\t22.1\t\n" + | |
| ".0\t1.2\t44.4\t-13.3\t\t", | |
| " .0 .3 2.4 -5.1\n" + | |
| " 23.0 12345678.9 2.4 -989.4\n" + | |
| " 5.1 12.0 2.4 -7.0\n" + | |
| " .0 0.0 332.0 8908.0\n" + | |
| " .0 -.3 456.4 22.1\n" + | |
| " .0 1.2 44.4 -13.3", | |
| }, | |
| { | |
| "14 debug", | |
| 1, 0, 2, ' ', AlignRight | Debug, | |
| ".0\t.3\t2.4\t-5.1\t\n" + | |
| "23.0\t12345678.9\t2.4\t-989.4\t\n" + | |
| "5.1\t12.0\t2.4\t-7.0\t\n" + | |
| ".0\t0.0\t332.0\t8908.0\t\n" + | |
| ".0\t-.3\t456.4\t22.1\t\n" + | |
| ".0\t1.2\t44.4\t-13.3\t\t", | |
| " .0| .3| 2.4| -5.1|\n" + | |
| " 23.0| 12345678.9| 2.4| -989.4|\n" + | |
| " 5.1| 12.0| 2.4| -7.0|\n" + | |
| " .0| 0.0| 332.0| 8908.0|\n" + | |
| " .0| -.3| 456.4| 22.1|\n" + | |
| " .0| 1.2| 44.4| -13.3|", | |
| }, | |
| { | |
| "15a", | |
| 4, 0, 0, '.', 0, | |
| "a\t\tb", | |
| "a.......b", | |
| }, | |
| { | |
| "15b", | |
| 4, 0, 0, '.', DiscardEmptyColumns, | |
| "a\t\tb", // htabs - do not discard column | |
| "a.......b", | |
| }, | |
| { | |
| "15c", | |
| 4, 0, 0, '.', DiscardEmptyColumns, | |
| "a\v\vb", | |
| "a...b", | |
| }, | |
| { | |
| "15d", | |
| 4, 0, 0, '.', AlignRight | DiscardEmptyColumns, | |
| "a\v\vb", | |
| "...ab", | |
| }, | |
| { | |
| "16a", | |
| 100, 100, 0, '\t', 0, | |
| "a\tb\t\td\n" + | |
| "a\tb\t\td\te\n" + | |
| "a\n" + | |
| "a\tb\tc\td\n" + | |
| "a\tb\tc\td\te\n", | |
| "a\tb\t\td\n" + | |
| "a\tb\t\td\te\n" + | |
| "a\n" + | |
| "a\tb\tc\td\n" + | |
| "a\tb\tc\td\te\n", | |
| }, | |
| { | |
| "16b", | |
| 100, 100, 0, '\t', DiscardEmptyColumns, | |
| "a\vb\v\vd\n" + | |
| "a\vb\v\vd\ve\n" + | |
| "a\n" + | |
| "a\vb\vc\vd\n" + | |
| "a\vb\vc\vd\ve\n", | |
| "a\tb\td\n" + | |
| "a\tb\td\te\n" + | |
| "a\n" + | |
| "a\tb\tc\td\n" + | |
| "a\tb\tc\td\te\n", | |
| }, | |
| { | |
| "16b debug", | |
| 100, 100, 0, '\t', DiscardEmptyColumns | Debug, | |
| "a\vb\v\vd\n" + | |
| "a\vb\v\vd\ve\n" + | |
| "a\n" + | |
| "a\vb\vc\vd\n" + | |
| "a\vb\vc\vd\ve\n", | |
| "a\t|b\t||d\n" + | |
| "a\t|b\t||d\t|e\n" + | |
| "a\n" + | |
| "a\t|b\t|c\t|d\n" + | |
| "a\t|b\t|c\t|d\t|e\n", | |
| }, | |
| { | |
| "16c", | |
| 100, 100, 0, '\t', DiscardEmptyColumns, | |
| "a\tb\t\td\n" + // hard tabs - do not discard column | |
| "a\tb\t\td\te\n" + | |
| "a\n" + | |
| "a\tb\tc\td\n" + | |
| "a\tb\tc\td\te\n", | |
| "a\tb\t\td\n" + | |
| "a\tb\t\td\te\n" + | |
| "a\n" + | |
| "a\tb\tc\td\n" + | |
| "a\tb\tc\td\te\n", | |
| }, | |
| { | |
| "16c debug", | |
| 100, 100, 0, '\t', DiscardEmptyColumns | Debug, | |
| "a\tb\t\td\n" + // hard tabs - do not discard column | |
| "a\tb\t\td\te\n" + | |
| "a\n" + | |
| "a\tb\tc\td\n" + | |
| "a\tb\tc\td\te\n", | |
| "a\t|b\t|\t|d\n" + | |
| "a\t|b\t|\t|d\t|e\n" + | |
| "a\n" + | |
| "a\t|b\t|c\t|d\n" + | |
| "a\t|b\t|c\t|d\t|e\n", | |
| }, | |
| } | |
| func Test(t *testing.T) { | |
| for _, e := range tests { | |
| check(t, e.testname, e.minwidth, e.tabwidth, e.padding, e.padchar, e.flags, e.src, e.expected) | |
| } | |
| } | |
| type panicWriter struct{} | |
| func (panicWriter) Write([]byte) (int, error) { | |
| panic("cannot write") | |
| } | |
| func wantPanicString(t *testing.T, want string) { | |
| if e := recover(); e != nil { | |
| got, ok := e.(string) | |
| switch { | |
| case !ok: | |
| t.Errorf("got %v (%T), want panic string", e, e) | |
| case got != want: | |
| t.Errorf("wrong panic message: got %q, want %q", got, want) | |
| } | |
| } | |
| } | |
| func TestPanicDuringFlush(t *testing.T) { | |
| defer wantPanicString(t, "tabwriter: panic during Flush") | |
| var p panicWriter | |
| w := new(Writer) | |
| w.Init(p, 0, 0, 5, ' ', 0) | |
| io.WriteString(w, "a") | |
| w.Flush() | |
| t.Errorf("failed to panic during Flush") | |
| } | |
| func TestPanicDuringWrite(t *testing.T) { | |
| defer wantPanicString(t, "tabwriter: panic during Write") | |
| var p panicWriter | |
| w := new(Writer) | |
| w.Init(p, 0, 0, 5, ' ', 0) | |
| io.WriteString(w, "a\n\n") // the second \n triggers a call to w.Write and thus a panic | |
| t.Errorf("failed to panic during Write") | |
| } | |
| func BenchmarkTable(b *testing.B) { | |
| for _, w := range [...]int{1, 10, 100} { | |
| // Build a line with w cells. | |
| line := bytes.Repeat([]byte("a\t"), w) | |
| line = append(line, '\n') | |
| for _, h := range [...]int{10, 1000, 100000} { | |
| b.Run(fmt.Sprintf("%dx%d", w, h), func(b *testing.B) { | |
| b.Run("new", func(b *testing.B) { | |
| b.ReportAllocs() | |
| for i := 0; i < b.N; i++ { | |
| w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings | |
| // Write the line h times. | |
| for j := 0; j < h; j++ { | |
| w.Write(line) | |
| } | |
| w.Flush() | |
| } | |
| }) | |
| b.Run("reuse", func(b *testing.B) { | |
| b.ReportAllocs() | |
| w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings | |
| for i := 0; i < b.N; i++ { | |
| // Write the line h times. | |
| for j := 0; j < h; j++ { | |
| w.Write(line) | |
| } | |
| w.Flush() | |
| } | |
| }) | |
| }) | |
| } | |
| } | |
| } | |
| func BenchmarkPyramid(b *testing.B) { | |
| for _, x := range [...]int{10, 100, 1000} { | |
| // Build a line with x cells. | |
| line := bytes.Repeat([]byte("a\t"), x) | |
| b.Run(fmt.Sprintf("%d", x), func(b *testing.B) { | |
| b.ReportAllocs() | |
| for i := 0; i < b.N; i++ { | |
| w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings | |
| // Write increasing prefixes of that line. | |
| for j := 0; j < x; j++ { | |
| w.Write(line[:j*2]) | |
| w.Write([]byte{'\n'}) | |
| } | |
| w.Flush() | |
| } | |
| }) | |
| } | |
| } | |
| func BenchmarkRagged(b *testing.B) { | |
| var lines [8][]byte | |
| for i, w := range [8]int{6, 2, 9, 5, 5, 7, 3, 8} { | |
| // Build a line with w cells. | |
| lines[i] = bytes.Repeat([]byte("a\t"), w) | |
| } | |
| for _, h := range [...]int{10, 100, 1000} { | |
| b.Run(fmt.Sprintf("%d", h), func(b *testing.B) { | |
| b.ReportAllocs() | |
| for i := 0; i < b.N; i++ { | |
| w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings | |
| // Write the lines in turn h times. | |
| for j := 0; j < h; j++ { | |
| w.Write(lines[j%len(lines)]) | |
| w.Write([]byte{'\n'}) | |
| } | |
| w.Flush() | |
| } | |
| }) | |
| } | |
| } | |
| const codeSnippet = ` | |
| some command | |
| foo # aligned | |
| barbaz # comments | |
| but | |
| mostly | |
| single | |
| cell | |
| lines | |
| ` | |
| func BenchmarkCode(b *testing.B) { | |
| b.ReportAllocs() | |
| for i := 0; i < b.N; i++ { | |
| w := NewWriter(io.Discard, 4, 4, 1, ' ', 0) // no particular reason for these settings | |
| // The code is small, so it's reasonable for the tabwriter user | |
| // to write it all at once, or buffer the writes. | |
| w.Write([]byte(codeSnippet)) | |
| w.Flush() | |
| } | |
| } |