From f578db31da537ade87c32227e79b37b840365ce2 Mon Sep 17 00:00:00 2001 From: Max Claus Nunes Date: Thu, 12 Jul 2018 10:27:07 -0300 Subject: [PATCH] Improve test coverage --- .gitignore | 1 + Gopkg.lock | 13 ++- Makefile | 4 +- cmd/gaper/main.go | 6 +- gaper.go | 134 +++++++++++++------------- gaper_test.go | 236 +++++++++++++++++++++++++++++++++++++++++++++- runner.go | 14 +++ runner_test.go | 31 ++++++ testdata/mocks.go | 80 ++++++++++++++++ watcher.go | 64 ++++++++----- watcher_test.go | 25 ++--- 11 files changed, 499 insertions(+), 109 deletions(-) create mode 100644 testdata/mocks.go diff --git a/.gitignore b/.gitignore index db83d95..8e1d9bd 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ coverage.out .DS_Store testdata/server/server dist +test-srv diff --git a/Gopkg.lock b/Gopkg.lock index e79e4cb..ba8afb6 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -46,9 +46,18 @@ revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" +[[projects]] + name = "github.com/stretchr/objx" + packages = ["."] + revision = "477a77ecc69700c7cdeb1fa9e129548e1c1c393c" + version = "v0.1.1" + [[projects]] name = "github.com/stretchr/testify" - packages = ["assert"] + packages = [ + "assert", + "mock" + ] revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" version = "v1.2.2" @@ -67,6 +76,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "501c303fff1c8cdb5806d0ebb0c92b671095cbc2049f5b8d2286df401f5efce5" + inputs-digest = "8446ff85ebcb6bc802d3f444727c3910444a405f3c073c1095eb5cc8c497138b" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Makefile b/Makefile index a9933e9..3c20b78 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ OS := $(shell uname -s) -TEST_PACKAGES := $(shell go list ./...) -COVER_PACKAGES := $(shell go list ./... | paste -sd "," -) +TEST_PACKAGES := $(shell go list ./... | grep -v cmd) +COVER_PACKAGES := $(shell go list ./... | grep -v cmd | paste -sd "," -) LINTER := $(shell command -v gometalinter 2> /dev/null) .PHONY: setup diff --git a/cmd/gaper/main.go b/cmd/gaper/main.go index 141f26e..73e97f6 100644 --- a/cmd/gaper/main.go +++ b/cmd/gaper/main.go @@ -27,7 +27,6 @@ func main() { PollInterval: c.Int("poll-interval"), Extensions: c.StringSlice("extensions"), NoRestartOn: c.String("no-restart-on"), - ExitOnSIGINT: true, } } @@ -38,7 +37,10 @@ func main() { app.Action = func(c *cli.Context) { args := parseArgs(c) - if err := gaper.Run(args); err != nil { + chOSSiginal := make(chan os.Signal, 2) + logger.Verbose(args.Verbose) + + if err := gaper.Run(args, chOSSiginal); err != nil { logger.Error(err) os.Exit(1) } diff --git a/gaper.go b/gaper.go index 2334461..4bffaf3 100644 --- a/gaper.go +++ b/gaper.go @@ -5,7 +5,6 @@ package gaper import ( "fmt" "os" - "os/exec" "os/signal" "path/filepath" "syscall" @@ -23,6 +22,13 @@ var DefaultExtensions = []string{"go"} // DefaultPoolInterval is the time in ms used by the watcher to wait between scans var DefaultPoolInterval = 500 +// No restart types +var ( + NoRestartOnError = "error" + NoRestartOnSuccess = "success" + NoRestartOnExit = "exit" +) + var logger = NewLogger("gaper") // exit statuses @@ -43,71 +49,53 @@ type Config struct { Extensions []string NoRestartOn string Verbose bool - ExitOnSIGINT bool + WorkingDirectory string } -// Run in the gaper high level API -// It starts the whole gaper process watching for file changes or exit codes +// Run starts the whole gaper process watching for file changes or exit codes // and restarting the program -func Run(cfg *Config) error { // nolint: gocyclo - var err error - logger.Verbose(cfg.Verbose) +func Run(cfg *Config, chOSSiginal chan os.Signal) error { logger.Debug("Starting gaper") - if len(cfg.BuildPath) == 0 { - cfg.BuildPath = DefaultBuildPath - } - - cfg.BuildArgs, err = parseInnerArgs(cfg.BuildArgs, cfg.BuildArgsMerged) - if err != nil { + if err := setupConfig(cfg); err != nil { return err } - cfg.ProgramArgs, err = parseInnerArgs(cfg.ProgramArgs, cfg.ProgramArgsMerged) - if err != nil { - return err - } - - wd, err := os.Getwd() - if err != nil { - return err - } - - if len(cfg.WatchItems) == 0 { - cfg.WatchItems = append(cfg.WatchItems, cfg.BuildPath) - } - - builder := NewBuilder(cfg.BuildPath, cfg.BinName, wd, cfg.BuildArgs) - runner := NewRunner(os.Stdout, os.Stderr, filepath.Join(wd, builder.Binary()), cfg.ProgramArgs) - - if err = builder.Build(); err != nil { - return fmt.Errorf("build error: %v", err) - } - - shutdown(runner, cfg.ExitOnSIGINT) - - if _, err = runner.Run(); err != nil { - return fmt.Errorf("run error: %v", err) - } - + builder := NewBuilder(cfg.BuildPath, cfg.BinName, cfg.WorkingDirectory, cfg.BuildArgs) + runner := NewRunner(os.Stdout, os.Stderr, filepath.Join(cfg.WorkingDirectory, builder.Binary()), cfg.ProgramArgs) watcher, err := NewWatcher(cfg.PollInterval, cfg.WatchItems, cfg.IgnoreItems, cfg.Extensions) if err != nil { return fmt.Errorf("watcher error: %v", err) } + return run(cfg, chOSSiginal, builder, runner, watcher) +} + +func run(cfg *Config, chOSSiginal chan os.Signal, builder Builder, runner Runner, watcher Watcher) error { // nolint: gocyclo + if err := builder.Build(); err != nil { + return fmt.Errorf("build error: %v", err) + } + + // listen for OS signals + signal.Notify(chOSSiginal, os.Interrupt, syscall.SIGTERM) + + if _, err := runner.Run(); err != nil { + return fmt.Errorf("run error: %v", err) + } + // flag to know if an exit was caused by a restart from a file changing changeRestart := false go watcher.Watch() for { select { - case event := <-watcher.Events: + case event := <-watcher.Events(): logger.Debug("Detected new changed file: ", event) changeRestart = true 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) case err := <-runner.Errors(): logger.Debug("Detected program exit: ", err) @@ -121,6 +109,14 @@ func Run(cfg *Config) error { // nolint: gocyclo if err = handleProgramExit(builder, runner, err, cfg.NoRestartOn); err != nil { return err } + case signal := <-chOSSiginal: + logger.Debug("Got signal: ", signal) + + if err := runner.Kill(); err != nil { + logger.Error("Error killing: ", err) + } + + return fmt.Errorf("OS signal: %v", signal) default: time.Sleep(time.Duration(cfg.PollInterval) * time.Millisecond) } @@ -149,49 +145,53 @@ func restart(builder Builder, runner Runner) error { } func handleProgramExit(builder Builder, runner Runner, err error, noRestartOn string) error { - var exitStatus int - if exiterr, ok := err.(*exec.ExitError); ok { - status, oks := exiterr.Sys().(syscall.WaitStatus) - if !oks { - return fmt.Errorf("couldn't resolve exit status: %v", err) - } - - exitStatus = status.ExitStatus() - } + exitStatus := runner.ExitStatus(err) // if "error", an exit code of 0 will still restart. - if noRestartOn == "error" && exitStatus == exitStatusError { + if noRestartOn == NoRestartOnError && exitStatus == exitStatusError { return nil } // if "success", no restart only if exit code is 0. - if noRestartOn == "success" && exitStatus == exitStatusSuccess { + if noRestartOn == NoRestartOnSuccess && exitStatus == exitStatusSuccess { return nil } // if "exit", no restart regardless of exit code. - if noRestartOn == "exit" { + if noRestartOn == NoRestartOnExit { return nil } return restart(builder, runner) } -func shutdown(runner Runner, exitOnSIGINT bool) { - c := make(chan os.Signal, 2) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - s := <-c - logger.Debug("Got signal: ", s) +func setupConfig(cfg *Config) error { + var err error - if err := runner.Kill(); err != nil { - logger.Error("Error killing: ", err) - } + if len(cfg.BuildPath) == 0 { + cfg.BuildPath = DefaultBuildPath + } - if exitOnSIGINT { - os.Exit(0) - } - }() + cfg.BuildArgs, err = parseInnerArgs(cfg.BuildArgs, cfg.BuildArgsMerged) + if err != nil { + return err + } + + cfg.ProgramArgs, err = parseInnerArgs(cfg.ProgramArgs, cfg.ProgramArgsMerged) + if err != nil { + return err + } + + cfg.WorkingDirectory, err = os.Getwd() + if err != nil { + return err + } + + if len(cfg.WatchItems) == 0 { + cfg.WatchItems = append(cfg.WatchItems, cfg.BuildPath) + } + + return nil } func parseInnerArgs(args []string, argsm string) ([]string, error) { diff --git a/gaper_test.go b/gaper_test.go index d30ae48..d546289 100644 --- a/gaper_test.go +++ b/gaper_test.go @@ -1,9 +1,241 @@ package gaper import ( + "errors" + "os" + "os/exec" + "path/filepath" + "syscall" "testing" + "time" + + "github.com/maxcnunes/gaper/testdata" + "github.com/stretchr/testify/assert" ) -func TestGaper(t *testing.T) { - // TODO: add test to gaper high level API +func TestGaperRunStopOnSGINT(t *testing.T) { + args := &Config{ + BuildPath: filepath.Join("testdata", "server"), + } + + chOSSiginal := make(chan os.Signal, 2) + go func() { + time.Sleep(1 * time.Second) + chOSSiginal <- syscall.SIGINT + }() + + err := Run(args, chOSSiginal) + assert.NotNil(t, err, "build error") + assert.Equal(t, "OS signal: interrupt", err.Error()) +} + +func TestGaperSetupConfigNoParams(t *testing.T) { + cwd, _ := os.Getwd() + args := &Config{} + err := setupConfig(args) + assert.Nil(t, err, "build error") + assert.Equal(t, args.BuildPath, ".") + assert.Equal(t, args.WorkingDirectory, cwd) + assert.Equal(t, args.WatchItems, []string{"."}) +} + +func TestGaperWatcherError(t *testing.T) { + mockBuilder := new(testdata.MockBuilder) + mockBuilder.On("Build").Return(nil) + + mockRunner := new(testdata.MockRunner) + cmd := &exec.Cmd{} + runnerErrorsChan := make(chan error) + mockRunner.On("Run").Return(cmd, nil) + mockRunner.On("Errors").Return(runnerErrorsChan) + + mockWatcher := new(testdata.MockWacther) + watcherErrorsChan := make(chan error) + watcherEvetnsChan := make(chan string) + mockWatcher.On("Errors").Return(watcherErrorsChan) + mockWatcher.On("Events").Return(watcherEvetnsChan) + + dir := filepath.Join("testdata", "server") + + cfg := &Config{ + BinName: "test-srv", + BuildPath: dir, + } + + go func() { + time.Sleep(3 * time.Second) + watcherErrorsChan <- errors.New("watcher-error") + }() + chOSSiginal := make(chan os.Signal, 2) + err := run(cfg, chOSSiginal, mockBuilder, mockRunner, mockWatcher) + assert.NotNil(t, err, "build error") + assert.Equal(t, "error on watching files: watcher-error", err.Error()) + mockBuilder.AssertExpectations(t) + mockRunner.AssertExpectations(t) + mockWatcher.AssertExpectations(t) +} + +func TestGaperProgramExit(t *testing.T) { + testCases := []struct { + name string + exitStatus int + noRestartOn string + restart bool + }{ + { + name: "no restart on exit error with no-restart-on=error", + exitStatus: exitStatusError, + noRestartOn: NoRestartOnError, + restart: false, + }, + { + name: "no restart on exit success with no-restart-on=success", + exitStatus: exitStatusSuccess, + noRestartOn: NoRestartOnSuccess, + restart: false, + }, + { + name: "no restart on exit error with no-restart-on=exit", + exitStatus: exitStatusError, + noRestartOn: NoRestartOnExit, + restart: false, + }, + { + name: "no restart on exit success with no-restart-on=exit", + exitStatus: exitStatusSuccess, + noRestartOn: NoRestartOnExit, + restart: false, + }, + { + name: "restart on exit error with disabled no-restart-on", + exitStatus: exitStatusError, + restart: true, + }, + { + name: "restart on exit success with disabled no-restart-on", + exitStatus: exitStatusSuccess, + restart: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockBuilder := new(testdata.MockBuilder) + mockBuilder.On("Build").Return(nil) + + mockRunner := new(testdata.MockRunner) + cmd := &exec.Cmd{} + runnerErrorsChan := make(chan error) + mockRunner.On("Run").Return(cmd, nil) + mockRunner.On("Kill").Return(nil) + mockRunner.On("Errors").Return(runnerErrorsChan) + mockRunner.On("ExitStatus").Return(tc.exitStatus) + if tc.restart { + mockRunner.On("Exited").Return(true) + } + + mockWatcher := new(testdata.MockWacther) + watcherErrorsChan := make(chan error) + watcherEvetnsChan := make(chan string) + mockWatcher.On("Errors").Return(watcherErrorsChan) + mockWatcher.On("Events").Return(watcherEvetnsChan) + + dir := filepath.Join("testdata", "server") + + cfg := &Config{ + BinName: "test-srv", + BuildPath: dir, + NoRestartOn: tc.noRestartOn, + } + + chOSSiginal := make(chan os.Signal, 2) + go func() { + time.Sleep(1 * time.Second) + runnerErrorsChan <- errors.New("runner-error") + time.Sleep(1 * time.Second) + chOSSiginal <- syscall.SIGINT + }() + err := run(cfg, chOSSiginal, mockBuilder, mockRunner, mockWatcher) + assert.NotNil(t, err, "build error") + assert.Equal(t, "OS signal: interrupt", err.Error()) + mockBuilder.AssertExpectations(t) + mockRunner.AssertExpectations(t) + mockWatcher.AssertExpectations(t) + }) + } +} + +func TestGaperRestartExited(t *testing.T) { + mockBuilder := new(testdata.MockBuilder) + mockBuilder.On("Build").Return(nil) + + mockRunner := new(testdata.MockRunner) + cmd := &exec.Cmd{} + mockRunner.On("Run").Return(cmd, nil) + mockRunner.On("Exited").Return(true) + + err := restart(mockBuilder, mockRunner) + assert.Nil(t, err, "restart error") + mockBuilder.AssertExpectations(t) + mockRunner.AssertExpectations(t) +} + +func TestGaperRestartNotExited(t *testing.T) { + mockBuilder := new(testdata.MockBuilder) + mockBuilder.On("Build").Return(nil) + + mockRunner := new(testdata.MockRunner) + cmd := &exec.Cmd{} + mockRunner.On("Run").Return(cmd, nil) + mockRunner.On("Kill").Return(nil) + mockRunner.On("Exited").Return(false) + + err := restart(mockBuilder, mockRunner) + assert.Nil(t, err, "restart error") + mockBuilder.AssertExpectations(t) + mockRunner.AssertExpectations(t) +} + +func TestGaperRestartNotExitedKillFail(t *testing.T) { + mockBuilder := new(testdata.MockBuilder) + + mockRunner := new(testdata.MockRunner) + mockRunner.On("Kill").Return(errors.New("kill-error")) + mockRunner.On("Exited").Return(false) + + err := restart(mockBuilder, mockRunner) + assert.NotNil(t, err, "restart error") + assert.Equal(t, "kill error: kill-error", err.Error()) + mockBuilder.AssertExpectations(t) + mockRunner.AssertExpectations(t) +} + +func TestGaperRestartBuildFail(t *testing.T) { + mockBuilder := new(testdata.MockBuilder) + mockBuilder.On("Build").Return(errors.New("build-error")) + + mockRunner := new(testdata.MockRunner) + mockRunner.On("Exited").Return(true) + + err := restart(mockBuilder, mockRunner) + assert.NotNil(t, err, "restart error") + assert.Equal(t, "build error: build-error", err.Error()) + mockBuilder.AssertExpectations(t) + mockRunner.AssertExpectations(t) +} + +func TestGaperRestartRunFail(t *testing.T) { + mockBuilder := new(testdata.MockBuilder) + mockBuilder.On("Build").Return(nil) + + mockRunner := new(testdata.MockRunner) + cmd := &exec.Cmd{} + mockRunner.On("Run").Return(cmd, errors.New("run-error")) + mockRunner.On("Exited").Return(true) + + err := restart(mockBuilder, mockRunner) + assert.NotNil(t, err, "restart error") + assert.Equal(t, "run error: run-error", err.Error()) + mockBuilder.AssertExpectations(t) + mockRunner.AssertExpectations(t) } diff --git a/runner.go b/runner.go index e5fc1e3..bee7f90 100644 --- a/runner.go +++ b/runner.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "runtime" + "syscall" "time" ) @@ -22,6 +23,7 @@ type Runner interface { Kill() error Errors() chan error Exited() bool + ExitStatus(err error) int } type runner struct { @@ -111,6 +113,18 @@ func (r *runner) Errors() chan error { return r.errors } +// ExitStatus resolves the exit status +func (r *runner) ExitStatus(err error) int { + var exitStatus int + if exiterr, ok := err.(*exec.ExitError); ok { + if status, oks := exiterr.Sys().(syscall.WaitStatus); oks { + exitStatus = status.ExitStatus() + } + } + + return exitStatus +} + func (r *runner) runBin() error { r.command = exec.Command(r.bin, r.args...) // nolint gas stdout, err := r.command.StdoutPipe() diff --git a/runner_test.go b/runner_test.go index 05814d8..c13a23e 100644 --- a/runner_test.go +++ b/runner_test.go @@ -2,7 +2,9 @@ package gaper import ( "bytes" + "errors" "os" + "os/exec" "path/filepath" "runtime" "testing" @@ -48,3 +50,32 @@ func TestRunnerSuccessKill(t *testing.T) { errCmd := <-runner.Errors() assert.NotNil(t, errCmd, "kill program") } + +func TestRunnerExitedNotStarted(t *testing.T) { + runner := NewRunner(os.Stdout, os.Stderr, "", nil) + assert.Equal(t, runner.Exited(), false) +} + +func TestRunnerExitStatusNonExitError(t *testing.T) { + runner := NewRunner(os.Stdout, os.Stderr, "", nil) + err := errors.New("non exec.ExitError") + assert.Equal(t, runner.ExitStatus(err), 0) +} + +func testExit() { + os.Exit(1) +} + +func TestRunnerExitStatusExitError(t *testing.T) { + if os.Getenv("TEST_EXIT") == "1" { + testExit() + return + } + + cmd := exec.Command(os.Args[0], "-test.run=TestRunnerExitStatusExitError") + cmd.Env = append(os.Environ(), "TEST_EXIT=1") + err := cmd.Run() + + runner := NewRunner(os.Stdout, os.Stderr, "", nil) + assert.Equal(t, runner.ExitStatus(err), 1) +} diff --git a/testdata/mocks.go b/testdata/mocks.go new file mode 100644 index 0000000..37d7a74 --- /dev/null +++ b/testdata/mocks.go @@ -0,0 +1,80 @@ +package testdata + +import ( + "os/exec" + + "github.com/stretchr/testify/mock" +) + +// MockBuilder ... +type MockBuilder struct { + mock.Mock +} + +// Build ... +func (m *MockBuilder) Build() error { + args := m.Called() + return args.Error(0) +} + +// Binary ... +func (m *MockBuilder) Binary() string { + args := m.Called() + return args.String(0) +} + +// MockRunner ... +type MockRunner struct { + mock.Mock +} + +// Run ... +func (m *MockRunner) Run() (*exec.Cmd, error) { + args := m.Called() + return args.Get(0).(*exec.Cmd), args.Error(1) + +} + +// Kill ... +func (m *MockRunner) Kill() error { + args := m.Called() + return args.Error(0) +} + +// Errors ... +func (m *MockRunner) Errors() chan error { + args := m.Called() + return args.Get(0).(chan error) +} + +// Exited ... +func (m *MockRunner) Exited() bool { + args := m.Called() + return args.Bool(0) +} + +// ExitStatus ... +func (m *MockRunner) ExitStatus(err error) int { + args := m.Called() + return args.Int(0) +} + +// MockWacther ... +type MockWacther struct { + mock.Mock +} + +// Watch ... +func (m *MockWacther) Watch() {} + +// Events ... +func (m *MockWacther) Events() chan string { + args := m.Called() + return args.Get(0).(chan string) +} + +// Errors ... +func (m *MockWacther) Errors() chan error { + args := m.Called() + return args.Get(0).(chan error) +} diff --git a/watcher.go b/watcher.go index b1d4d78..23bd9f9 100644 --- a/watcher.go +++ b/watcher.go @@ -12,17 +12,24 @@ import ( ) // Watcher is a interface for the watch process -type Watcher struct { - PollInterval int - WatchItems map[string]bool - IgnoreItems map[string]bool - AllowedExtensions map[string]bool - Events chan string - Errors chan error +type Watcher interface { + Watch() + Errors() chan error + Events() chan string +} + +// watcher is a interface for the watch process +type watcher struct { + pollInterval int + watchItems map[string]bool + ignoreItems map[string]bool + allowedExtensions map[string]bool + events chan string + errors chan error } // NewWatcher creates a new watcher -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 } @@ -48,13 +55,13 @@ func NewWatcher(pollInterval int, watchItems []string, ignoreItems []string, ext logger.Debugf("Resolved watch paths: %v", watchPaths) logger.Debugf("Resolved ignore paths: %v", ignorePaths) - return &Watcher{ - Events: make(chan string), - Errors: make(chan error), - PollInterval: pollInterval, - WatchItems: watchPaths, - IgnoreItems: ignorePaths, - AllowedExtensions: allowedExts, + return &watcher{ + events: make(chan string), + errors: make(chan error), + pollInterval: pollInterval, + watchItems: watchPaths, + ignoreItems: ignorePaths, + allowedExtensions: allowedExts, }, nil } @@ -62,26 +69,37 @@ var startTime = time.Now() var errDetectedChange = errors.New("done") // Watch starts watching for file changes -func (w *Watcher) Watch() { +func (w *watcher) Watch() { for { - for watchPath := range w.WatchItems { + for watchPath := range w.watchItems { fileChanged, err := w.scanChange(watchPath) if err != nil { - w.Errors <- err + w.errors <- err return } if fileChanged != "" { - w.Events <- fileChanged + w.events <- fileChanged startTime = time.Now() } } - time.Sleep(time.Duration(w.PollInterval) * time.Millisecond) + time.Sleep(time.Duration(w.pollInterval) * time.Millisecond) } } -func (w *Watcher) scanChange(watchPath string) (string, error) { +// Events get events occurred during the watching +// these events are emited only a file changing is detected +func (w *watcher) Events() chan string { + return w.events +} + +// Errors get errors occurred during the watching +func (w *watcher) Errors() chan error { + return w.errors +} + +func (w *watcher) scanChange(watchPath string) (string, error) { logger.Debug("Watching ", watchPath) var fileChanged string @@ -92,12 +110,12 @@ func (w *Watcher) scanChange(watchPath string) (string, error) { return skipFile(info) } - if _, ignored := w.IgnoreItems[path]; ignored { + if _, ignored := w.ignoreItems[path]; ignored { return skipFile(info) } 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 return errDetectedChange } diff --git a/watcher_test.go b/watcher_test.go index 41a03bc..d6816b3 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -16,18 +16,19 @@ func TestWatcherDefaultValues(t *testing.T) { var ignoreItems []string var extensions []string - w, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions) + wt, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions) expectedPath := "testdata/server" if runtime.GOOS == OSWindows { expectedPath = "testdata\\server" } + w := wt.(*watcher) assert.Nil(t, err, "wacher error") - assert.Equal(t, 500, w.PollInterval) - assert.Equal(t, map[string]bool{expectedPath: true}, w.WatchItems) - assert.Len(t, w.IgnoreItems, 0) - assert.Equal(t, map[string]bool{".go": true}, w.AllowedExtensions) + assert.Equal(t, 500, w.pollInterval) + assert.Equal(t, map[string]bool{expectedPath: true}, w.watchItems) + assert.Len(t, w.ignoreItems, 0) + assert.Equal(t, map[string]bool{".go": true}, w.allowedExtensions) } func TestWatcherGlobPath(t *testing.T) { @@ -36,9 +37,10 @@ func TestWatcherGlobPath(t *testing.T) { ignoreItems := []string{"./testdata/**/*_test.go"} var extensions []string - w, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions) + wt, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions) assert.Nil(t, err, "wacher error") - assert.Equal(t, map[string]bool{"testdata/server/main_test.go": true}, w.IgnoreItems) + w := wt.(*watcher) + assert.Equal(t, map[string]bool{"testdata/server/main_test.go": true}, w.ignoreItems) } func TestWatcherRemoveOverlapdPaths(t *testing.T) { @@ -47,9 +49,10 @@ func TestWatcherRemoveOverlapdPaths(t *testing.T) { ignoreItems := []string{"./testdata/**/*", "./testdata/server"} var extensions []string - w, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions) + wt, err := NewWatcher(pollInterval, watchItems, ignoreItems, extensions) assert.Nil(t, err, "wacher error") - assert.Equal(t, map[string]bool{"./testdata/server": true}, w.IgnoreItems) + w := wt.(*watcher) + assert.Equal(t, map[string]bool{"./testdata/server": true}, w.ignoreItems) } func TestWatcherWatchChange(t *testing.T) { @@ -83,9 +86,9 @@ func TestWatcherWatchChange(t *testing.T) { os.Chtimes(mainfile, time.Now(), time.Now()) select { - case event := <-w.Events: + case event := <-w.Events(): assert.Equal(t, mainfile, event) - case err := <-w.Errors: + case err := <-w.Errors(): assert.Nil(t, err, "wacher event error") } }