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/
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
build: clean
@ -16,14 +21,21 @@ clean:
start:
go run cmd/main.go
test:
go test -v -race -failfast ./...
test:coverage
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)
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: 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 (
"fmt"
"os"
"sync"
"time"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
"github.com/go-vgo/robotgo"
"github.com/prashantgupta24/activity-tracker/pkg/activity"
"github.com/prashantgupta24/activity-tracker/pkg/tracker"
@ -16,25 +11,18 @@ 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"
logFileName = "logFile-amm-1"
logFileName = "logFile-amm-2"
)
//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
@ -47,37 +35,43 @@ func (m *MouseMover) Start() {
}
heartbeatCh := activityTracker.Start()
m.run(heartbeatCh, activityTracker)
}
go func(m *MouseMover) {
logger := getLogger(m, false) //set writeToFile=true only for debugging
m.updateRunningStatus(true)
func (m *MouseMover) run(heartbeatCh chan *tracker.Heartbeat, activityTracker *tracker.Instance) {
go func() {
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
var lastMoved time.Time
isSystemSleeping := false
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)
}()
@ -88,59 +82,32 @@ func (m *MouseMover) Start() {
logger.Errorf("timeout happened after %vms while trying to move mouse", timeout)
}
} 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")
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
}
}
}(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
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 {
@ -151,35 +118,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
}
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
import (
"os"
"testing"
"time"
"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
}
func TestSuite(t *testing.T) {
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
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()
mouseMover1 := GetInstance()
mouseMover1.Start()
mouseMover1.run(suite.heartbeatCh, suite.activityTracker)
time.Sleep(time.Millisecond * 500)
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()
mouseMover := GetInstance()
mouseMover.Start()
time.Sleep(time.Millisecond * 500) //wait for app to start
assert.True(t, mouseMover.isRunning(), "app should have started")
logFileName := "test1"
mouseMover.Quit()
time.Sleep(time.Millisecond * 500) //wait for app to stop
assert.False(t, mouseMover.isRunning(), "app should have stopped")
getLogger(mouseMover, true, logFileName)
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
}