From 5bb581e3bd5e8763bb519bffcd8bc0d57fcf4e2e Mon Sep 17 00:00:00 2001 From: gedi Date: Mon, 12 Nov 2018 15:54:08 +0200 Subject: [PATCH] add tag based color writter for printing formatter tests --- color_tag_test.go | 250 ++++++++++++++++++ formatter-tests/features/empty.feature | 1 + .../features/empty_with_description.feature | 4 + ...with_single_scenario_without_steps.feature | 3 + ...ario_without_steps_and_description.feature | 6 + .../single_scenario_with_passing_step.feature | 7 + formatter-tests/pretty/empty | 1 + formatters_print_test.go | 1 + 8 files changed, 273 insertions(+) create mode 100644 color_tag_test.go create mode 100644 formatter-tests/features/empty.feature create mode 100644 formatter-tests/features/empty_with_description.feature create mode 100644 formatter-tests/features/empty_with_single_scenario_without_steps.feature create mode 100644 formatter-tests/features/empty_with_single_scenario_without_steps_and_description.feature create mode 100644 formatter-tests/features/single_scenario_with_passing_step.feature create mode 100644 formatter-tests/pretty/empty create mode 100644 formatters_print_test.go diff --git a/color_tag_test.go b/color_tag_test.go new file mode 100644 index 0000000..862c82b --- /dev/null +++ b/color_tag_test.go @@ -0,0 +1,250 @@ +package godog + +import ( + "bytes" + "fmt" + "io" + "strings" + "testing" + + "github.com/DATA-DOG/godog/colors" +) + +type csiState int + +const ( + outsideCsiCode csiState = iota + firstCsiCode + secondCsiCode +) + +type tagColorWriter struct { + w io.Writer + state csiState + paramStartBuf bytes.Buffer + paramBuf bytes.Buffer + tag string +} + +const ( + firstCsiChar byte = '\x1b' + secondeCsiChar byte = '[' + separatorChar byte = ';' + sgrCode byte = 'm' +) + +const ( + ansiReset = "0" + ansiIntensityOn = "1" + ansiIntensityOff = "21" + ansiUnderlineOn = "4" + ansiUnderlineOff = "24" + ansiBlinkOn = "5" + ansiBlinkOff = "25" + + ansiForegroundBlack = "30" + ansiForegroundRed = "31" + ansiForegroundGreen = "32" + ansiForegroundYellow = "33" + ansiForegroundBlue = "34" + ansiForegroundMagenta = "35" + ansiForegroundCyan = "36" + ansiForegroundWhite = "37" + ansiForegroundDefault = "39" + + ansiBackgroundBlack = "40" + ansiBackgroundRed = "41" + ansiBackgroundGreen = "42" + ansiBackgroundYellow = "43" + ansiBackgroundBlue = "44" + ansiBackgroundMagenta = "45" + ansiBackgroundCyan = "46" + ansiBackgroundWhite = "47" + ansiBackgroundDefault = "49" + + ansiLightForegroundGray = "90" + ansiLightForegroundRed = "91" + ansiLightForegroundGreen = "92" + ansiLightForegroundYellow = "93" + ansiLightForegroundBlue = "94" + ansiLightForegroundMagenta = "95" + ansiLightForegroundCyan = "96" + ansiLightForegroundWhite = "97" + + ansiLightBackgroundGray = "100" + ansiLightBackgroundRed = "101" + ansiLightBackgroundGreen = "102" + ansiLightBackgroundYellow = "103" + ansiLightBackgroundBlue = "104" + ansiLightBackgroundMagenta = "105" + ansiLightBackgroundCyan = "106" + ansiLightBackgroundWhite = "107" +) + +var colorMap = map[string]string{ + ansiForegroundBlack: "black", + ansiForegroundRed: "red", + ansiForegroundGreen: "green", + ansiForegroundYellow: "yellow", + ansiForegroundBlue: "blue", + ansiForegroundMagenta: "magenta", + ansiForegroundCyan: "cyan", + ansiForegroundWhite: "white", + ansiForegroundDefault: "", +} + +func (cw *tagColorWriter) flushBuffer() (int, error) { + return cw.flushTo(cw.w) +} + +func (cw *tagColorWriter) resetBuffer() (int, error) { + return cw.flushTo(nil) +} + +func (cw *tagColorWriter) flushTo(w io.Writer) (int, error) { + var n1, n2 int + var err error + + startBytes := cw.paramStartBuf.Bytes() + cw.paramStartBuf.Reset() + if w != nil { + n1, err = cw.w.Write(startBytes) + if err != nil { + return n1, err + } + } else { + n1 = len(startBytes) + } + paramBytes := cw.paramBuf.Bytes() + cw.paramBuf.Reset() + if w != nil { + n2, err = cw.w.Write(paramBytes) + if err != nil { + return n1 + n2, err + } + } else { + n2 = len(paramBytes) + } + return n1 + n2, nil +} + +func isParameterChar(b byte) bool { + return ('0' <= b && b <= '9') || b == separatorChar +} + +func (cw *tagColorWriter) Write(p []byte) (int, error) { + r, nw, first, last := 0, 0, 0, 0 + + var err error + for i, ch := range p { + switch cw.state { + case outsideCsiCode: + if ch == firstCsiChar { + cw.paramStartBuf.WriteByte(ch) + cw.state = firstCsiCode + } + case firstCsiCode: + switch ch { + case firstCsiChar: + cw.paramStartBuf.WriteByte(ch) + break + case secondeCsiChar: + cw.paramStartBuf.WriteByte(ch) + cw.state = secondCsiCode + last = i - 1 + default: + cw.resetBuffer() + cw.state = outsideCsiCode + } + case secondCsiCode: + if isParameterChar(ch) { + cw.paramBuf.WriteByte(ch) + } else { + nw, err = cw.w.Write(p[first:last]) + r += nw + if err != nil { + return r, err + } + first = i + 1 + if ch == sgrCode { + cw.changeColor() + } + n, _ := cw.resetBuffer() + // Add one more to the size of the buffer for the last ch + r += n + 1 + + cw.state = outsideCsiCode + } + default: + cw.state = outsideCsiCode + } + } + + if cw.state == outsideCsiCode { + nw, err = cw.w.Write(p[first:len(p)]) + r += nw + } + + return r, err +} + +func (cw *tagColorWriter) changeColor() { + strParam := cw.paramBuf.String() + if len(strParam) <= 0 { + strParam = "0" + } + csiParam := strings.Split(strParam, string(separatorChar)) + for _, p := range csiParam { + c, ok := colorMap[p] + switch { + case !ok: + switch p { + case ansiReset: + fmt.Fprint(cw.w, "") + cw.tag = "" + case ansiIntensityOn: + cw.tag = "bold-" + cw.tag + case ansiIntensityOff: + case ansiUnderlineOn: + case ansiUnderlineOff: + case ansiBlinkOn: + case ansiBlinkOff: + default: + // unknown code + } + default: + cw.tag += c + fmt.Fprint(cw.w, "<"+cw.tag+">") + } + } +} + +func TestTagColorWriter(t *testing.T) { + var buf bytes.Buffer + w := &tagColorWriter{w: &buf} + + s := fmt.Sprintf("text %s then %s", colors.Red("in red"), colors.Yellow("yel")) + fmt.Fprint(w, s) + + expected := "text in red then yel" + if buf.String() != expected { + t.Fatalf("expected `%s` but got `%s`", expected, buf.String()) + } +} + +func TestTagBoldColorWriter(t *testing.T) { + var buf bytes.Buffer + w := &tagColorWriter{w: &buf} + + s := fmt.Sprintf( + "text %s then %s", + colors.Bold(colors.Red)("in red"), + colors.Bold(colors.Yellow)("yel"), + ) + fmt.Fprint(w, s) + + expected := "text in red then yel" + if buf.String() != expected { + t.Fatalf("expected `%s` but got `%s`", expected, buf.String()) + } +} diff --git a/formatter-tests/features/empty.feature b/formatter-tests/features/empty.feature new file mode 100644 index 0000000..97faafc --- /dev/null +++ b/formatter-tests/features/empty.feature @@ -0,0 +1 @@ +Feature: empty feature diff --git a/formatter-tests/features/empty_with_description.feature b/formatter-tests/features/empty_with_description.feature new file mode 100644 index 0000000..721769b --- /dev/null +++ b/formatter-tests/features/empty_with_description.feature @@ -0,0 +1,4 @@ +Feature: empty feature + describes + an empty + feature diff --git a/formatter-tests/features/empty_with_single_scenario_without_steps.feature b/formatter-tests/features/empty_with_single_scenario_without_steps.feature new file mode 100644 index 0000000..5bdbcb0 --- /dev/null +++ b/formatter-tests/features/empty_with_single_scenario_without_steps.feature @@ -0,0 +1,3 @@ +Feature: empty feature + + Scenario: without steps diff --git a/formatter-tests/features/empty_with_single_scenario_without_steps_and_description.feature b/formatter-tests/features/empty_with_single_scenario_without_steps_and_description.feature new file mode 100644 index 0000000..cb5c7f2 --- /dev/null +++ b/formatter-tests/features/empty_with_single_scenario_without_steps_and_description.feature @@ -0,0 +1,6 @@ +Feature: empty feature + describes + an empty + feature + + Scenario: without steps diff --git a/formatter-tests/features/single_scenario_with_passing_step.feature b/formatter-tests/features/single_scenario_with_passing_step.feature new file mode 100644 index 0000000..8cdaf17 --- /dev/null +++ b/formatter-tests/features/single_scenario_with_passing_step.feature @@ -0,0 +1,7 @@ +Feature: single passing scenario + describes + a single scenario + feature + + Scenario: one step passing + Given a passing step diff --git a/formatter-tests/pretty/empty b/formatter-tests/pretty/empty new file mode 100644 index 0000000..97faafc --- /dev/null +++ b/formatter-tests/pretty/empty @@ -0,0 +1 @@ +Feature: empty feature diff --git a/formatters_print_test.go b/formatters_print_test.go new file mode 100644 index 0000000..5d7c8c5 --- /dev/null +++ b/formatters_print_test.go @@ -0,0 +1 @@ +package godog