Skip to content

Commit

Permalink
Merge pull request #249 from dnephin/add-update-flag
Browse files Browse the repository at this point in the history
watch: Add u key for updating golden values
  • Loading branch information
dnephin authored May 8, 2022
2 parents f1fdcbd + 849289b commit e209ec9
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 29 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# gotestsum

`gotestsum` runs tests using `go test --json`, prints formatted test output, and a summary of the test run.
`gotestsum` runs tests using `go test -json`, prints formatted test output, and a summary of the test run.
It is designed to work well for both local development, and for automation like CI.
`gotest.tools/gotestsum/testjson` ([godoc](https://pkg.go.dev/gotest.tools/gotestsum/testjson)) is a library
that can be used to read [`test2json`](https://golang.org/cmd/test2json/) output.
Expand Down Expand Up @@ -112,7 +112,7 @@ warning.
When the `--jsonfile` flag or `GOTESTSUM_JSONFILE` environment variable are set
to a file path, `gotestsum` will write a line-delimited JSON file with all the
[test2json](https://golang.org/cmd/test2json/#hdr-Output_Format)
output that was written by `go test --json`. This file can be used to compare test
output that was written by `go test -json`. This file can be used to compare test
runs, or find flaky tests.

```
Expand Down Expand Up @@ -202,7 +202,7 @@ how you specify args to `go test`:

### Custom `go test` command

By default `gotestsum` runs tests using the command `go test --json ./...`. You
By default `gotestsum` runs tests using the command `go test -json ./...`. You
can change the command with positional arguments after a `--`. You can change just the
test directory value (which defaults to `./...`) by setting the `TEST_DIRECTORY`
environment variable.
Expand Down Expand Up @@ -354,6 +354,10 @@ While in watch mode, pressing some keys will perform an action:

* `r` will run tests for the previous event.
Added in version 1.6.1.
* `u` will run tests for the previous event, with the `-update` flag added.
Many [golden](https://gotest.tools/v3/golden) packages use this flag to automatically
update expected values of tests.
Added in version 1.8.1.
* `d` will run tests for the previous event using `dlv test`, allowing you to
debug a test failure using [delve]. A breakpoint will automatically be added at
the first line of any tests which failed in the previous run. Additional
Expand Down
12 changes: 9 additions & 3 deletions cmd/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import (
)

func runWatcher(opts *options) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

w := &watchRuns{opts: *opts}
return filewatcher.Watch(opts.packages, w.run)
return filewatcher.Watch(ctx, opts.packages, w.run)
}

type watchRuns struct {
Expand All @@ -40,8 +43,11 @@ func (w *watchRuns) run(event filewatcher.Event) error {
return nil
}

opts := w.opts
opts.packages = []string{event.PkgPath}
opts := w.opts // shallow copy opts
opts.packages = append([]string{}, opts.packages...)
opts.packages = append(opts.packages, event.PkgPath)
opts.packages = append(opts.packages, event.Args...)

var err error
if w.prevExec, err = runSingle(&opts); !IsExitCoder(err) {
return err
Expand Down
17 changes: 9 additions & 8 deletions internal/filewatcher/term_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"bufio"
"context"
"fmt"
"io"
"os"

"golang.org/x/sys/unix"
Expand Down Expand Up @@ -62,13 +63,15 @@ func enableNonBlockingRead(fd int) (func(), error) {
return reset, nil
}

var stdin io.Reader = os.Stdin

// Monitor the terminal for key presses. If the key press is associated with an
// action, an event will be sent to channel returned by Events.
func (r *terminal) Monitor(ctx context.Context) {
if r == nil {
return
}
in := bufio.NewReader(os.Stdin)
in := bufio.NewReader(stdin)
for {
char, err := in.ReadByte()
if err != nil {
Expand All @@ -77,20 +80,18 @@ func (r *terminal) Monitor(ctx context.Context) {
}
log.Debugf("received byte %v (%v)", char, string(char))

var chResume chan struct{}
chResume := make(chan struct{})
switch char {
case 'r':
chResume = make(chan struct{})
r.ch <- Event{resume: chResume}
r.ch <- Event{resume: chResume, useLastPath: true}
case 'd':
chResume = make(chan struct{})
r.ch <- Event{Debug: true, resume: chResume}
r.ch <- Event{resume: chResume, useLastPath: true, Debug: true}
case 'a':
chResume = make(chan struct{})
r.ch <- Event{resume: chResume, PkgPath: "./..."}
case 'l':
chResume = make(chan struct{})
r.ch <- Event{resume: chResume, reloadPaths: true}
case 'u':
r.ch <- Event{resume: chResume, useLastPath: true, Args: []string{"-update"}}
case '\n':
fmt.Println()
continue
Expand Down
35 changes: 22 additions & 13 deletions internal/filewatcher/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,25 @@ import (
const maxDepth = 7

type Event struct {
PkgPath string
Debug bool
resume chan struct{}
// PkgPath of the package that triggered the event.
PkgPath string
// Args will be appended to the command line args for 'go test'.
Args []string
// Debug runs the tests with delve.
Debug bool
// resume the Watch goroutine when this channel is closed. Used to block
// the Watch goroutine while tests are running.
resume chan struct{}
// reloadPaths will cause the watched path list to be reloaded, to watch
// new directories.
reloadPaths bool
// useLastPath when true will use the PkgPath from the previous run.
useLastPath bool
}

// Watch dirs for filesystem events, and run tests when .go files are saved.
// nolint: gocyclo
func Watch(dirs []string, run func(Event) error) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

func Watch(ctx context.Context, dirs []string, run func(Event) error) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return fmt.Errorf("failed to create file watcher: %w", err)
Expand All @@ -48,9 +55,11 @@ func Watch(dirs []string, run func(Event) error) error {
defer term.Reset()
go term.Monitor(ctx)

h := &handler{last: time.Now(), fn: run}
h := &fsEventHandler{last: time.Now(), fn: run}
for {
select {
case <-ctx.Done():
return nil
case <-timer.C:
return fmt.Errorf("exceeded idle timeout while watching files")

Expand Down Expand Up @@ -218,15 +227,15 @@ func handleDirCreated(watcher *fsnotify.Watcher, event fsnotify.Event) (handled
return true
}

type handler struct {
type fsEventHandler struct {
last time.Time
lastPath string
fn func(opts Event) error
}

const floodThreshold = 250 * time.Millisecond
var floodThreshold = 250 * time.Millisecond

func (h *handler) handleEvent(event fsnotify.Event) error {
func (h *fsEventHandler) handleEvent(event fsnotify.Event) error {
if event.Op&(fsnotify.Write|fsnotify.Create) == 0 {
return nil
}
Expand All @@ -242,8 +251,8 @@ func (h *handler) handleEvent(event fsnotify.Event) error {
return h.runTests(Event{PkgPath: "./" + filepath.Dir(event.Name)})
}

func (h *handler) runTests(opts Event) error {
if opts.PkgPath == "" {
func (h *fsEventHandler) runTests(opts Event) error {
if opts.useLastPath {
opts.PkgPath = h.lastPath
}
fmt.Printf("\nRunning tests in %v\n", opts.PkgPath)
Expand Down
4 changes: 2 additions & 2 deletions internal/filewatcher/watch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"gotest.tools/v3/fs"
)

func TestHandler_HandleEvent(t *testing.T) {
func TestFSEventHandler_HandleEvent(t *testing.T) {
type testCase struct {
name string
last time.Time
Expand All @@ -27,7 +27,7 @@ func TestHandler_HandleEvent(t *testing.T) {
return nil
}

h := handler{last: tc.last, fn: run}
h := fsEventHandler{last: tc.last, fn: run}
err := h.handleEvent(tc.event)
assert.NilError(t, err)
assert.Equal(t, ran, tc.expectedRun)
Expand Down
110 changes: 110 additions & 0 deletions internal/filewatcher/watch_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//go:build !windows && !aix
// +build !windows,!aix

package filewatcher

import (
"context"
"io"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"gotest.tools/v3/assert"
"gotest.tools/v3/fs"
)

func TestWatch(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
dir := fs.NewDir(t, t.Name())

r, w := io.Pipe()
patchStdin(t, r)
patchFloodThreshold(t, 0)

chEvents := make(chan Event, 1)
capture := func(event Event) error {
chEvents <- event
return nil
}

go func() {
err := Watch(ctx, []string{dir.Path()}, capture)
assert.Check(t, err)
}()

t.Run("run all tests", func(t *testing.T) {
_, err := w.Write([]byte("a"))
assert.NilError(t, err)

event := <-chEvents
expected := Event{PkgPath: "./..."}
assert.DeepEqual(t, event, expected, cmpEvent)
})

t.Run("run tests on file change", func(t *testing.T) {
fs.Apply(t, dir, fs.WithFile("file.go", ""))

event := <-chEvents
expected := Event{PkgPath: "./" + dir.Path()}
assert.DeepEqual(t, event, expected, cmpEvent)

t.Run("and rerun", func(t *testing.T) {
_, err := w.Write([]byte("r"))
assert.NilError(t, err)

event := <-chEvents
expected := Event{PkgPath: "./" + dir.Path(), useLastPath: true}
assert.DeepEqual(t, event, expected, cmpEvent)
})

t.Run("and debug", func(t *testing.T) {
_, err := w.Write([]byte("d"))
assert.NilError(t, err)

event := <-chEvents
expected := Event{
PkgPath: "./" + dir.Path(),
useLastPath: true,
Debug: true,
}
assert.DeepEqual(t, event, expected, cmpEvent)
})

t.Run("and update", func(t *testing.T) {
_, err := w.Write([]byte("u"))
assert.NilError(t, err)

event := <-chEvents
expected := Event{
PkgPath: "./" + dir.Path(),
Args: []string{"-update"},
useLastPath: true,
}
assert.DeepEqual(t, event, expected, cmpEvent)
})
})
}

var cmpEvent = cmp.Options{
cmp.AllowUnexported(Event{}),
cmpopts.IgnoreTypes(make(chan struct{})),
}

func patchStdin(t *testing.T, in io.Reader) {
orig := stdin
stdin = in
t.Cleanup(func() {
stdin = orig
})
}

func patchFloodThreshold(t *testing.T, d time.Duration) {
orig := floodThreshold
floodThreshold = d
t.Cleanup(func() {
floodThreshold = orig
})
}

0 comments on commit e209ec9

Please sign in to comment.