123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- package testjson
- import (
- "fmt"
- "io"
- "strings"
- "time"
- "unicode"
- "unicode/utf8"
- "github.com/fatih/color"
- )
- // Summary enumerates the sections which can be printed by PrintSummary
- type Summary int
- // nolint: golint
- const (
- SummarizeNone Summary = 0
- SummarizeSkipped Summary = (1 << iota) / 2
- SummarizeFailed
- SummarizeErrors
- SummarizeOutput
- SummarizeAll = SummarizeSkipped | SummarizeFailed | SummarizeErrors | SummarizeOutput
- )
- var summaryValues = map[Summary]string{
- SummarizeSkipped: "skipped",
- SummarizeFailed: "failed",
- SummarizeErrors: "errors",
- SummarizeOutput: "output",
- }
- var summaryFromValue = map[string]Summary{
- "none": SummarizeNone,
- "skipped": SummarizeSkipped,
- "failed": SummarizeFailed,
- "errors": SummarizeErrors,
- "output": SummarizeOutput,
- "all": SummarizeAll,
- }
- func (s Summary) String() string {
- if s == SummarizeNone {
- return "none"
- }
- var result []string
- for v := Summary(1); v <= s; v <<= 1 {
- if s.Includes(v) {
- result = append(result, summaryValues[v])
- }
- }
- return strings.Join(result, ",")
- }
- // Includes returns true if Summary includes all the values set by other.
- func (s Summary) Includes(other Summary) bool {
- return s&other == other
- }
- // NewSummary returns a new Summary from a string value. If the string does not
- // match any known values returns false for the second value.
- func NewSummary(value string) (Summary, bool) {
- s, ok := summaryFromValue[value]
- return s, ok
- }
- // PrintSummary of a test Execution. Prints a section for each summary type
- // followed by a DONE line.
- func PrintSummary(out io.Writer, execution *Execution, opts Summary) error {
- execSummary := newExecSummary(execution, opts)
- if opts.Includes(SummarizeSkipped) {
- writeTestCaseSummary(out, execSummary, formatSkipped())
- }
- if opts.Includes(SummarizeFailed) {
- writeTestCaseSummary(out, execSummary, formatFailed())
- }
- errors := execution.Errors()
- if opts.Includes(SummarizeErrors) {
- writeErrorSummary(out, errors)
- }
- fmt.Fprintf(out, "\n%s %d tests%s%s%s in %s\n",
- "DONE", // TODO: maybe color this?
- execution.Total(),
- formatTestCount(len(execution.Skipped()), "skipped", ""),
- formatTestCount(len(execution.Failed()), "failure", "s"),
- formatTestCount(countErrors(errors), "error", "s"),
- FormatDurationAsSeconds(execution.Elapsed(), 3))
- return nil
- }
- func formatTestCount(count int, category string, pluralize string) string {
- switch count {
- case 0:
- return ""
- case 1:
- default:
- category += pluralize
- }
- return fmt.Sprintf(", %d %s", count, category)
- }
- // FormatDurationAsSeconds formats a time.Duration as a float with an s suffix.
- func FormatDurationAsSeconds(d time.Duration, precision int) string {
- return fmt.Sprintf("%.[2]*[1]fs", d.Seconds(), precision)
- }
- func writeErrorSummary(out io.Writer, errors []string) {
- if len(errors) > 0 {
- fmt.Fprintln(out, color.MagentaString("\n=== Errors"))
- }
- for _, err := range errors {
- fmt.Fprintln(out, err)
- }
- }
- // countErrors in stderr lines. Build errors may include multiple lines where
- // subsequent lines are indented.
- // FIXME: Panics will include multiple lines, and are still overcounted.
- func countErrors(errors []string) int {
- var count int
- for _, line := range errors {
- r, _ := utf8.DecodeRuneInString(line)
- if !unicode.IsSpace(r) {
- count++
- }
- }
- return count
- }
- type executionSummary interface {
- Failed() []TestCase
- Skipped() []TestCase
- OutputLines(pkg, test string) []string
- }
- type noOutputSummary struct {
- Execution
- }
- func (s *noOutputSummary) OutputLines(_, _ string) []string {
- return nil
- }
- func newExecSummary(execution *Execution, opts Summary) executionSummary {
- if opts.Includes(SummarizeOutput) {
- return execution
- }
- return &noOutputSummary{Execution: *execution}
- }
- func writeTestCaseSummary(out io.Writer, execution executionSummary, conf testCaseFormatConfig) {
- testCases := conf.getter(execution)
- if len(testCases) == 0 {
- return
- }
- fmt.Fprintln(out, "\n=== "+conf.header)
- for _, tc := range testCases {
- fmt.Fprintf(out, "=== %s: %s %s (%s)\n",
- conf.prefix,
- relativePackagePath(tc.Package),
- tc.Test,
- FormatDurationAsSeconds(tc.Elapsed, 2))
- for _, line := range execution.OutputLines(tc.Package, tc.Test) {
- if isRunLine(line) || conf.filter(line) {
- continue
- }
- fmt.Fprint(out, line)
- }
- fmt.Fprintln(out)
- }
- }
- type testCaseFormatConfig struct {
- header string
- prefix string
- filter func(string) bool
- getter func(executionSummary) []TestCase
- }
- func formatFailed() testCaseFormatConfig {
- withColor := color.RedString
- return testCaseFormatConfig{
- header: withColor("Failed"),
- prefix: withColor("FAIL"),
- filter: func(line string) bool {
- return strings.HasPrefix(line, "--- FAIL: Test")
- },
- getter: func(execution executionSummary) []TestCase {
- return execution.Failed()
- },
- }
- }
- func formatSkipped() testCaseFormatConfig {
- withColor := color.YellowString
- return testCaseFormatConfig{
- header: withColor("Skipped"),
- prefix: withColor("SKIP"),
- filter: func(line string) bool {
- return strings.HasPrefix(line, "--- SKIP: Test")
- },
- getter: func(execution executionSummary) []TestCase {
- return execution.Skipped()
- },
- }
- }
- func isRunLine(line string) bool {
- return strings.HasPrefix(line, "=== RUN Test")
- }
|