diff --git a/cmd/handler.go b/cmd/handler.go index 8c472056..74a2ae30 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -82,6 +82,11 @@ func (h *eventHandler) Flush() { } func (h *eventHandler) Close() error { + if closer, ok := h.formatter.(io.Closer); ok { + if err := closer.Close(); err != nil { + log.Errorf("Failed to close formatter: %v", err) + } + } if h.jsonFile != nil { if err := h.jsonFile.Close(); err != nil { log.Errorf("Failed to close JSON file: %v", err) @@ -107,6 +112,14 @@ func newEventHandler(opts *options) (*eventHandler, error) { err: bufio.NewWriter(opts.stderr), maxFails: opts.maxFails, } + + switch opts.format { + case "dots", "dots-v1", "dots-v2": + // Discard the error from the handler to prevent extra lines. The + // error will be printed in the summary. + handler.err = bufio.NewWriter(io.Discard) + } + var err error if opts.jsonFile != "" { _ = os.MkdirAll(filepath.Dir(opts.jsonFile), 0o755) diff --git a/cmd/main.go b/cmd/main.go index 57d5bca6..cabe7168 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -206,7 +206,7 @@ var defaultNoColor = func() bool { // try to detect these CI environments via their environment variables. // This code is based on https://github.com/jwalton/go-supportscolor if _, exists := os.LookupEnv("CI"); exists { - var ciEnvNames = []string{ + ciEnvNames := []string{ "APPVEYOR", "BUILDKITE", "CIRCLECI", @@ -255,7 +255,7 @@ func run(opts *options) error { if err != nil { return err } - defer handler.Close() // nolint: errcheck + // defer handler.Close() // nolint: errcheck cfg := testjson.ScanConfig{ Stdout: goTestProc.stdout, Stderr: goTestProc.stderr, @@ -263,38 +263,45 @@ func run(opts *options) error { Stop: cancel, IgnoreNonJSONOutputLines: opts.ignoreNonJSONOutputLines, } + exec, err := testjson.ScanTestOutput(cfg) - handler.Flush() - if err != nil { - return finishRun(opts, exec, err) - } + // Run the rest in a function so we can simplify error handling and ensure we always Close the handler + // before we call finishRun. + runErr := func() error { + defer handler.Close() + handler.Flush() + if err != nil { + return err + } - exitErr := goTestProc.cmd.Wait() - if signum := atomic.LoadInt32(&goTestProc.signal); signum != 0 { - return finishRun(opts, exec, exitError{num: signalExitCode + int(signum)}) - } - if exitErr == nil || opts.rerunFailsMaxAttempts == 0 { - return finishRun(opts, exec, exitErr) - } - if err := hasErrors(exitErr, exec); err != nil { - return finishRun(opts, exec, err) - } + exitErr := goTestProc.cmd.Wait() + if signum := atomic.LoadInt32(&goTestProc.signal); signum != 0 { + return exitError{num: signalExitCode + int(signum)} + } + if exitErr == nil || opts.rerunFailsMaxAttempts == 0 { + return exitErr + } + if err := hasErrors(exitErr, exec); err != nil { + return err + } - failed := len(rerunFailsFilter(opts)(exec.Failed())) - if failed > opts.rerunFailsMaxInitialFailures { - err := fmt.Errorf( - "number of test failures (%d) exceeds maximum (%d) set by --rerun-fails-max-failures", - failed, opts.rerunFailsMaxInitialFailures) - return finishRun(opts, exec, err) - } + failed := len(rerunFailsFilter(opts)(exec.Failed())) + if failed > opts.rerunFailsMaxInitialFailures { + err := fmt.Errorf( + "number of test failures (%d) exceeds maximum (%d) set by --rerun-fails-max-failures", + failed, opts.rerunFailsMaxInitialFailures) + return err + } - cfg = testjson.ScanConfig{Execution: exec, Handler: handler} - exitErr = rerunFailed(ctx, opts, cfg) - handler.Flush() - if err := writeRerunFailsReport(opts, exec); err != nil { - return err - } - return finishRun(opts, exec, exitErr) + cfg = testjson.ScanConfig{Execution: exec, Handler: handler} + exitErr = rerunFailed(ctx, opts, cfg) + handler.Flush() + if err := writeRerunFailsReport(opts, exec); err != nil { + return err + } + return exitErr + }() + return finishRun(opts, exec, runErr) } func finishRun(opts *options, exec *testjson.Execution, exitErr error) error { diff --git a/cmd/testdata/gotestsum-help-text b/cmd/testdata/gotestsum-help-text index 048054d7..5fbbc8ca 100644 --- a/cmd/testdata/gotestsum-help-text +++ b/cmd/testdata/gotestsum-help-text @@ -18,7 +18,7 @@ Flags: --junitfile-testcase-classname field-format format the testcase classname field as: full, relative, short (default full) --junitfile-testsuite-name field-format format the testsuite name field as: full, relative, short (default full) --max-fails int end the test run after this number of failures - --no-color disable color output + --no-color disable color output (default true) --packages list space separated list of package to test --post-run-command command command to run after the tests have completed --raw-command don't prepend 'go test -json' to the 'go test' command diff --git a/go.mod b/go.mod index 86e78cdc..d62d7b37 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/fsnotify/fsnotify v1.5.4 github.com/google/go-cmp v0.5.8 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/mattn/go-colorable v0.1.12 // indirect golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 @@ -14,4 +13,10 @@ require ( gotest.tools/v3 v3.3.0 ) -go 1.13 +require ( + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect +) + +go 1.18 diff --git a/go.sum b/go.sum index 8f8ed3ba..33b5c179 100644 --- a/go.sum +++ b/go.sum @@ -17,22 +17,17 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -40,23 +35,16 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= diff --git a/internal/dotwriter/writer.go b/internal/dotwriter/writer.go index 2bc3a26b..e2a57752 100644 --- a/internal/dotwriter/writer.go +++ b/internal/dotwriter/writer.go @@ -5,8 +5,10 @@ terminal. package dotwriter import ( + "bufio" "bytes" "io" + "time" ) // ESC is the ASCII code for escape character @@ -17,12 +19,16 @@ const ESC = 27 type Writer struct { out io.Writer buf bytes.Buffer + last []byte lineCount int + t *time.Timer } // New returns a new Writer func New(out io.Writer) *Writer { - return &Writer{out: out} + out = bufio.NewWriter(out) + w := &Writer{out: out} + return w } // Flush the buffer, writing all buffered lines to out @@ -30,10 +36,14 @@ func (w *Writer) Flush() error { if w.buf.Len() == 0 { return nil } + w.hideCursor() + b := w.buf.Bytes() w.clearLines(w.lineCount) - w.lineCount = bytes.Count(w.buf.Bytes(), []byte{'\n'}) - _, err := w.out.Write(w.buf.Bytes()) + w.lineCount = bytes.Count(b, []byte{'\n'}) + _, err := w.out.Write(b) + w.showCursor() w.buf.Reset() + w.out.(*bufio.Writer).Flush() return err } diff --git a/internal/dotwriter/writer_posix.go b/internal/dotwriter/writer_posix.go index 9136866e..ccc92ec9 100644 --- a/internal/dotwriter/writer_posix.go +++ b/internal/dotwriter/writer_posix.go @@ -10,7 +10,17 @@ import ( // clear the line and move the cursor up var clear = fmt.Sprintf("%c[%dA%c[2K", ESC, 1, ESC) +var hide = fmt.Sprintf("%c[?25l", ESC) +var show = fmt.Sprintf("%c[?25h", ESC) func (w *Writer) clearLines(count int) { _, _ = fmt.Fprint(w.out, strings.Repeat(clear, count)) } + +// hideCursor hides the cursor and returns a function to restore the cursor back. +func (w *Writer) hideCursor() { + _, _ = fmt.Fprint(w.out, hide) +} +func (w *Writer) showCursor() { + _, _ = fmt.Fprint(w.out, show) +} diff --git a/internal/dotwriter/writer_windows.go b/internal/dotwriter/writer_windows.go index fa2e4a1e..a56885fb 100644 --- a/internal/dotwriter/writer_windows.go +++ b/internal/dotwriter/writer_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package dotwriter @@ -70,3 +71,8 @@ func isConsole(fd uintptr) bool { err := windows.GetConsoleMode(windows.Handle(fd), &mode) return err == nil } + +// This may work on Windows but I am not sure how to do it and its optional. For now, just do nothing. +func (w *Writer) hideCursor() func() { + return func() {} +} diff --git a/testjson/dotformat.go b/testjson/dotformat.go index 625cd87a..7d5d7de9 100644 --- a/testjson/dotformat.go +++ b/testjson/dotformat.go @@ -1,12 +1,13 @@ package testjson import ( - "bufio" + "bytes" "fmt" "io" "os" - "sort" + "slices" "strings" + "sync" "time" "golang.org/x/term" @@ -14,54 +15,36 @@ import ( "gotest.tools/gotestsum/internal/log" ) -func dotsFormatV1(out io.Writer) EventFormatter { - buf := bufio.NewWriter(out) - // nolint:errcheck - return eventFormatterFunc(func(event TestEvent, exec *Execution) error { - pkg := exec.Package(event.Package) - switch { - case event.PackageEvent(): - return nil - case event.Action == ActionRun && pkg.Total == 1: - buf.WriteString("[" + RelativePackagePath(event.Package) + "]") - return buf.Flush() - } - buf.WriteString(fmtDot(event)) - return buf.Flush() - }) -} - -func fmtDot(event TestEvent) string { - withColor := colorEvent(event) - switch event.Action { - case ActionPass: - return withColor("·") - case ActionFail: - return withColor("✖") - case ActionSkip: - return withColor("↷") - } - return "" -} - type dotFormatter struct { - pkgs map[string]*dotLine - order []string - writer *dotwriter.Writer - opts FormatOptions - termWidth int + pkgs map[string]*dotLine + order []string + writer *dotwriter.Writer + opts FormatOptions + termWidth int + termHeight int + stop chan struct{} + flushed chan struct{} + mu sync.RWMutex + last string + summary []byte } type dotLine struct { runes int builder *strings.Builder lastUpdate time.Time + out string + empty bool + result Action } func (l *dotLine) update(dot string) { if dot == "" { return } + if l.runes == -1 { // Stop once we hit max length. TODO: add back line wrapping. + return + } l.builder.WriteString(dot) l.runes++ } @@ -69,27 +52,39 @@ func (l *dotLine) update(dot string) { // checkWidth marks the line as full when the width of the line hits the // terminal width. func (l *dotLine) checkWidth(prefix, terminal int) { - if prefix+l.runes >= terminal { - l.builder.WriteString("\n" + strings.Repeat(" ", prefix)) - l.runes = 0 + if prefix+l.runes >= terminal-1 { + l.runes = -1 } } func newDotFormatter(out io.Writer, opts FormatOptions) EventFormatter { - w, _, err := term.GetSize(int(os.Stdout.Fd())) + w, h, err := term.GetSize(int(os.Stdout.Fd())) if err != nil || w == 0 { log.Warnf("Failed to detect terminal width for dots format, error: %v", err) return dotsFormatV1(out) } - return &dotFormatter{ - pkgs: make(map[string]*dotLine), - writer: dotwriter.New(out), - termWidth: w, - opts: opts, + f := &dotFormatter{ + pkgs: make(map[string]*dotLine), + writer: dotwriter.New(out), + termWidth: w, + termHeight: h - 10, + opts: opts, + stop: make(chan struct{}), + flushed: make(chan struct{}), } + go f.runWriter() + return f +} + +func (d *dotFormatter) Close() error { + close(d.stop) + <-d.flushed // Wait until we write the last data + return nil } func (d *dotFormatter) Format(event TestEvent, exec *Execution) error { + d.mu.Lock() + defer d.mu.Unlock() if d.pkgs[event.Package] == nil { d.pkgs[event.Package] = &dotLine{builder: new(strings.Builder)} d.order = append(d.order, event.Package) @@ -100,39 +95,114 @@ func (d *dotFormatter) Format(event TestEvent, exec *Execution) error { if !event.PackageEvent() { line.update(fmtDot(event)) } - switch event.Action { - case ActionOutput, ActionBench: - return nil + pkg := exec.Package(event.Package) + + pkgname := RelativePackagePath(event.Package) + " " + prefix := fmtDotElapsed(pkg) + line.checkWidth(len(prefix+pkgname), d.termWidth) + line.checkWidth(len(prefix+pkgname), d.termWidth) + line.out = prefix + pkgname + line.builder.String() + line.result = pkg.Result() + + line.empty = pkg.IsEmpty() + buf := bytes.Buffer{} + PrintSummary(&buf, exec, SummarizeNone) + d.summary = buf.Bytes() + + return nil +} + +func (d *dotFormatter) runWriter() { + t := time.NewTicker(time.Millisecond * 100) + for { + select { + case <-d.stop: + if err := d.write(); err != nil { + log.Warnf("failed to write: %v", err) + } + close(d.flushed) + return + case <-t.C: + if err := d.write(); err != nil { + log.Warnf("failed to write: %v", err) + } + } } +} - // Add an empty header to work around incorrect line counting - fmt.Fprint(d.writer, "\n\n") +func (d *dotFormatter) write() error { + d.mu.RLock() // TODO: lock is not sufficient, we need to read from d.exec in the event handler. + defer d.mu.RUnlock() - sort.Slice(d.order, d.orderByLastUpdated) + // TODO summary time should update on each iteration ideally. Although that drops our "skip" optimization + summaryLines := strings.Split(string(d.summary), "\n") + + packageLines := []*dotLine{} for _, pkg := range d.order { - if d.opts.HideEmptyPackages && exec.Package(pkg).IsEmpty() { + line := d.pkgs[pkg] + if d.opts.HideEmptyPackages && line.empty { continue } - line := d.pkgs[pkg] - pkgname := RelativePackagePath(pkg) + " " - prefix := fmtDotElapsed(exec.Package(pkg)) - line.checkWidth(len(prefix+pkgname), d.termWidth) - fmt.Fprintf(d.writer, prefix+pkgname+line.builder.String()+"\n") + packageLines = append(packageLines, line) + } + maxTestLines := d.termHeight - len(summaryLines) + lines := filterLines(packageLines, maxTestLines) + lines = append(lines, summaryLines...) + res := strings.Join(lines, "\n") + if res == d.last { + return nil } - PrintSummary(d.writer, exec, SummarizeNone) + d.last = res + + // Write empty lines for some padding + fmt.Fprint(d.writer, "\n") + d.writer.Write([]byte(res)) + return d.writer.Flush() } -// orderByLastUpdated so that the most recently updated packages move to the -// bottom of the list, leaving completed package in the same order at the top. -func (d *dotFormatter) orderByLastUpdated(i, j int) bool { - return d.pkgs[d.order[i]].lastUpdate.Before(d.pkgs[d.order[j]].lastUpdate) +// filterLines picks which lines to show. +// In general, we want to show as many lines as possible, but we have some height constraints. +// If we have more lines than we can display, we need to be pickier. +// We will take the last lines, but prioritize in progress packages. +func filterLines(lines []*dotLine, budget int) []string { + res := []string{} + inProgress := 0 + for _, l := range lines { + if !l.result.IsTerminal() { + inProgress++ + } + } + terminalToDisplay := budget - inProgress + for i := len(lines) - 1; i >= 0 && budget > 0; i-- { + l := lines[i] + if l.result.IsTerminal() { + if terminalToDisplay <= 0 { + continue + } + terminalToDisplay-- + } + budget-- + res = append(res, l.out) + } + slices.Reverse(res) + return res } func fmtDotElapsed(p *Package) string { f := func(v string) string { - return fmt.Sprintf(" %5s ", v) + c := colorEvent(TestEvent{Action: p.Result()}) + act := c("⏱️") + switch p.Result() { + case ActionPass: + act = c("✔️") + case ActionFail: + act = c("✖") + case ActionSkip: + act = c("↷") + } + return fmt.Sprintf(" %5s %v ", v, act) } elapsed := p.Elapsed() @@ -148,7 +218,7 @@ func fmtDotElapsed(p *Package) string { } const maxWidth = 7 - var steps = []time.Duration{ + steps := []time.Duration{ time.Millisecond, 10 * time.Millisecond, 100 * time.Millisecond, diff --git a/testjson/dotformat_test.go b/testjson/dotformat_test.go index e3f0c2c6..86d7423e 100644 --- a/testjson/dotformat_test.go +++ b/testjson/dotformat_test.go @@ -22,13 +22,15 @@ func TestScanTestOutput_WithDotsFormatter(t *testing.T) { out := new(bytes.Buffer) dotfmt := &dotFormatter{ - pkgs: make(map[string]*dotLine), - writer: dotwriter.New(out), - termWidth: 80, + pkgs: make(map[string]*dotLine), + writer: dotwriter.New(out), + termWidth: 80, + termHeight: 80, } shim := newFakeHandler(dotfmt, "input/go-test-json") _, err := ScanTestOutput(shim.Config(t)) assert.NilError(t, err) + assert.NilError(t, dotfmt.write()) actual := text.ProcessLines(t, out, text.OpRemoveSummaryLineElapsedTime) golden.Assert(t, actual, "format/dots-v2.out") diff --git a/testjson/dotv1format.go b/testjson/dotv1format.go new file mode 100644 index 00000000..0449dde0 --- /dev/null +++ b/testjson/dotv1format.go @@ -0,0 +1,36 @@ +package testjson + +import ( + "bufio" + "io" +) + +func dotsFormatV1(out io.Writer) EventFormatter { + buf := bufio.NewWriter(out) + // nolint:errcheck + return eventFormatterFunc(func(event TestEvent, exec *Execution) error { + pkg := exec.Package(event.Package) + switch { + case event.PackageEvent(): + return nil + case event.Action == ActionRun && pkg.Total == 1: + buf.WriteString("[" + RelativePackagePath(event.Package) + "]") + return buf.Flush() + } + buf.WriteString(fmtDot(event)) + return buf.Flush() + }) +} + +func fmtDot(event TestEvent) string { + withColor := colorEvent(event) + switch event.Action { + case ActionPass: + return withColor("·") + case ActionFail: + return withColor("✖") + case ActionSkip: + return withColor("↷") + } + return "" +} diff --git a/testjson/testdata/format/dots-v2.out b/testjson/testdata/format/dots-v2.out index 72eb8824..54bea428 100644 --- a/testjson/testdata/format/dots-v2.out +++ b/testjson/testdata/format/dots-v2.out @@ -1,1209 +1,11 @@ - - - 1ms testjson/internal/badmain - - 0 tests, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - - 0 tests, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good - - 1 tests, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good · - - 1 tests, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good · - - 2 tests, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ·· - - 2 tests, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ·· - - 3 tests, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ··· - - 3 tests, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ··· - - 4 tests, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷ - - 4 tests, 1 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷ - - 5 tests, 1 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷ - - 5 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷ - - 6 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 6 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 7 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 7 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 8 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 8 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 9 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 9 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 10 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 11 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 12 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 13 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 14 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 15 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 16 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 17 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷·· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷··· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷···· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷····· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷······ - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷······· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷········ - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷········· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷·········· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷·········· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷··········· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷··········· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷··········· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷············ - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - testjson/internal/good ···↷↷············· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - - 18 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails - - 19 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails · - - 19 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails · - - 20 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ·· - - 20 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ·· - - 21 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ··· - - 21 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ··· - - 22 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 22 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 23 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 23 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 24 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 24 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 25 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 25 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 26 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 27 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 27 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 28 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 28 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 29 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 29 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 30 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 30 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 30 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 30 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 30 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ···· - - 30 tests, 2 skipped, 1 failure, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ····✖ - - 30 tests, 2 skipped, 2 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ····✖✖ - - 30 tests, 2 skipped, 3 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ····✖✖✖ - - 30 tests, 2 skipped, 4 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ····✖✖✖✖ - - 30 tests, 2 skipped, 5 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ····✖✖✖✖✖ - - 30 tests, 2 skipped, 6 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ····✖✖✖✖✖ - - 30 tests, 2 skipped, 6 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ····✖✖✖✖✖✖ - - 30 tests, 2 skipped, 7 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ····✖✖✖✖✖✖ - - 30 tests, 2 skipped, 7 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ····✖✖✖✖✖✖✖ - - 30 tests, 2 skipped, 8 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ····✖✖✖✖✖✖✖ - - 30 tests, 2 skipped, 8 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - - 30 tests, 2 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - - 30 tests, 2 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails - - 31 tests, 2 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails · - - 31 tests, 2 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails · - - 32 tests, 2 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ·· - - 32 tests, 2 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ·· - - 33 tests, 2 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ··· - - 33 tests, 2 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ··· - - 34 tests, 2 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷ - - 34 tests, 3 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷ - - 35 tests, 3 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷ - - 35 tests, 4 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷ - - 36 tests, 4 skipped, 9 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖ - - 36 tests, 4 skipped, 10 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖ - - 37 tests, 4 skipped, 10 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖· - - 37 tests, 4 skipped, 10 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖· - - 38 tests, 4 skipped, 10 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 38 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 39 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 39 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 40 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 40 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 41 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 41 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 42 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 43 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 44 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 45 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 46 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 47 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 48 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖ - - 49 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖· - - 49 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖·· - - 49 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖··· - - 49 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖···· - - 49 tests, 4 skipped, 11 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖ - - 49 tests, 4 skipped, 12 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖· - - 49 tests, 4 skipped, 12 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖·· - - 49 tests, 4 skipped, 12 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖ - - 49 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖ - - 50 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖ - - 51 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖ - - 52 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖ - - 53 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖ - - 54 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖ - - 55 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖ - - 56 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖ - - 57 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖ - - 58 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖· - - 58 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖·· - - 58 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖··· - - 58 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖···· - - 58 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖····· - - 58 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖······ - - 58 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖······· - - 58 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖········ - - 58 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖········· - - 58 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖········· - - 59 tests, 4 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖·········↷ - - 59 tests, 5 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖·········↷ - - 59 tests, 5 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖·········↷· - - 59 tests, 5 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖·········↷· - - 59 tests, 5 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖·········↷·· - - 59 tests, 5 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖·········↷·· - - 59 tests, 5 skipped, 13 failures, 1 error - - - 1ms testjson/internal/badmain - 🖴 testjson/internal/empty - 🖴 testjson/internal/good ···↷↷············· - 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ - testjson/internal/withfails ···↷↷✖·✖····✖··✖·········↷··· - - 59 tests, 5 skipped, 13 failures, 1 error - - +[?25l +1 height: 80, orders: 5, skips: 0 1ms testjson/internal/badmain 🖴 testjson/internal/empty 🖴 testjson/internal/good ···↷↷············· 20ms testjson/internal/parallelfails ····✖✖✖✖✖✖✖✖ 20ms testjson/internal/withfails ···↷↷✖·✖····✖··✖·········↷··· + 59 tests, 5 skipped, 13 failures, 1 error +[?25h