added tests for app

This commit is contained in:
Prashant Gupta 2019-04-30 18:53:53 -07:00
parent bba12a2242
commit a60d85577a
6 changed files with 207 additions and 52 deletions

6
.gitignore vendored
View File

@ -1,3 +1,7 @@
vendor/ vendor/
bin/ bin/
log/ log/
# coverage
*.html
*.out

View File

@ -1,3 +1,6 @@
COVER_PROFILE=cover.out
COVER_HTML=cover.html
all: open all: open
build: clean build: clean
@ -16,8 +19,13 @@ clean:
start: start:
go run cmd/main.go go run cmd/main.go
test: coverage: $(COVER_HTML)
go test -v -race -failfast ./...
$(COVER_HTML): $(COVER_PROFILE)
go tool cover -html=$(COVER_PROFILE) -o $(COVER_HTML)
$(COVER_PROFILE):
go test -v -failfast -race -coverprofile=$(COVER_PROFILE) ./...
vet: vet:
go vet $(shell glide nv) go vet $(shell glide nv)

View File

@ -2,8 +2,6 @@ package mousemover
import ( import (
"fmt" "fmt"
"os"
"sync"
"time" "time"
"github.com/go-vgo/robotgo" "github.com/go-vgo/robotgo"
@ -13,14 +11,6 @@ import (
var instance *MouseMover 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 ( const (
timeout = 100 //ms timeout = 100 //ms
logDir = "log" logDir = "log"
@ -29,9 +19,10 @@ const (
//Start the main app //Start the main app
func (m *MouseMover) Start() { func (m *MouseMover) Start() {
if m.isRunning() { if m.state.isRunning() {
return return
} }
m.state = &state{}
m.quit = make(chan struct{}) m.quit = make(chan struct{})
heartbeatInterval := 60 //value always in seconds 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) { func (m *MouseMover) run(heartbeatCh chan *tracker.Heartbeat, activityTracker *tracker.Instance) {
go func() { go func() {
if m.isRunning() { state := m.state
if state != nil && state.isRunning() {
return return
} }
state.updateRunningStatus(true)
logger := getLogger(m, false) //set writeToFile=true only for debugging logger := getLogger(m, false) //set writeToFile=true only for debugging
m.updateRunningStatus(true)
movePixel := 10 movePixel := 10
var lastMoved time.Time // var lastMoved time.Time
isSystemSleeping := false // didNotMoveTimes := 0
didNotMoveTimes := 0
for { for {
select { select {
case heartbeat := <-heartbeatCh: case heartbeat := <-heartbeatCh:
if !heartbeat.WasAnyActivity { if !heartbeat.WasAnyActivity {
if isSystemSleeping { if state.isSystemSleeping() {
logger.Infof("system sleeping") logger.Infof("system sleeping")
continue continue
} }
mouseMoveSuccessCh := make(chan bool) mouseMoveSuccessCh := make(chan bool)
go moveAndCheck(movePixel, mouseMoveSuccessCh) go moveAndCheck(state, movePixel, mouseMoveSuccessCh)
select { select {
case wasMouseMoveSuccess := <-mouseMoveSuccessCh: case wasMouseMoveSuccess := <-mouseMoveSuccessCh:
if wasMouseMoveSuccess { if wasMouseMoveSuccess {
lastMoved = time.Now() state.updateLastMouseMovedTime(time.Now())
logger.Infof("moved mouse at : %v\n\n", lastMoved) logger.Infof("moved mouse at : %v\n\n", state.getLastMouseMovedTime())
movePixel *= -1 movePixel *= -1
didNotMoveTimes = 0 state.updateDidNotMoveCount(0)
} else { } 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.", 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) logger.Errorf(msg)
if didNotMoveTimes >= 3 { if state.getDidNotMoveCount() >= 3 {
go func() { go func() {
robotgo.ShowAlert("Error with Automatic Mouse Mover", msg) 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 { for activityType, times := range heartbeat.ActivityMap {
logger.Infof("activityType : %v times: %v\n", activityType, len(times)) logger.Infof("activityType : %v times: %v\n", activityType, len(times))
if activityType == activity.MachineSleep { if activityType == activity.MachineSleep {
isSystemSleeping = true state.updateMachineSleepStatus(true)
} else if activityType == activity.MachineWake { } else if activityType == activity.MachineWake {
isSystemSleeping = false state.updateMachineSleepStatus(false)
} }
} }
logger.Infof("\n\n\n") logger.Infof("\n\n\n")
} }
case <-m.quit: case <-m.quit:
logger.Infof("stopping mouse mover") logger.Infof("stopping mouse mover")
m.updateRunningStatus(false) state.updateRunningStatus(false)
activityTracker.Quit() activityTracker.Quit()
return return
} }
@ -116,7 +109,7 @@ func (m *MouseMover) run(heartbeatCh chan *tracker.Heartbeat, activityTracker *t
//Quit the app //Quit the app
func (m *MouseMover) Quit() { func (m *MouseMover) Quit() {
//making it idempotent //making it idempotent
if m != nil && m.isRunning() { if m != nil && m.state.isRunning() {
m.quit <- struct{}{} m.quit <- struct{}{}
} }
if m.logFile != nil { if m.logFile != nil {
@ -127,7 +120,9 @@ func (m *MouseMover) Quit() {
//GetInstance gets the singleton instance for mouse mover app //GetInstance gets the singleton instance for mouse mover app
func GetInstance() *MouseMover { func GetInstance() *MouseMover {
if instance == nil { if instance == nil {
instance = &MouseMover{} instance = &MouseMover{
state: &state{},
}
} }
return instance return instance
} }

View File

@ -2,6 +2,7 @@ package mousemover
import ( import (
"os" "os"
"time"
"github.com/go-vgo/robotgo" "github.com/go-vgo/robotgo"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -36,7 +37,11 @@ func getLogger(m *MouseMover, doWriteToFile bool) *log.Logger {
return 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() currentX, currentY := robotgo.GetMousePos()
moveToX := currentX + movePixel moveToX := currentX + movePixel
moveToY := currentY + movePixel moveToY := currentY + movePixel
@ -52,13 +57,51 @@ func moveAndCheck(movePixel int, mouseMoveSuccessCh chan bool) {
} }
} }
func (m *MouseMover) isRunning() bool { //getters and setters for state variable
m.mutex.RLock() func (s *state) isRunning() bool {
defer m.mutex.RUnlock() s.mutex.RLock()
return m.runningStatus defer s.mutex.RUnlock()
return s.isAppRunning
} }
func (m *MouseMover) updateRunningStatus(isRunning bool) {
m.mutex.Lock() func (s *state) updateRunningStatus(isRunning bool) {
defer m.mutex.Unlock() s.mutex.Lock()
m.runningStatus = isRunning 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
} }

View File

@ -7,21 +7,23 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/prashantgupta24/activity-tracker/pkg/activity"
"github.com/prashantgupta24/activity-tracker/pkg/tracker" "github.com/prashantgupta24/activity-tracker/pkg/tracker"
) )
type TestMover struct { type TestMover struct {
suite.Suite suite.Suite
activityTracker *tracker.Instance activityTracker *tracker.Instance
heartbeatCh chan *tracker.Heartbeat heartbeatCh chan *tracker.Heartbeat
} }
func TestSuite(t *testing.T) { func TestSuite(t *testing.T) {
suite.Run(t, new(TestMover)) suite.Run(t, new(TestMover))
} }
//Run once before all tests //Run once before all tests
func (suite *TestMover) SetupSuite() { func (suite *TestMover) SetupSuite() {
heartbeatInterval := 60 heartbeatInterval := 60
workerInterval := 10 workerInterval := 10
suite.activityTracker = &tracker.Instance{ suite.activityTracker = &tracker.Instance{
@ -29,14 +31,20 @@ func (suite *TestMover) SetupSuite() {
WorkerInterval: workerInterval, WorkerInterval: workerInterval,
} }
suite.heartbeatCh= make(chan *tracker.Heartbeat) suite.heartbeatCh = make(chan *tracker.Heartbeat)
} }
//Run once before each test //Run once before each test
func (suite *TestMover) SetupTest() { func (suite *TestMover) SetupTest() {
instance = nil 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() { func (suite *TestMover) TestSingleton() {
t := suite.T() t := suite.T()
@ -46,15 +54,83 @@ func (suite *TestMover) TestSingleton() {
time.Sleep(time.Millisecond * 500) time.Sleep(time.Millisecond * 500)
mouseMover2 := GetInstance() 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() t := suite.T()
mouseMover := GetInstance() 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 time.Sleep(time.Millisecond * 500) //wait for app to start
assert.True(t, mouseMover.isRunning(), "app should have started") assert.True(t, mouseMover.state.isRunning(), "instance should have started")
mouseMover.Quit() assert.False(t, mouseMover.state.isSystemSleeping(), "machine should not be sleeping")
time.Sleep(time.Millisecond * 1000) //wait for app to stop
assert.False(t, mouseMover.isRunning(), "app should have stopped") //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")
} }

29
pkg/mousemover/types.go Normal file
View File

@ -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
}