| // 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" |
| "io/ioutil" |
| "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(ioutil.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(ioutil.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(ioutil.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(ioutil.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(ioutil.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() |
| } |
| } |