Skip to content

Commit

Permalink
Merge pull request #160 from dnephin/watch-retest
Browse files Browse the repository at this point in the history
Add rerun for --watch mode
  • Loading branch information
dnephin committed Nov 8, 2020
2 parents b081783 + c41d054 commit a0ef4c9
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ tests for the package which contains the changed file. By default all
directories under the current directory will be watched. Use the `--packages` flag
to specify a different list.

While in watch mode, pressing `r` will re-run the tests for the previous event.

**Example: run tests for a package when any file in that package is saved**
```
gotestsum --watch --format testname
Expand Down
8 changes: 8 additions & 0 deletions internal/filewatcher/term_bsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// +build darwin dragonfly freebsd netbsd openbsd

package filewatcher

import "golang.org/x/sys/unix"

const tcGet = unix.TIOCGETA
const tcSet = unix.TIOCSETA
6 changes: 6 additions & 0 deletions internal/filewatcher/term_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package filewatcher

import "golang.org/x/sys/unix"

const tcGet = unix.TCGETS
const tcSet = unix.TCSETS
67 changes: 67 additions & 0 deletions internal/filewatcher/term_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// +build !windows

package filewatcher

import (
"bufio"
"context"
"fmt"
"os"

"golang.org/x/sys/unix"
"gotest.tools/gotestsum/log"
)

func enableNonBlockingRead(fd int) (func(), error) {
term, err := unix.IoctlGetTermios(fd, tcGet)
if err != nil {
return nil, err
}

state := *term
reset := func() {
if err := unix.IoctlSetTermios(fd, tcSet, &state); err != nil {
log.Debugf("failed to reset fd %d: %v", fd, err)
}
}

term.Lflag &^= unix.ECHO | unix.ICANON
term.Cc[unix.VMIN] = 1
term.Cc[unix.VTIME] = 0
if err := unix.IoctlSetTermios(fd, tcSet, term); err != nil {
reset()
return nil, err
}
return reset, nil
}

func (r *redoHandler) run(ctx context.Context) {
fd := int(os.Stdin.Fd())
reset, err := enableNonBlockingRead(fd)
if err != nil {
log.Debugf("failed to put terminal (fd %d) into raw mode: %v", fd, err)
return
}
defer reset()

in := bufio.NewReader(os.Stdin)
for {
if ctx.Err() != nil {
return
}

char, err := in.ReadByte()
if err != nil {
log.Warnf("failed to read input: %v", err)
return
}
log.Debugf("received byte %v (%v)", char, string(char))

switch char {
case 'r':
r.ch <- r.prevPath
case '\n':
fmt.Println()
}
}
}
7 changes: 7 additions & 0 deletions internal/filewatcher/term_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package filewatcher

import "context"

func (r *redoHandler) run(_ context.Context) {
return
}
37 changes: 32 additions & 5 deletions internal/filewatcher/watch.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package filewatcher

import (
"context"
"fmt"
"io"
"os"
Expand All @@ -15,6 +16,9 @@ import (
const maxDepth = 7

func Watch(dirs []string, run func(pkg string) error) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

toWatch := findAllDirs(dirs, maxDepth)
watcher, err := fsnotify.NewWatcher()
if err != nil {
Expand All @@ -32,15 +36,22 @@ func Watch(dirs []string, run func(pkg string) error) error {
timer := time.NewTimer(time.Hour)
defer timer.Stop()

redo := &redoHandler{ch: make(chan string)}
go redo.run(ctx)
h := &handler{last: time.Now(), fn: run}
for {
select {
case <-timer.C:
return fmt.Errorf("exceeded idle timeout while watching files")
case event := <-watcher.Events:
if !timer.Stop() {
<-timer.C

case path := <-redo.ch:
resetTimer(timer)
if err := h.runTests(path); err != nil {
return fmt.Errorf("failed to rerun tests for %v: %v", path, err)
}

case event := <-watcher.Events:
resetTimer(timer)
log.Debugf("handling event %v", event)

if handleDirCreated(watcher, event) {
Expand All @@ -50,13 +61,21 @@ func Watch(dirs []string, run func(pkg string) error) error {
if err := h.handleEvent(event); err != nil {
return fmt.Errorf("failed to run tests for %v: %v", event.Name, err)
}
timer.Reset(time.Hour)
redo.prevPath = event.Name

case err := <-watcher.Errors:
return fmt.Errorf("failed while watching files: %v", err)
}
}
}

func resetTimer(timer *time.Timer) {
if !timer.Stop() {
<-timer.C
}
timer.Reset(time.Hour)
}

func findAllDirs(dirs []string, maxDepth int) []string {
if len(dirs) == 0 {
dirs = []string{"./..."}
Expand Down Expand Up @@ -184,12 +203,20 @@ func (h *handler) handleEvent(event fsnotify.Event) error {
log.Debugf("skipping event received less than %v after the previous", floodThreshold)
return nil
}
return h.runTests(event.Name)
}

pkg := "./" + filepath.Dir(event.Name)
func (h *handler) runTests(path string) error {
pkg := "./" + filepath.Dir(path)
fmt.Printf("\nRunning tests in %v\n", pkg)
if err := h.fn(pkg); err != nil {
return err
}
h.last = time.Now()
return nil
}

type redoHandler struct {
prevPath string
ch chan string
}

0 comments on commit a0ef4c9

Please sign in to comment.