Skip to content

Commit

Permalink
dotsv2: improvements to reduce text jitteriness
Browse files Browse the repository at this point in the history
  • Loading branch information
howardjohn committed Aug 15, 2023
1 parent 0e788d2 commit 5b4c2d5
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 1,318 deletions.
13 changes: 13 additions & 0 deletions cmd/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
67 changes: 37 additions & 30 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -255,46 +255,53 @@ 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,
Handler: handler,
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 {
Expand Down
2 changes: 1 addition & 1 deletion cmd/testdata/gotestsum-help-text
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ 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
golang.org/x/tools v0.1.11
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
12 changes: 0 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,46 +17,34 @@ 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=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
Expand Down
16 changes: 13 additions & 3 deletions internal/dotwriter/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ terminal.
package dotwriter

import (
"bufio"
"bytes"
"io"
"time"
)

// ESC is the ASCII code for escape character
Expand All @@ -17,23 +19,31 @@ 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
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
}

Expand Down
10 changes: 10 additions & 0 deletions internal/dotwriter/writer_posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
6 changes: 6 additions & 0 deletions internal/dotwriter/writer_windows.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build windows
// +build windows

package dotwriter
Expand Down Expand Up @@ -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() {}
}
Loading

0 comments on commit 5b4c2d5

Please sign in to comment.