diff --git a/.gitignore b/.gitignore index ade3b91..711de40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ vendor/ bin/ -log/ \ No newline at end of file +log/ + +# coverage +*.html +*.out \ No newline at end of file diff --git a/Makefile b/Makefile index dacc7dc..faf08c0 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +COVER_PROFILE=cover.out +COVER_HTML=cover.html + all: open build: clean @@ -16,8 +19,13 @@ clean: start: go run cmd/main.go -test: - go test -v -race -failfast ./... +coverage: $(COVER_HTML) + +$(COVER_HTML): $(COVER_PROFILE) + go tool cover -html=$(COVER_PROFILE) -o $(COVER_HTML) + +$(COVER_PROFILE): + go test -v -failfast -race -coverprofile=$(COVER_PROFILE) ./... vet: go vet $(shell glide nv) diff --git a/pkg/mousemover/mouseMover.go b/pkg/mousemover/mouseMover.go index 2270c31..db6461e 100644 --- a/pkg/mousemover/mouseMover.go +++ b/pkg/mousemover/mouseMover.go @@ -2,8 +2,6 @@ package mousemover import ( "fmt" - "os" - "sync" "time" "github.com/go-vgo/robotgo" @@ -13,14 +11,6 @@ import ( var instance *MouseMover -//MouseMover is the main struct for the app -type MouseMover struct { - quit chan struct{} - mutex sync.RWMutex - runningStatus bool - logFile *os.File -} - const ( timeout = 100 //ms logDir = "log" @@ -29,9 +19,10 @@ const ( //Start the main app func (m *MouseMover) Start() { - if m.isRunning() { + if m.state.isRunning() { return } + m.state = &state{} m.quit = make(chan struct{}) heartbeatInterval := 60 //value always in seconds @@ -49,38 +40,40 @@ func (m *MouseMover) Start() { func (m *MouseMover) run(heartbeatCh chan *tracker.Heartbeat, activityTracker *tracker.Instance) { go func() { - if m.isRunning() { + state := m.state + if state != nil && state.isRunning() { return } + state.updateRunningStatus(true) + logger := getLogger(m, false) //set writeToFile=true only for debugging - m.updateRunningStatus(true) movePixel := 10 - var lastMoved time.Time - isSystemSleeping := false - didNotMoveTimes := 0 + // var lastMoved time.Time + // didNotMoveTimes := 0 for { select { case heartbeat := <-heartbeatCh: if !heartbeat.WasAnyActivity { - if isSystemSleeping { + if state.isSystemSleeping() { logger.Infof("system sleeping") continue } mouseMoveSuccessCh := make(chan bool) - go moveAndCheck(movePixel, mouseMoveSuccessCh) + go moveAndCheck(state, movePixel, mouseMoveSuccessCh) select { case wasMouseMoveSuccess := <-mouseMoveSuccessCh: if wasMouseMoveSuccess { - lastMoved = time.Now() - logger.Infof("moved mouse at : %v\n\n", lastMoved) + state.updateLastMouseMovedTime(time.Now()) + logger.Infof("moved mouse at : %v\n\n", state.getLastMouseMovedTime()) movePixel *= -1 - didNotMoveTimes = 0 + state.updateDidNotMoveCount(0) } else { - didNotMoveTimes++ + didNotMoveCount := state.getDidNotMoveCount() + state.updateDidNotMoveCount(didNotMoveCount + 1) msg := fmt.Sprintf("Mouse pointer cannot be moved at %v. Last moved at %v. Happened %v times. See README for details.", - time.Now(), lastMoved, didNotMoveTimes) + time.Now(), state.getLastMouseMovedTime(), state.getDidNotMoveCount()) logger.Errorf(msg) - if didNotMoveTimes >= 3 { + if state.getDidNotMoveCount() >= 3 { go func() { robotgo.ShowAlert("Error with Automatic Mouse Mover", msg) }() @@ -96,16 +89,16 @@ func (m *MouseMover) run(heartbeatCh chan *tracker.Heartbeat, activityTracker *t for activityType, times := range heartbeat.ActivityMap { logger.Infof("activityType : %v times: %v\n", activityType, len(times)) if activityType == activity.MachineSleep { - isSystemSleeping = true + state.updateMachineSleepStatus(true) } else if activityType == activity.MachineWake { - isSystemSleeping = false + state.updateMachineSleepStatus(false) } } logger.Infof("\n\n\n") } case <-m.quit: logger.Infof("stopping mouse mover") - m.updateRunningStatus(false) + state.updateRunningStatus(false) activityTracker.Quit() return } @@ -116,7 +109,7 @@ func (m *MouseMover) run(heartbeatCh chan *tracker.Heartbeat, activityTracker *t //Quit the app func (m *MouseMover) Quit() { //making it idempotent - if m != nil && m.isRunning() { + if m != nil && m.state.isRunning() { m.quit <- struct{}{} } if m.logFile != nil { @@ -127,7 +120,9 @@ func (m *MouseMover) Quit() { //GetInstance gets the singleton instance for mouse mover app func GetInstance() *MouseMover { if instance == nil { - instance = &MouseMover{} + instance = &MouseMover{ + state: &state{}, + } } return instance } diff --git a/pkg/mousemover/mouseMoverUtil.go b/pkg/mousemover/mouseMoverUtil.go index ecda123..b686a69 100644 --- a/pkg/mousemover/mouseMoverUtil.go +++ b/pkg/mousemover/mouseMoverUtil.go @@ -2,6 +2,7 @@ package mousemover import ( "os" + "time" "github.com/go-vgo/robotgo" "github.com/sirupsen/logrus" @@ -36,7 +37,11 @@ func getLogger(m *MouseMover, doWriteToFile bool) *log.Logger { return logger } -func moveAndCheck(movePixel int, mouseMoveSuccessCh chan bool) { +func moveAndCheck(state *state, movePixel int, mouseMoveSuccessCh chan bool) { + if state.override != nil { //we don't want to move mouse for tests + mouseMoveSuccessCh <- state.override.valueToReturn + return + } currentX, currentY := robotgo.GetMousePos() moveToX := currentX + movePixel moveToY := currentY + movePixel @@ -52,13 +57,51 @@ func moveAndCheck(movePixel int, mouseMoveSuccessCh chan bool) { } } -func (m *MouseMover) isRunning() bool { - m.mutex.RLock() - defer m.mutex.RUnlock() - return m.runningStatus +//getters and setters for state variable +func (s *state) isRunning() bool { + s.mutex.RLock() + defer s.mutex.RUnlock() + return s.isAppRunning } -func (m *MouseMover) updateRunningStatus(isRunning bool) { - m.mutex.Lock() - defer m.mutex.Unlock() - m.runningStatus = isRunning + +func (s *state) updateRunningStatus(isRunning bool) { + s.mutex.Lock() + defer s.mutex.Unlock() + s.isAppRunning = isRunning +} + +func (s *state) isSystemSleeping() bool { + s.mutex.RLock() + defer s.mutex.RUnlock() + return s.isSysSleeping +} + +func (s *state) updateMachineSleepStatus(isSleeping bool) { + s.mutex.Lock() + defer s.mutex.Unlock() + s.isSysSleeping = isSleeping +} + +func (s *state) getLastMouseMovedTime() time.Time { + s.mutex.RLock() + defer s.mutex.RUnlock() + return s.lastMouseMovedTime +} + +func (s *state) updateLastMouseMovedTime(time time.Time) { + s.mutex.Lock() + defer s.mutex.Unlock() + s.lastMouseMovedTime = time +} + +func (s *state) getDidNotMoveCount() int { + s.mutex.RLock() + defer s.mutex.RUnlock() + return s.didNotMoveCount +} + +func (s *state) updateDidNotMoveCount(count int) { + s.mutex.Lock() + defer s.mutex.Unlock() + s.didNotMoveCount = count } diff --git a/pkg/mousemover/mouseMover_test.go b/pkg/mousemover/mouseMover_test.go index cca5df5..6606ca1 100644 --- a/pkg/mousemover/mouseMover_test.go +++ b/pkg/mousemover/mouseMover_test.go @@ -7,21 +7,23 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/prashantgupta24/activity-tracker/pkg/activity" "github.com/prashantgupta24/activity-tracker/pkg/tracker" ) type TestMover struct { suite.Suite activityTracker *tracker.Instance - heartbeatCh chan *tracker.Heartbeat + heartbeatCh chan *tracker.Heartbeat } func TestSuite(t *testing.T) { suite.Run(t, new(TestMover)) } + //Run once before all tests func (suite *TestMover) SetupSuite() { - heartbeatInterval := 60 + heartbeatInterval := 60 workerInterval := 10 suite.activityTracker = &tracker.Instance{ @@ -29,14 +31,20 @@ func (suite *TestMover) SetupSuite() { WorkerInterval: workerInterval, } - suite.heartbeatCh= make(chan *tracker.Heartbeat) + suite.heartbeatCh = make(chan *tracker.Heartbeat) } //Run once before each test func (suite *TestMover) SetupTest() { instance = nil } - +func (suite *TestMover) TestAppStart() { + t := suite.T() + mouseMover := GetInstance() + mouseMover.run(suite.heartbeatCh, suite.activityTracker) + time.Sleep(time.Millisecond * 500) //wait for app to start + assert.True(t, mouseMover.state.isRunning(), "app should have started") +} func (suite *TestMover) TestSingleton() { t := suite.T() @@ -46,15 +54,83 @@ func (suite *TestMover) TestSingleton() { time.Sleep(time.Millisecond * 500) mouseMover2 := GetInstance() - assert.True(t, mouseMover2.isRunning(), "instance should have started") + assert.True(t, mouseMover2.state.isRunning(), "instance should have started") } -func (suite *TestMover) TestAppStartAndStop() { + +func (suite *TestMover) TestSystemSleep() { t := suite.T() mouseMover := GetInstance() - mouseMover.run(suite.heartbeatCh, suite.activityTracker) + + heartbeatCh := make(chan *tracker.Heartbeat) + mouseMover.run(heartbeatCh, suite.activityTracker) time.Sleep(time.Millisecond * 500) //wait for app to start - assert.True(t, mouseMover.isRunning(), "app should have started") - mouseMover.Quit() - time.Sleep(time.Millisecond * 1000) //wait for app to stop - assert.False(t, mouseMover.isRunning(), "app should have stopped") + assert.True(t, mouseMover.state.isRunning(), "instance should have started") + assert.False(t, mouseMover.state.isSystemSleeping(), "machine should not be sleeping") + + //fake a machine-sleep activity + machineSleepActivityMap := make(map[activity.Type][]time.Time) + var timeArray []time.Time + timeArray = append(timeArray, time.Now()) + machineSleepActivityMap[activity.MachineSleep] = timeArray + heartbeatCh <- &tracker.Heartbeat{ + WasAnyActivity: true, + ActivityMap: machineSleepActivityMap, + } + time.Sleep(time.Millisecond * 500) //wait for it to be registered + assert.True(t, mouseMover.state.isSystemSleeping(), "machine should be sleeping now") +} + +func (suite *TestMover) TestMouseMoveSuccess() { + t := suite.T() + mouseMover := GetInstance() + + state := &state{ + override: &override{ + valueToReturn: true, + }, + } + mouseMover.state = state + heartbeatCh := make(chan *tracker.Heartbeat) + + mouseMover.run(heartbeatCh, suite.activityTracker) + time.Sleep(time.Millisecond * 500) //wait for app to start + assert.True(t, state.isRunning(), "instance should have started") + assert.False(t, state.isSystemSleeping(), "machine should not be sleeping") + assert.True(t, time.Time.IsZero(state.getLastMouseMovedTime()), "should be default") + assert.Equal(t, state.getDidNotMoveCount(), 0, "should be 0") + + heartbeatCh <- &tracker.Heartbeat{ + WasAnyActivity: false, + } + + time.Sleep(time.Millisecond * 500) //wait for it to be registered + assert.False(t, time.Time.IsZero(state.getLastMouseMovedTime()), "should be default but is ", state.getLastMouseMovedTime()) +} + +func (suite *TestMover) TestMouseMoveFailure() { + t := suite.T() + mouseMover := GetInstance() + + state := &state{ + override: &override{ + valueToReturn: false, + }, + } + mouseMover.state = state + heartbeatCh := make(chan *tracker.Heartbeat) + + mouseMover.run(heartbeatCh, suite.activityTracker) + time.Sleep(time.Millisecond * 500) //wait for app to start + assert.True(t, state.isRunning(), "instance should have started") + assert.False(t, state.isSystemSleeping(), "machine should not be sleeping") + assert.True(t, time.Time.IsZero(state.getLastMouseMovedTime()), "should be default") + assert.Equal(t, state.getDidNotMoveCount(), 0, "should be 0") + + heartbeatCh <- &tracker.Heartbeat{ + WasAnyActivity: false, + } + + time.Sleep(time.Millisecond * 500) //wait for it to be registered + assert.True(t, time.Time.IsZero(state.getLastMouseMovedTime()), "should be default but is ", state.getLastMouseMovedTime()) + assert.NotEqual(t, state.getDidNotMoveCount(), 0, "should not be 0") } diff --git a/pkg/mousemover/types.go b/pkg/mousemover/types.go new file mode 100644 index 0000000..f7265e7 --- /dev/null +++ b/pkg/mousemover/types.go @@ -0,0 +1,29 @@ +package mousemover + +import ( + "os" + "sync" + "time" +) + +//MouseMover is the main struct for the app +type MouseMover struct { + quit chan struct{} + logFile *os.File + state *state +} + +//state manages the internal working of the app +type state struct { + mutex sync.RWMutex + isAppRunning bool + isSysSleeping bool + lastMouseMovedTime time.Time + didNotMoveCount int + override *override +} + +//only needed for tests +type override struct { + valueToReturn bool +}