mirror of https://github.com/maxcnunes/gaper.git
Add more tests
This commit is contained in:
parent
8e2f1573d6
commit
2847f41c96
README.mdbuilder.gobuilder_test.gomain.gorunner.gorunner_test.go
testdata
watcher.gowatcher_test.go
|
@ -53,6 +53,14 @@ GLOBAL OPTIONS:
|
||||||
--version, -v print the version
|
--version, -v print the version
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Ignore watch over all test files:
|
||||||
|
|
||||||
|
```
|
||||||
|
--ignore './**/*_test.go'
|
||||||
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See the [Contributing guide](/CONTRIBUTING.md) for steps on how to contribute to this project.
|
See the [Contributing guide](/CONTRIBUTING.md) for steps on how to contribute to this project.
|
||||||
|
|
21
builder.go
21
builder.go
|
@ -12,7 +12,6 @@ import (
|
||||||
type Builder interface {
|
type Builder interface {
|
||||||
Build() error
|
Build() error
|
||||||
Binary() string
|
Binary() string
|
||||||
Errors() string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type builder struct {
|
type builder struct {
|
||||||
|
@ -25,8 +24,9 @@ type builder struct {
|
||||||
|
|
||||||
// NewBuilder ...
|
// NewBuilder ...
|
||||||
func NewBuilder(dir string, bin string, wd string, buildArgs []string) Builder {
|
func NewBuilder(dir string, bin string, wd string, buildArgs []string) Builder {
|
||||||
if len(bin) == 0 {
|
// resolve bin name by current folder name
|
||||||
bin = "bin"
|
if bin == "" {
|
||||||
|
bin = filepath.Base(wd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// does not work on Windows without the ".exe" extension
|
// does not work on Windows without the ".exe" extension
|
||||||
|
@ -45,11 +45,6 @@ func (b *builder) Binary() string {
|
||||||
return b.binary
|
return b.binary
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errors ...
|
|
||||||
func (b *builder) Errors() string {
|
|
||||||
return b.errors
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build ...
|
// Build ...
|
||||||
func (b *builder) Build() error {
|
func (b *builder) Build() error {
|
||||||
logger.Info("Building program")
|
logger.Info("Building program")
|
||||||
|
@ -64,14 +59,8 @@ func (b *builder) Build() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if command.ProcessState.Success() {
|
if !command.ProcessState.Success() {
|
||||||
b.errors = ""
|
return fmt.Errorf("error building: %s", output)
|
||||||
} else {
|
|
||||||
b.errors = string(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b.errors) > 0 {
|
|
||||||
return fmt.Errorf(b.errors)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -27,3 +27,26 @@ func TestBuilderSuccessBuild(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.NotNil(t, file, "binary not written properly")
|
assert.NotNil(t, file, "binary not written properly")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilderFailureBuild(t *testing.T) {
|
||||||
|
bArgs := []string{}
|
||||||
|
bin := "srv"
|
||||||
|
dir := filepath.Join("testdata", "build-failure")
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("couldn't get current working directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := NewBuilder(dir, bin, wd, bArgs)
|
||||||
|
err = b.Build()
|
||||||
|
assert.NotNil(t, err, "build error")
|
||||||
|
assert.Equal(t, err.Error(), "exit status 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderDefaultBinName(t *testing.T) {
|
||||||
|
bin := ""
|
||||||
|
dir := filepath.Join("testdata", "server")
|
||||||
|
wd := "/src/projects/project-name"
|
||||||
|
b := NewBuilder(dir, bin, wd, nil)
|
||||||
|
assert.Equal(t, b.Binary(), "project-name")
|
||||||
|
}
|
||||||
|
|
18
main.go
18
main.go
|
@ -15,10 +15,6 @@ import (
|
||||||
|
|
||||||
var logger = NewLogger("gaper")
|
var logger = NewLogger("gaper")
|
||||||
|
|
||||||
// default values
|
|
||||||
var defaultExtensions = cli.StringSlice{"go"}
|
|
||||||
var defaultPoolInterval = 500
|
|
||||||
|
|
||||||
// exit statuses
|
// exit statuses
|
||||||
var exitStatusSuccess = 0
|
var exitStatusSuccess = 0
|
||||||
var exitStatusError = 1
|
var exitStatusError = 1
|
||||||
|
@ -94,12 +90,10 @@ func main() {
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "poll-interval, p",
|
Name: "poll-interval, p",
|
||||||
Value: defaultPoolInterval,
|
|
||||||
Usage: "how often in milliseconds to poll watched files for changes",
|
Usage: "how often in milliseconds to poll watched files for changes",
|
||||||
},
|
},
|
||||||
cli.StringSliceFlag{
|
cli.StringSliceFlag{
|
||||||
Name: "extensions, e",
|
Name: "extensions, e",
|
||||||
Value: &defaultExtensions,
|
|
||||||
Usage: "a comma-delimited list of file extensions to watch for changes",
|
Usage: "a comma-delimited list of file extensions to watch for changes",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
|
@ -135,11 +129,6 @@ func runGaper(cfg *Config) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve bin name by current folder name
|
|
||||||
if cfg.BinName == "" {
|
|
||||||
cfg.BinName = filepath.Base(wd)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.WatchItems) == 0 {
|
if len(cfg.WatchItems) == 0 {
|
||||||
cfg.WatchItems = append(cfg.WatchItems, cfg.BuildPath)
|
cfg.WatchItems = append(cfg.WatchItems, cfg.BuildPath)
|
||||||
}
|
}
|
||||||
|
@ -182,7 +171,9 @@ func runGaper(cfg *Config) error {
|
||||||
case event := <-watcher.Events:
|
case event := <-watcher.Events:
|
||||||
logger.Debug("Detected new changed file: ", event)
|
logger.Debug("Detected new changed file: ", event)
|
||||||
changeRestart = true
|
changeRestart = true
|
||||||
restart(builder, runner)
|
if err := restart(builder, runner); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case err := <-watcher.Errors:
|
case err := <-watcher.Errors:
|
||||||
return fmt.Errorf("error on watching files: %v", err)
|
return fmt.Errorf("error on watching files: %v", err)
|
||||||
case err := <-runner.Errors():
|
case err := <-runner.Errors():
|
||||||
|
@ -249,8 +240,7 @@ func handleProgramExit(builder Builder, runner Runner, err error, noRestartOn st
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
restart(builder, runner)
|
return restart(builder, runner)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func shutdown(runner Runner) {
|
func shutdown(runner Runner) {
|
||||||
|
|
20
runner.go
20
runner.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -12,6 +13,9 @@ import (
|
||||||
// OSWindows ...
|
// OSWindows ...
|
||||||
const OSWindows = "windows"
|
const OSWindows = "windows"
|
||||||
|
|
||||||
|
// os errors
|
||||||
|
var errFinished = errors.New("os: process already finished")
|
||||||
|
|
||||||
// Runner ...
|
// Runner ...
|
||||||
type Runner interface {
|
type Runner interface {
|
||||||
Run() (*exec.Cmd, error)
|
Run() (*exec.Cmd, error)
|
||||||
|
@ -28,6 +32,7 @@ type runner struct {
|
||||||
command *exec.Cmd
|
command *exec.Cmd
|
||||||
starttime time.Time
|
starttime time.Time
|
||||||
errors chan error
|
errors chan error
|
||||||
|
end chan bool // used internally by Kill to wait a process die
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRunner ...
|
// NewRunner ...
|
||||||
|
@ -39,6 +44,7 @@ func NewRunner(wStdout io.Writer, wStderr io.Writer, bin string, args []string)
|
||||||
writerStderr: wStderr,
|
writerStderr: wStderr,
|
||||||
starttime: time.Now(),
|
starttime: time.Now(),
|
||||||
errors: make(chan error),
|
errors: make(chan error),
|
||||||
|
end: make(chan bool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,13 +52,12 @@ func NewRunner(wStdout io.Writer, wStderr io.Writer, bin string, args []string)
|
||||||
func (r *runner) Run() (*exec.Cmd, error) {
|
func (r *runner) Run() (*exec.Cmd, error) {
|
||||||
logger.Info("Starting program")
|
logger.Info("Starting program")
|
||||||
|
|
||||||
if r.command == nil || r.Exited() {
|
if r.command != nil && !r.Exited() {
|
||||||
if err := r.runBin(); err != nil {
|
return r.command, nil
|
||||||
return nil, fmt.Errorf("error running: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(250 * time.Millisecond)
|
if err := r.runBin(); err != nil {
|
||||||
return r.command, nil
|
return nil, fmt.Errorf("error running: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.command, nil
|
return r.command, nil
|
||||||
|
@ -66,7 +71,7 @@ func (r *runner) Kill() error {
|
||||||
|
|
||||||
done := make(chan error)
|
done := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
r.command.Wait() // nolint errcheck
|
<-r.end
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -82,7 +87,7 @@ func (r *runner) Kill() error {
|
||||||
// Wait for our process to die before we return or hard kill after 3 sec
|
// Wait for our process to die before we return or hard kill after 3 sec
|
||||||
select {
|
select {
|
||||||
case <-time.After(3 * time.Second):
|
case <-time.After(3 * time.Second):
|
||||||
if err := r.command.Process.Kill(); err != nil {
|
if err := r.command.Process.Kill(); err != nil && err.Error() != errFinished.Error() {
|
||||||
return fmt.Errorf("failed to kill: %v", err)
|
return fmt.Errorf("failed to kill: %v", err)
|
||||||
}
|
}
|
||||||
case <-done:
|
case <-done:
|
||||||
|
@ -128,6 +133,7 @@ func (r *runner) runBin() error {
|
||||||
// wait for exit errors
|
// wait for exit errors
|
||||||
go func() {
|
go func() {
|
||||||
r.errors <- r.command.Wait()
|
r.errors <- r.command.Wait()
|
||||||
|
r.end <- true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -33,3 +34,21 @@ func TestRunnerSuccessRun(t *testing.T) {
|
||||||
assert.Equal(t, "Gaper\n", stdout.String())
|
assert.Equal(t, "Gaper\n", stdout.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunnerSuccessKill(t *testing.T) {
|
||||||
|
bin := filepath.Join("testdata", "print-gaper")
|
||||||
|
if runtime.GOOS == OSWindows {
|
||||||
|
bin += ".bat"
|
||||||
|
}
|
||||||
|
|
||||||
|
runner := NewRunner(os.Stdout, os.Stderr, bin, nil)
|
||||||
|
|
||||||
|
_, err := runner.Run()
|
||||||
|
assert.Nil(t, err, "error running binary")
|
||||||
|
|
||||||
|
err = runner.Kill()
|
||||||
|
assert.Nil(t, err, "error killing program")
|
||||||
|
|
||||||
|
errCmd := <-runner.Errors()
|
||||||
|
assert.NotNil(t, errCmd, "kill program")
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// nolint
|
||||||
|
func main() error {
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
sleep 2
|
||||||
echo "Gaper"
|
echo "Gaper"
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
timeout 2 > nul
|
||||||
@echo Gaper
|
@echo Gaper
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// an empty test file just to check during tests we can ignore _test.go files
|
24
watcher.go
24
watcher.go
|
@ -10,6 +10,12 @@ import (
|
||||||
zglob "github.com/mattn/go-zglob"
|
zglob "github.com/mattn/go-zglob"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultExtensions used by the watcher
|
||||||
|
var DefaultExtensions = []string{"go"}
|
||||||
|
|
||||||
|
// DefaultPoolInterval used by the watcher
|
||||||
|
var DefaultPoolInterval = 500
|
||||||
|
|
||||||
// Watcher ...
|
// Watcher ...
|
||||||
type Watcher struct {
|
type Watcher struct {
|
||||||
PollInterval int
|
PollInterval int
|
||||||
|
@ -22,6 +28,14 @@ type Watcher struct {
|
||||||
|
|
||||||
// NewWatcher ...
|
// NewWatcher ...
|
||||||
func NewWatcher(pollInterval int, watchItems []string, ignoreItems []string, extensions []string) (*Watcher, error) {
|
func NewWatcher(pollInterval int, watchItems []string, ignoreItems []string, extensions []string) (*Watcher, error) {
|
||||||
|
if pollInterval == 0 {
|
||||||
|
pollInterval = DefaultPoolInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(extensions) == 0 {
|
||||||
|
extensions = DefaultExtensions
|
||||||
|
}
|
||||||
|
|
||||||
allowedExts := make(map[string]bool)
|
allowedExts := make(map[string]bool)
|
||||||
for _, ext := range extensions {
|
for _, ext := range extensions {
|
||||||
allowedExts["."+ext] = true
|
allowedExts["."+ext] = true
|
||||||
|
@ -76,8 +90,9 @@ func (w *Watcher) scanChange(watchPath string) (string, error) {
|
||||||
var fileChanged string
|
var fileChanged string
|
||||||
|
|
||||||
err := filepath.Walk(watchPath, func(path string, info os.FileInfo, err error) error {
|
err := filepath.Walk(watchPath, func(path string, info os.FileInfo, err error) error {
|
||||||
if path == ".git" && info.IsDir() {
|
// ignore hidden files and directories
|
||||||
return filepath.SkipDir
|
if filepath.Base(path)[0] == '.' {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, x := range w.IgnoreItems {
|
for _, x := range w.IgnoreItems {
|
||||||
|
@ -86,11 +101,6 @@ func (w *Watcher) scanChange(watchPath string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore hidden files
|
|
||||||
if filepath.Base(path)[0] == '.' {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ext := filepath.Ext(path)
|
ext := filepath.Ext(path)
|
||||||
if _, ok := w.AllowedExtensions[ext]; ok && info.ModTime().After(startTime) {
|
if _, ok := w.AllowedExtensions[ext]; ok && info.ModTime().After(startTime) {
|
||||||
fileChanged = path
|
fileChanged = path
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWatcherDefaultValues(t *testing.T) {
|
||||||
|
pollInterval := 0
|
||||||
|
watchItems := []string{filepath.Join("testdata", "server")}
|
||||||
|
var ignoreItems []string
|
||||||
|
var extensions []string
|
||||||
|
|
||||||
|
w, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions)
|
||||||
|
|
||||||
|
assert.Nil(t, err, "wacher error")
|
||||||
|
assert.Equal(t, 500, w.PollInterval)
|
||||||
|
assert.Equal(t, watchItems, w.WatchItems)
|
||||||
|
assert.Len(t, w.IgnoreItems, 0)
|
||||||
|
assert.Equal(t, map[string]bool{".go": true}, w.AllowedExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWatcherGlobPath(t *testing.T) {
|
||||||
|
pollInterval := 0
|
||||||
|
watchItems := []string{filepath.Join("testdata", "server")}
|
||||||
|
ignoreItems := []string{"./testdata/**/*_test.go"}
|
||||||
|
var extensions []string
|
||||||
|
|
||||||
|
w, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions)
|
||||||
|
file := filepath.Join("testdata", "server", "main_test.go")
|
||||||
|
assert.Nil(t, err, "wacher error")
|
||||||
|
assert.Equal(t, []string{file}, w.IgnoreItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWatcherWatchChange(t *testing.T) {
|
||||||
|
srvdir := filepath.Join("testdata", "server")
|
||||||
|
hiddendir := filepath.Join("testdata", "hidden-test")
|
||||||
|
|
||||||
|
hiddenfile1 := filepath.Join("testdata", ".hidden-file")
|
||||||
|
hiddenfile2 := filepath.Join("testdata", ".hidden-folder", ".gitkeep")
|
||||||
|
mainfile := filepath.Join("testdata", "server", "main.go")
|
||||||
|
testfile := filepath.Join("testdata", "server", "main_test.go")
|
||||||
|
|
||||||
|
pollInterval := 0
|
||||||
|
watchItems := []string{srvdir, hiddendir}
|
||||||
|
ignoreItems := []string{testfile}
|
||||||
|
extensions := []string{"go"}
|
||||||
|
|
||||||
|
w, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions)
|
||||||
|
assert.Nil(t, err, "wacher error")
|
||||||
|
|
||||||
|
go w.Watch()
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|
||||||
|
// update hidden files and dirs to check builtin hidden ignore is working
|
||||||
|
os.Chtimes(hiddenfile1, time.Now(), time.Now())
|
||||||
|
os.Chtimes(hiddenfile2, time.Now(), time.Now())
|
||||||
|
|
||||||
|
// update testfile first to check ignore is working
|
||||||
|
os.Chtimes(testfile, time.Now(), time.Now())
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
os.Chtimes(mainfile, time.Now(), time.Now())
|
||||||
|
|
||||||
|
select {
|
||||||
|
case event := <-w.Events:
|
||||||
|
assert.Equal(t, mainfile, event)
|
||||||
|
case err := <-w.Errors:
|
||||||
|
assert.Nil(t, err, "wacher event error")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue