Skip to content

Commit e209ec9

Browse files
authored
Merge pull request #249 from dnephin/add-update-flag
watch: Add u key for updating golden values
2 parents f1fdcbd + 849289b commit e209ec9

File tree

6 files changed

+159
-29
lines changed

6 files changed

+159
-29
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# gotestsum
22

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

118118
```
@@ -202,7 +202,7 @@ how you specify args to `go test`:
202202

203203
### Custom `go test` command
204204

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

355355
* `r` will run tests for the previous event.
356356
Added in version 1.6.1.
357+
* `u` will run tests for the previous event, with the `-update` flag added.
358+
Many [golden](https://gotest.tools/v3/golden) packages use this flag to automatically
359+
update expected values of tests.
360+
Added in version 1.8.1.
357361
* `d` will run tests for the previous event using `dlv test`, allowing you to
358362
debug a test failure using [delve]. A breakpoint will automatically be added at
359363
the first line of any tests which failed in the previous run. Additional

cmd/watch.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ import (
1313
)
1414

1515
func runWatcher(opts *options) error {
16+
ctx, cancel := context.WithCancel(context.Background())
17+
defer cancel()
18+
1619
w := &watchRuns{opts: *opts}
17-
return filewatcher.Watch(opts.packages, w.run)
20+
return filewatcher.Watch(ctx, opts.packages, w.run)
1821
}
1922

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

43-
opts := w.opts
44-
opts.packages = []string{event.PkgPath}
46+
opts := w.opts // shallow copy opts
47+
opts.packages = append([]string{}, opts.packages...)
48+
opts.packages = append(opts.packages, event.PkgPath)
49+
opts.packages = append(opts.packages, event.Args...)
50+
4551
var err error
4652
if w.prevExec, err = runSingle(&opts); !IsExitCoder(err) {
4753
return err

internal/filewatcher/term_unix.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"bufio"
88
"context"
99
"fmt"
10+
"io"
1011
"os"
1112

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

66+
var stdin io.Reader = os.Stdin
67+
6568
// Monitor the terminal for key presses. If the key press is associated with an
6669
// action, an event will be sent to channel returned by Events.
6770
func (r *terminal) Monitor(ctx context.Context) {
6871
if r == nil {
6972
return
7073
}
71-
in := bufio.NewReader(os.Stdin)
74+
in := bufio.NewReader(stdin)
7275
for {
7376
char, err := in.ReadByte()
7477
if err != nil {
@@ -77,20 +80,18 @@ func (r *terminal) Monitor(ctx context.Context) {
7780
}
7881
log.Debugf("received byte %v (%v)", char, string(char))
7982

80-
var chResume chan struct{}
83+
chResume := make(chan struct{})
8184
switch char {
8285
case 'r':
83-
chResume = make(chan struct{})
84-
r.ch <- Event{resume: chResume}
86+
r.ch <- Event{resume: chResume, useLastPath: true}
8587
case 'd':
86-
chResume = make(chan struct{})
87-
r.ch <- Event{Debug: true, resume: chResume}
88+
r.ch <- Event{resume: chResume, useLastPath: true, Debug: true}
8889
case 'a':
89-
chResume = make(chan struct{})
9090
r.ch <- Event{resume: chResume, PkgPath: "./..."}
9191
case 'l':
92-
chResume = make(chan struct{})
9392
r.ch <- Event{resume: chResume, reloadPaths: true}
93+
case 'u':
94+
r.ch <- Event{resume: chResume, useLastPath: true, Args: []string{"-update"}}
9495
case '\n':
9596
fmt.Println()
9697
continue

internal/filewatcher/watch.go

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,25 @@ import (
1919
const maxDepth = 7
2020

2121
type Event struct {
22-
PkgPath string
23-
Debug bool
24-
resume chan struct{}
22+
// PkgPath of the package that triggered the event.
23+
PkgPath string
24+
// Args will be appended to the command line args for 'go test'.
25+
Args []string
26+
// Debug runs the tests with delve.
27+
Debug bool
28+
// resume the Watch goroutine when this channel is closed. Used to block
29+
// the Watch goroutine while tests are running.
30+
resume chan struct{}
31+
// reloadPaths will cause the watched path list to be reloaded, to watch
32+
// new directories.
2533
reloadPaths bool
34+
// useLastPath when true will use the PkgPath from the previous run.
35+
useLastPath bool
2636
}
2737

2838
// Watch dirs for filesystem events, and run tests when .go files are saved.
2939
// nolint: gocyclo
30-
func Watch(dirs []string, run func(Event) error) error {
31-
ctx, cancel := context.WithCancel(context.Background())
32-
defer cancel()
33-
40+
func Watch(ctx context.Context, dirs []string, run func(Event) error) error {
3441
watcher, err := fsnotify.NewWatcher()
3542
if err != nil {
3643
return fmt.Errorf("failed to create file watcher: %w", err)
@@ -48,9 +55,11 @@ func Watch(dirs []string, run func(Event) error) error {
4855
defer term.Reset()
4956
go term.Monitor(ctx)
5057

51-
h := &handler{last: time.Now(), fn: run}
58+
h := &fsEventHandler{last: time.Now(), fn: run}
5259
for {
5360
select {
61+
case <-ctx.Done():
62+
return nil
5463
case <-timer.C:
5564
return fmt.Errorf("exceeded idle timeout while watching files")
5665

@@ -218,15 +227,15 @@ func handleDirCreated(watcher *fsnotify.Watcher, event fsnotify.Event) (handled
218227
return true
219228
}
220229

221-
type handler struct {
230+
type fsEventHandler struct {
222231
last time.Time
223232
lastPath string
224233
fn func(opts Event) error
225234
}
226235

227-
const floodThreshold = 250 * time.Millisecond
236+
var floodThreshold = 250 * time.Millisecond
228237

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

245-
func (h *handler) runTests(opts Event) error {
246-
if opts.PkgPath == "" {
254+
func (h *fsEventHandler) runTests(opts Event) error {
255+
if opts.useLastPath {
247256
opts.PkgPath = h.lastPath
248257
}
249258
fmt.Printf("\nRunning tests in %v\n", opts.PkgPath)

internal/filewatcher/watch_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"gotest.tools/v3/fs"
1313
)
1414

15-
func TestHandler_HandleEvent(t *testing.T) {
15+
func TestFSEventHandler_HandleEvent(t *testing.T) {
1616
type testCase struct {
1717
name string
1818
last time.Time
@@ -27,7 +27,7 @@ func TestHandler_HandleEvent(t *testing.T) {
2727
return nil
2828
}
2929

30-
h := handler{last: tc.last, fn: run}
30+
h := fsEventHandler{last: tc.last, fn: run}
3131
err := h.handleEvent(tc.event)
3232
assert.NilError(t, err)
3333
assert.Equal(t, ran, tc.expectedRun)
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//go:build !windows && !aix
2+
// +build !windows,!aix
3+
4+
package filewatcher
5+
6+
import (
7+
"context"
8+
"io"
9+
"testing"
10+
"time"
11+
12+
"github.com/google/go-cmp/cmp"
13+
"github.com/google/go-cmp/cmp/cmpopts"
14+
"gotest.tools/v3/assert"
15+
"gotest.tools/v3/fs"
16+
)
17+
18+
func TestWatch(t *testing.T) {
19+
ctx, cancel := context.WithCancel(context.Background())
20+
t.Cleanup(cancel)
21+
dir := fs.NewDir(t, t.Name())
22+
23+
r, w := io.Pipe()
24+
patchStdin(t, r)
25+
patchFloodThreshold(t, 0)
26+
27+
chEvents := make(chan Event, 1)
28+
capture := func(event Event) error {
29+
chEvents <- event
30+
return nil
31+
}
32+
33+
go func() {
34+
err := Watch(ctx, []string{dir.Path()}, capture)
35+
assert.Check(t, err)
36+
}()
37+
38+
t.Run("run all tests", func(t *testing.T) {
39+
_, err := w.Write([]byte("a"))
40+
assert.NilError(t, err)
41+
42+
event := <-chEvents
43+
expected := Event{PkgPath: "./..."}
44+
assert.DeepEqual(t, event, expected, cmpEvent)
45+
})
46+
47+
t.Run("run tests on file change", func(t *testing.T) {
48+
fs.Apply(t, dir, fs.WithFile("file.go", ""))
49+
50+
event := <-chEvents
51+
expected := Event{PkgPath: "./" + dir.Path()}
52+
assert.DeepEqual(t, event, expected, cmpEvent)
53+
54+
t.Run("and rerun", func(t *testing.T) {
55+
_, err := w.Write([]byte("r"))
56+
assert.NilError(t, err)
57+
58+
event := <-chEvents
59+
expected := Event{PkgPath: "./" + dir.Path(), useLastPath: true}
60+
assert.DeepEqual(t, event, expected, cmpEvent)
61+
})
62+
63+
t.Run("and debug", func(t *testing.T) {
64+
_, err := w.Write([]byte("d"))
65+
assert.NilError(t, err)
66+
67+
event := <-chEvents
68+
expected := Event{
69+
PkgPath: "./" + dir.Path(),
70+
useLastPath: true,
71+
Debug: true,
72+
}
73+
assert.DeepEqual(t, event, expected, cmpEvent)
74+
})
75+
76+
t.Run("and update", func(t *testing.T) {
77+
_, err := w.Write([]byte("u"))
78+
assert.NilError(t, err)
79+
80+
event := <-chEvents
81+
expected := Event{
82+
PkgPath: "./" + dir.Path(),
83+
Args: []string{"-update"},
84+
useLastPath: true,
85+
}
86+
assert.DeepEqual(t, event, expected, cmpEvent)
87+
})
88+
})
89+
}
90+
91+
var cmpEvent = cmp.Options{
92+
cmp.AllowUnexported(Event{}),
93+
cmpopts.IgnoreTypes(make(chan struct{})),
94+
}
95+
96+
func patchStdin(t *testing.T, in io.Reader) {
97+
orig := stdin
98+
stdin = in
99+
t.Cleanup(func() {
100+
stdin = orig
101+
})
102+
}
103+
104+
func patchFloodThreshold(t *testing.T, d time.Duration) {
105+
orig := floodThreshold
106+
floodThreshold = d
107+
t.Cleanup(func() {
108+
floodThreshold = orig
109+
})
110+
}

0 commit comments

Comments
 (0)