diff --git a/internal/app/app.go b/internal/app/app.go index 93f7f82..6303ecc 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -68,13 +68,14 @@ func Run(option Options) (int, error) { defer option.FollowOutputWriter.Close() } + progressWriter := newConsoleWriter(option.Output, option.Format, option.DisableColor) summary, err := parse.Process( reader, parse.WithFollowOutput(option.FollowOutput), parse.WithFollowVersboseOutput(option.FollowOutputVerbose), parse.WithWriter(option.FollowOutputWriter), parse.WithProgress(option.Progress), - parse.WithProgressOutput(option.ProgressOutput), + parse.WithProgressOutput(progressWriter), ) if err != nil { return 1, err diff --git a/internal/app/console_writer.go b/internal/app/console_writer.go index 7871cbe..979424b 100644 --- a/internal/app/console_writer.go +++ b/internal/app/console_writer.go @@ -2,9 +2,12 @@ package app import ( "io" + "strings" "github.com/charmbracelet/lipgloss" "github.com/muesli/termenv" + + "github.com/mfridman/tparse/parse" ) type OutputFormat int @@ -19,8 +22,8 @@ const ( ) type consoleWriter struct { + io.Writer format OutputFormat - w io.Writer red colorOptionFunc green colorOptionFunc @@ -52,7 +55,7 @@ func newConsoleWriter(w io.Writer, format OutputFormat, disableColor bool) *cons format = OutputFormatBasic } cw := &consoleWriter{ - w: w, + Writer: w, format: format, } cw.red = noColor() @@ -81,3 +84,17 @@ func newConsoleWriter(w io.Writer, format OutputFormat, disableColor bool) *cons } return cw } + +func (w *consoleWriter) FormatAction(action parse.Action) string { + s := strings.ToUpper(action.String()) + switch action { + case parse.ActionPass: + return w.green(s) + case parse.ActionSkip: + return w.yellow(s) + case parse.ActionFail: + return w.red(s) + default: + return s + } +} diff --git a/internal/app/table_failed.go b/internal/app/table_failed.go index 8bc8f57..1f11ef0 100644 --- a/internal/app/table_failed.go +++ b/internal/app/table_failed.go @@ -29,7 +29,7 @@ func (c *consoleWriter) printFailed(packages []*parse.Package) { // TODO(mf): document why panics are handled separately. A panic may or may // not be associated with tests, so we print it at the package level. output := c.prepareStyledPanic(pkg.Summary.Package, pkg.Summary.Test, pkg.PanicEvents, width) - fmt.Fprintln(c.w, output) + fmt.Fprintln(c, output) continue } failedTests := pkg.TestsByAction(parse.ActionFail) @@ -40,8 +40,8 @@ func (c *consoleWriter) printFailed(packages []*parse.Package) { pkg.Summary.Action.String(), pkg.Summary.Package, ) - fmt.Fprintln(c.w, styledPackageHeader) - fmt.Fprintln(c.w) + fmt.Fprintln(c, styledPackageHeader) + fmt.Fprintln(c) /* Failed tests are all the individual tests, where the subtests are not separated. @@ -75,20 +75,20 @@ func (c *consoleWriter) printFailed(packages []*parse.Package) { */ if c.format == OutputFormatMarkdown { - fmt.Fprintln(c.w, fencedCodeBlock) + fmt.Fprintln(c, fencedCodeBlock) } var key string for i, t := range failedTests { // Add top divider to all tests except first one. base, _, _ := cut(t.Name, "/") if i > 0 && key != base { - fmt.Fprintln(c.w, divider.String()) + fmt.Fprintln(c, divider.String()) } key = base - fmt.Fprintln(c.w, c.prepareStyledTest(t)) + fmt.Fprintln(c, c.prepareStyledTest(t)) } if c.format == OutputFormatMarkdown { - fmt.Fprint(c.w, fencedCodeBlock+"\n\n") + fmt.Fprint(c, fencedCodeBlock+"\n\n") } } } diff --git a/internal/app/table_summary.go b/internal/app/table_summary.go index 8a2741d..bb571c6 100644 --- a/internal/app/table_summary.go +++ b/internal/app/table_summary.go @@ -167,15 +167,7 @@ func (c *consoleWriter) summaryTable( } } - status := strings.ToUpper(pkg.Summary.Action.String()) - switch pkg.Summary.Action { - case parse.ActionPass: - status = c.green(status) - case parse.ActionSkip: - status = c.yellow(status) - case parse.ActionFail: - status = c.red(status) - } + status := c.FormatAction(pkg.Summary.Action) // Skip packages with no coverage to mimic nocoverageredesign behavior (changed in github.com/golang/go/issues/24570) totalTests := len(pkg.TestsByAction(parse.ActionPass)) + len(pkg.TestsByAction(parse.ActionFail)) + len(pkg.TestsByAction(parse.ActionSkip)) @@ -212,7 +204,7 @@ func (c *consoleWriter) summaryTable( } } - fmt.Fprintln(c.w, tbl.Data(data).Render()) + fmt.Fprintln(c, tbl.Data(data).Render()) } type summaryRow struct { diff --git a/internal/app/table_tests.go b/internal/app/table_tests.go index 4eb7a0f..9eeb6fa 100644 --- a/internal/app/table_tests.go +++ b/internal/app/table_tests.go @@ -87,16 +87,7 @@ func (c *consoleWriter) testsTable(packages []*parse.Package, option TestTableOp testName := shortenTestName(t.Name, option.Trim, 32) - status := strings.ToUpper(t.Status().String()) - switch t.Status() { - case parse.ActionPass: - status = c.green(status) - case parse.ActionSkip: - status = c.yellow(status) - case parse.ActionFail: - status = c.red(status) - } - + status := c.FormatAction(t.Status()) packageName := shortenPackageName(t.Package, packagePrefix, 16, option.Trim, option.TrimPath) row := testRow{ @@ -114,7 +105,7 @@ func (c *consoleWriter) testsTable(packages []*parse.Package, option TestTableOp } if data.Rows() > 0 { - fmt.Fprintln(c.w, tbl.Data(data).Render()) + fmt.Fprintln(c, tbl.Data(data).Render()) } } @@ -155,15 +146,7 @@ func (c *consoleWriter) testsTableMarkdown(packages []*parse.Package, option Tes testName := shortenTestName(t.Name, option.Trim, 32) - status := strings.ToUpper(t.Status().String()) - switch t.Status() { - case parse.ActionPass: - status = c.green(status) - case parse.ActionSkip: - status = c.yellow(status) - case parse.ActionFail: - status = c.red(status) - } + status := c.FormatAction(t.Status()) data.Append([]string{ status, strconv.FormatFloat(t.Elapsed(), 'f', 2, 64), @@ -171,8 +154,8 @@ func (c *consoleWriter) testsTableMarkdown(packages []*parse.Package, option Tes }) } if data.Rows() > 0 { - fmt.Fprintf(c.w, "## 📦 Package **`%s`**\n", pkg.Summary.Package) - fmt.Fprintln(c.w) + fmt.Fprintf(c, "## 📦 Package **`%s`**\n", pkg.Summary.Package) + fmt.Fprintln(c) msg := fmt.Sprintf("Tests: ✓ %d passed | %d skipped\n", pkgTests.passedCount, @@ -184,18 +167,18 @@ func (c *consoleWriter) testsTableMarkdown(packages []*parse.Package, option Tes pkgTests.passedCount, ) } - fmt.Fprint(c.w, msg) - - fmt.Fprintln(c.w) - fmt.Fprintln(c.w, "
") - fmt.Fprintln(c.w) - fmt.Fprintln(c.w, "Click for test summary") - fmt.Fprintln(c.w) - fmt.Fprintln(c.w, tbl.Data(data).Render()) - fmt.Fprintln(c.w, "
") - fmt.Fprintln(c.w) + fmt.Fprint(c, msg) + + fmt.Fprintln(c) + fmt.Fprintln(c, "
") + fmt.Fprintln(c) + fmt.Fprintln(c, "Click for test summary") + fmt.Fprintln(c) + fmt.Fprintln(c, tbl.Data(data).Render()) + fmt.Fprintln(c, "
") + fmt.Fprintln(c) } - fmt.Fprintln(c.w) + fmt.Fprintln(c) } } diff --git a/parse/process.go b/parse/process.go index 8c28e99..3535b72 100644 --- a/parse/process.go +++ b/parse/process.go @@ -142,7 +142,7 @@ func Process(r io.Reader, optionsFunc ...OptionsFunc) (*GoTestSummary, error) { // printProgress prints a single summary line for each PASS or FAIL package. // This is useful for long-running test suites. -func printProgress(w io.Writer, e *Event, summary map[string]*Package) { +func printProgress(w progressWriter, e *Event, summary map[string]*Package) { if !e.LastLine() { return } @@ -174,7 +174,7 @@ func printProgress(w io.Writer, e *Event, summary map[string]*Package) { // // We modify this output slightly so it's more consistent and easier to parse. fmt.Fprintf(w, "[%s]\t%10s\t%s%s\n", - strings.ToUpper(action.String()), + w.FormatAction(action), strconv.FormatFloat(e.Elapsed, 'f', 2, 64)+"s", e.Package, suffix, diff --git a/parse/process_options.go b/parse/process_options.go index 8a63b5c..1dab626 100644 --- a/parse/process_options.go +++ b/parse/process_options.go @@ -4,6 +4,11 @@ import ( "io" ) +type progressWriter interface { + io.Writer + FormatAction(Action) string +} + type options struct { w io.Writer follow bool @@ -11,7 +16,7 @@ type options struct { debug bool progress bool - progressOutput io.Writer + progressOutput progressWriter } type OptionsFunc func(o *options) @@ -36,6 +41,6 @@ func WithProgress(b bool) OptionsFunc { return func(o *options) { o.progress = b } } -func WithProgressOutput(w io.Writer) OptionsFunc { +func WithProgressOutput(w progressWriter) OptionsFunc { return func(o *options) { o.progressOutput = w } }