Merge pull request #13 from prashantgupta24/test_update

adding tests + godoc
This commit is contained in:
Prashant Gupta 2019-05-02 18:08:19 -07:00 committed by GitHub
commit ced87f1d96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 355 additions and 105 deletions

6
.gitignore vendored
View File

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

View File

@ -1,3 +1,8 @@
COVER_PROFILE=cover.out
COVER_HTML=cover.html
.PHONY: $(COVER_PROFILE) $(COVER_HTML)
all: open all: open
build: clean build: clean
@ -16,14 +21,21 @@ clean:
start: start:
go run cmd/main.go go run cmd/main.go
test: test:coverage
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: vet:
go vet $(shell glide nv) go vet $(shell glide nv)
lint: lint:
go list ./... | grep -v vendor | xargs -L1 golint -set_exit_status go list ./... | grep -v vendor | grep -v /assets/ |xargs -L1 golint -set_exit_status
.PHONY: build .PHONY: build
.PHONY: clean .PHONY: clean

24
pkg/mousemover/doc.go Normal file
View File

@ -0,0 +1,24 @@
/*
Ever felt the need to keep your machine awake without having to resort to the age-old
methods of installing an app that you don't trust or playing a video? Well, not anymore!
Introducing the simplest app on the market that has the sole purpose of moving your mouse
pointer at regular intervals so that your machine never sleeps! And best of all, it works
ONLY when you are not working, so be rest assured that the mouse won't start moving on its
own without the machine actually being idle.
Installation
The libary can be installed using:
go get -u github.com/automatic-mouse-mover/pkg/mousemover
Usage
Clone this repo and run Make, it should create the amm.app and open the folder where
it was built for you. You just have to drag and drop it to the Applications folder on your mac.
Refer to the README for more details.
*/
package mousemover

View File

@ -2,13 +2,8 @@ package mousemover
import ( import (
"fmt" "fmt"
"os"
"sync"
"time" "time"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
"github.com/go-vgo/robotgo" "github.com/go-vgo/robotgo"
"github.com/prashantgupta24/activity-tracker/pkg/activity" "github.com/prashantgupta24/activity-tracker/pkg/activity"
"github.com/prashantgupta24/activity-tracker/pkg/tracker" "github.com/prashantgupta24/activity-tracker/pkg/tracker"
@ -16,25 +11,18 @@ 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"
logFileName = "logFile-amm-1" logFileName = "logFile-amm-2"
) )
//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
@ -47,37 +35,43 @@ func (m *MouseMover) Start() {
} }
heartbeatCh := activityTracker.Start() heartbeatCh := activityTracker.Start()
m.run(heartbeatCh, activityTracker)
}
go func(m *MouseMover) { func (m *MouseMover) run(heartbeatCh chan *tracker.Heartbeat, activityTracker *tracker.Instance) {
logger := getLogger(m, false) //set writeToFile=true only for debugging go func() {
m.updateRunningStatus(true) state := m.state
if state != nil && state.isRunning() {
return
}
state.updateRunningStatus(true)
logger := getLogger(m, false, logFileName) //set writeToFile=true only for debugging
movePixel := 10 movePixel := 10
var lastMoved time.Time
isSystemSleeping := false
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)
}() }()
@ -88,59 +82,32 @@ func (m *MouseMover) Start() {
logger.Errorf("timeout happened after %vms while trying to move mouse", timeout) logger.Errorf("timeout happened after %vms while trying to move mouse", timeout)
} }
} else { } else {
logger.Infof("activity detected in the last %v seconds.", int(heartbeatInterval)) logger.Infof("activity detected in the last %v seconds.", int(activityTracker.HeartbeatInterval))
logger.Infof("Activity type:\n") logger.Infof("Activity type:\n")
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
} }
} }
}(m) }()
}
func (m *MouseMover) isRunning() bool {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.runningStatus
}
func (m *MouseMover) updateRunningStatus(isRunning bool) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.runningStatus = isRunning
}
func moveAndCheck(movePixel int, mouseMoveSuccessCh chan bool) {
currentX, currentY := robotgo.GetMousePos()
moveToX := currentX + movePixel
moveToY := currentY + movePixel
robotgo.MoveMouse(moveToX, moveToY)
//check if mouse moved. Sometimes mac users need to give
//extra permission for controlling the mouse
movedX, movedY := robotgo.GetMousePos()
if movedX == currentX && movedY == currentY {
mouseMoveSuccessCh <- false
} else {
mouseMoveSuccessCh <- true
}
} }
//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 {
@ -151,35 +118,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
} }
func getLogger(m *MouseMover, doWriteToFile bool) *log.Logger {
logger := log.New()
logger.Formatter = &logrus.TextFormatter{
FullTimestamp: true,
}
if doWriteToFile {
_, err := os.Stat(logDir)
if err != nil {
if os.IsNotExist(err) {
err = os.Mkdir(logDir, os.ModePerm)
if err != nil {
log.Fatalf("error creating dir: %v", err)
}
}
}
logFile, err := os.OpenFile(logDir+"/"+logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("error opening file: %v", err)
}
logger.SetOutput(logFile)
m.logFile = logFile
}
return logger
}

View File

@ -0,0 +1,107 @@
package mousemover
import (
"os"
"time"
"github.com/go-vgo/robotgo"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
)
func getLogger(m *MouseMover, doWriteToFile bool, filename string) *log.Logger {
logger := log.New()
logger.Formatter = &logrus.TextFormatter{
FullTimestamp: true,
}
if doWriteToFile {
_, err := os.Stat(logDir)
if err != nil {
if os.IsNotExist(err) {
err = os.Mkdir(logDir, os.ModePerm)
if err != nil {
log.Fatalf("error creating dir: %v", err)
}
}
}
logFile, err := os.OpenFile(logDir+"/"+filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("error opening file: %v", err)
}
logger.SetOutput(logFile)
m.logFile = logFile
}
return logger
}
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
robotgo.MoveMouse(moveToX, moveToY)
//check if mouse moved. Sometimes mac users need to give
//extra permission for controlling the mouse
movedX, movedY := robotgo.GetMousePos()
if movedX == currentX && movedY == currentY {
mouseMoveSuccessCh <- false
} else {
mouseMoveSuccessCh <- true
}
}
//getters and setters for state variable
func (s *state) isRunning() bool {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.isAppRunning
}
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
}

View File

@ -1,44 +1,177 @@
package mousemover package mousemover
import ( import (
"os"
"testing" "testing"
"time" "time"
"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"
) )
type TestMover struct { type TestMover struct {
suite.Suite suite.Suite
activityTracker *tracker.Instance
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
func (suite *TestMover) SetupSuite() {
heartbeatInterval := 60
workerInterval := 10
suite.activityTracker = &tracker.Instance{
HeartbeatInterval: heartbeatInterval,
WorkerInterval: workerInterval,
}
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()
mouseMover1 := GetInstance() mouseMover1 := GetInstance()
mouseMover1.Start() mouseMover1.run(suite.heartbeatCh, suite.activityTracker)
time.Sleep(time.Millisecond * 500) time.Sleep(time.Millisecond * 500)
mouseMover2 := GetInstance() mouseMover2 := GetInstance()
assert.True(t, mouseMover2.isRunning(), "instance should not have started") assert.True(t, mouseMover2.state.isRunning(), "instance should have started")
} }
func (suite *TestMover) TestAppStartAndStop() {
func (suite *TestMover) TestLogFile() {
t := suite.T() t := suite.T()
mouseMover := GetInstance() mouseMover := GetInstance()
mouseMover.Start() logFileName := "test1"
time.Sleep(time.Millisecond * 500) //wait for app to start
assert.True(t, mouseMover.isRunning(), "app should have started")
mouseMover.Quit() getLogger(mouseMover, true, logFileName)
time.Sleep(time.Millisecond * 500) //wait for app to stop
assert.False(t, mouseMover.isRunning(), "app should have stopped") filePath := logDir + "/" + logFileName
assert.FileExists(t, filePath, "log file should exist")
os.RemoveAll(filePath)
}
func (suite *TestMover) TestSystemSleepAndWake() {
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, 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 sleepTimeArray []time.Time
sleepTimeArray = append(sleepTimeArray, time.Now())
machineSleepActivityMap[activity.MachineSleep] = sleepTimeArray
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")
//assert app is sleeping
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.Equal(t, state.getDidNotMoveCount(), 0, "should be 0")
//fake a machine-wake activity
machineWakeActivityMap := make(map[activity.Type][]time.Time)
var wakeTimeArray []time.Time
wakeTimeArray = append(wakeTimeArray, time.Now())
machineWakeActivityMap[activity.MachineWake] = wakeTimeArray
heartbeatCh <- &tracker.Heartbeat{
WasAnyActivity: true,
ActivityMap: machineWakeActivityMap,
}
time.Sleep(time.Millisecond * 500) //wait for it to be registered
assert.False(t, mouseMover.state.isSystemSleeping(), "machine should be awake 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
}