12 changed files with 1024 additions and 0 deletions
-
24.drone.yml
-
17vendor/github.com/judwhite/go-svc/svc/doc.go
-
62vendor/github.com/judwhite/go-svc/svc/internal/test/test.go
-
34vendor/github.com/judwhite/go-svc/svc/svc.go
-
36vendor/github.com/judwhite/go-svc/svc/svc_common_test.go
-
39vendor/github.com/judwhite/go-svc/svc/svc_other.go
-
68vendor/github.com/judwhite/go-svc/svc/svc_other_test.go
-
145vendor/github.com/judwhite/go-svc/svc/svc_windows.go
-
438vendor/github.com/judwhite/go-svc/svc/svc_windows_test.go
-
148vendor/github.com/judwhite/go-svc/svc/test.sh
-
9vendor/github.com/judwhite/go-svc/svc/test_cover.sh
-
4vendor/vgo.list
@ -0,0 +1,24 @@ |
|||
matrix: |
|||
GO_VERSION: |
|||
- "1.10" |
|||
GOOS: |
|||
- "windows" |
|||
- "linux" |
|||
- "darwin" |
|||
GOARCH: |
|||
- "amd64" |
|||
pipeline: |
|||
build: |
|||
pipeline: |
|||
build: |
|||
image: golang:${GO_VERSION} |
|||
environment: |
|||
- FILENAME=${DRONE_REPO_NAME}-${GOOS}-${GOARCH} |
|||
commands: |
|||
- go build -ldflags "-s -w" -o ${FILENAME/windows-amd64/windows-amd64.exe} |
|||
release: |
|||
image: plugins/github-release |
|||
secrets: [ github_token ] |
|||
files: app/build/outputs/apk/app-debug.apk |
|||
when: |
|||
event: tag |
@ -0,0 +1,17 @@ |
|||
/* |
|||
Package svc helps you write Windows Service executables without getting in the way of other target platforms. |
|||
|
|||
To get started, implement the Init, Start, and Stop methods to do |
|||
any work needed during these steps. |
|||
|
|||
Init and Start cannot block. Launch long-running your code in a new Goroutine. |
|||
|
|||
Stop may block for a short amount of time to attempt clean shutdown. |
|||
|
|||
Call svc.Run() with a reference to your svc.Service implementation to start your program. |
|||
|
|||
When running in console mode Ctrl+C is treated like a Stop Service signal. |
|||
|
|||
For a full guide visit https://github.com/judwhite/go-svc
|
|||
*/ |
|||
package svc |
@ -0,0 +1,62 @@ |
|||
package test |
|||
|
|||
import ( |
|||
"path/filepath" |
|||
"reflect" |
|||
"runtime" |
|||
"testing" |
|||
) |
|||
|
|||
// Equal asserts two parameters are equal by using reflect.DeepEqual.
|
|||
func Equal(t *testing.T, expected, actual interface{}) { |
|||
if !reflect.DeepEqual(expected, actual) { |
|||
_, file, line, _ := runtime.Caller(1) |
|||
t.Logf("\033[31m%s:%d:\n\n\t %#v (expected)\n\n\t!= %#v (actual)\033[39m\n\n", |
|||
filepath.Base(file), line, expected, actual) |
|||
t.FailNow() |
|||
} |
|||
} |
|||
|
|||
// NotEqual asserts two parameters are not equal by using reflect.DeepEqual.
|
|||
func NotEqual(t *testing.T, expected, actual interface{}) { |
|||
if !reflect.DeepEqual(expected, actual) { |
|||
_, file, line, _ := runtime.Caller(1) |
|||
t.Logf("\033[31m%s:%d:\n\n\tvalue should not equal %#v\033[39m\n\n", |
|||
filepath.Base(file), line, actual) |
|||
t.FailNow() |
|||
} |
|||
} |
|||
|
|||
// Nil asserts the parameter is nil.
|
|||
func Nil(t *testing.T, object interface{}) { |
|||
if !isNil(object) { |
|||
_, file, line, _ := runtime.Caller(1) |
|||
t.Logf("\033[31m%s:%d:\n\n\t <nil> (expected)\n\n\t!= %#v (actual)\033[39m\n\n", |
|||
filepath.Base(file), line, object) |
|||
t.FailNow() |
|||
} |
|||
} |
|||
|
|||
// NotNil asserts the parameter is not nil.
|
|||
func NotNil(t *testing.T, object interface{}) { |
|||
if isNil(object) { |
|||
_, file, line, _ := runtime.Caller(1) |
|||
t.Logf("\033[31m%s:%d:\n\n\tExpected value not to be <nil>\033[39m\n\n", |
|||
filepath.Base(file), line) |
|||
t.FailNow() |
|||
} |
|||
} |
|||
|
|||
func isNil(object interface{}) bool { |
|||
if object == nil { |
|||
return true |
|||
} |
|||
|
|||
value := reflect.ValueOf(object) |
|||
kind := value.Kind() |
|||
if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() { |
|||
return true |
|||
} |
|||
|
|||
return false |
|||
} |
@ -0,0 +1,34 @@ |
|||
package svc |
|||
|
|||
import "os/signal" |
|||
|
|||
// Create variable signal.Notify function so we can mock it in tests
|
|||
var signalNotify = signal.Notify |
|||
|
|||
// Service interface contains Start and Stop methods which are called
|
|||
// when the service is started and stopped. The Init method is called
|
|||
// before the service is started, and after it's determined if the program
|
|||
// is running as a Windows Service.
|
|||
//
|
|||
// The Start method must be non-blocking.
|
|||
//
|
|||
// Implement this interface and pass it to the Run function to start your program.
|
|||
type Service interface { |
|||
// Init is called before the program/service is started and after it's
|
|||
// determined if the program is running as a Windows Service.
|
|||
Init(Environment) error |
|||
|
|||
// Start is called after Init. This method must be non-blocking.
|
|||
Start() error |
|||
|
|||
// Stop is called in response to os.Interrupt, os.Kill, or when a
|
|||
// Windows Service is stopped.
|
|||
Stop() error |
|||
} |
|||
|
|||
// Environment contains information about the environment
|
|||
// your application is running in.
|
|||
type Environment interface { |
|||
// IsWindowsService returns true if the program is running as a Windows Service.
|
|||
IsWindowsService() bool |
|||
} |
@ -0,0 +1,36 @@ |
|||
package svc |
|||
|
|||
type mockProgram struct { |
|||
start func() error |
|||
stop func() error |
|||
init func(Environment) error |
|||
} |
|||
|
|||
func (p *mockProgram) Start() error { |
|||
return p.start() |
|||
} |
|||
|
|||
func (p *mockProgram) Stop() error { |
|||
return p.stop() |
|||
} |
|||
|
|||
func (p *mockProgram) Init(wse Environment) error { |
|||
return p.init(wse) |
|||
} |
|||
|
|||
func makeProgram(startCalled, stopCalled, initCalled *int) *mockProgram { |
|||
return &mockProgram{ |
|||
start: func() error { |
|||
*startCalled++ |
|||
return nil |
|||
}, |
|||
stop: func() error { |
|||
*stopCalled++ |
|||
return nil |
|||
}, |
|||
init: func(wse Environment) error { |
|||
*initCalled++ |
|||
return nil |
|||
}, |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
// +build !windows
|
|||
|
|||
package svc |
|||
|
|||
import ( |
|||
"os" |
|||
"syscall" |
|||
) |
|||
|
|||
// Run runs your Service.
|
|||
//
|
|||
// Run will block until one of the signals specified in sig is received.
|
|||
// If sig is empty syscall.SIGINT and syscall.SIGTERM are used by default.
|
|||
func Run(service Service, sig ...os.Signal) error { |
|||
env := environment{} |
|||
if err := service.Init(env); err != nil { |
|||
return err |
|||
} |
|||
|
|||
if err := service.Start(); err != nil { |
|||
return err |
|||
} |
|||
|
|||
if len(sig) == 0 { |
|||
sig = []os.Signal{syscall.SIGINT, syscall.SIGTERM} |
|||
} |
|||
|
|||
signalChan := make(chan os.Signal, 1) |
|||
signalNotify(signalChan, sig...) |
|||
<-signalChan |
|||
|
|||
return service.Stop() |
|||
} |
|||
|
|||
type environment struct{} |
|||
|
|||
func (environment) IsWindowsService() bool { |
|||
return false |
|||
} |
@ -0,0 +1,68 @@ |
|||
// +build !windows
|
|||
|
|||
package svc |
|||
|
|||
import ( |
|||
"os" |
|||
"syscall" |
|||
"testing" |
|||
|
|||
"github.com/judwhite/go-svc/svc/internal/test" |
|||
) |
|||
|
|||
func TestDefaultSignalHandling(t *testing.T) { |
|||
signals := []os.Signal{syscall.SIGINT, syscall.SIGTERM} // default signals handled
|
|||
for _, signal := range signals { |
|||
testSignalNotify(t, signal) |
|||
} |
|||
} |
|||
|
|||
func TestUserDefinedSignalHandling(t *testing.T) { |
|||
signals := []os.Signal{syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP} |
|||
for _, signal := range signals { |
|||
testSignalNotify(t, signal, signals...) |
|||
} |
|||
} |
|||
|
|||
func testSignalNotify(t *testing.T, signal os.Signal, sig ...os.Signal) { |
|||
// arrange
|
|||
|
|||
// sigChan is the chan we'll send to here. if a signal matches a registered signal
|
|||
// type in the Run function (in svc_other.go) the signal will be delegated to the
|
|||
// channel passed to signalNotify, which is created in the Run function in svc_other.go.
|
|||
// shortly: we send here and the Run function gets it if it matches the filter.
|
|||
sigChan := make(chan os.Signal) |
|||
|
|||
var startCalled, stopCalled, initCalled int |
|||
prg := makeProgram(&startCalled, &stopCalled, &initCalled) |
|||
|
|||
signalNotify = func(c chan<- os.Signal, sig ...os.Signal) { |
|||
if c == nil { |
|||
panic("os/signal: Notify using nil channel") |
|||
} |
|||
|
|||
go func() { |
|||
for val := range sigChan { |
|||
for _, registeredSig := range sig { |
|||
if val == registeredSig { |
|||
c <- val |
|||
} |
|||
} |
|||
} |
|||
}() |
|||
} |
|||
|
|||
go func() { |
|||
sigChan <- signal |
|||
}() |
|||
|
|||
// act
|
|||
if err := Run(prg, sig...); err != nil { |
|||
t.Fatal(err) |
|||
} |
|||
|
|||
// assert
|
|||
test.Equal(t, 1, startCalled) |
|||
test.Equal(t, 1, stopCalled) |
|||
test.Equal(t, 1, initCalled) |
|||
} |
@ -0,0 +1,145 @@ |
|||
// +build windows
|
|||
|
|||
package svc |
|||
|
|||
import ( |
|||
"os" |
|||
"sync" |
|||
"syscall" |
|||
|
|||
wsvc "golang.org/x/sys/windows/svc" |
|||
) |
|||
|
|||
// Create variables for svc and signal functions so we can mock them in tests
|
|||
var svcIsAnInteractiveSession = wsvc.IsAnInteractiveSession |
|||
var svcRun = wsvc.Run |
|||
|
|||
type windowsService struct { |
|||
i Service |
|||
errSync sync.Mutex |
|||
stopStartErr error |
|||
isInteractive bool |
|||
signals []os.Signal |
|||
Name string |
|||
} |
|||
|
|||
// Run runs an implementation of the Service interface.
|
|||
//
|
|||
// Run will block until the Windows Service is stopped or Ctrl+C is pressed if
|
|||
// running from the console.
|
|||
//
|
|||
// Stopping the Windows Service and Ctrl+C will call the Service's Stop method to
|
|||
// initiate a graceful shutdown.
|
|||
//
|
|||
// Note that WM_CLOSE is not handled (end task) and the Service's Stop method will
|
|||
// not be called.
|
|||
//
|
|||
// The sig parameter is to keep parity with the non-Windows API. Only syscall.SIGINT
|
|||
// (Ctrl+C) can be handled on Windows. Nevertheless, you can override the default
|
|||
// signals which are handled by specifying sig.
|
|||
func Run(service Service, sig ...os.Signal) error { |
|||
var err error |
|||
|
|||
interactive, err := svcIsAnInteractiveSession() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
if len(sig) == 0 { |
|||
sig = []os.Signal{syscall.SIGINT} |
|||
} |
|||
|
|||
ws := &windowsService{ |
|||
i: service, |
|||
isInteractive: interactive, |
|||
signals: sig, |
|||
} |
|||
|
|||
if err = service.Init(ws); err != nil { |
|||
return err |
|||
} |
|||
|
|||
return ws.run() |
|||
} |
|||
|
|||
func (ws *windowsService) setError(err error) { |
|||
ws.errSync.Lock() |
|||
ws.stopStartErr = err |
|||
ws.errSync.Unlock() |
|||
} |
|||
|
|||
func (ws *windowsService) getError() error { |
|||
ws.errSync.Lock() |
|||
err := ws.stopStartErr |
|||
ws.errSync.Unlock() |
|||
return err |
|||
} |
|||
|
|||
func (ws *windowsService) IsWindowsService() bool { |
|||
return !ws.isInteractive |
|||
} |
|||
|
|||
func (ws *windowsService) run() error { |
|||
ws.setError(nil) |
|||
if ws.IsWindowsService() { |
|||
// Return error messages from start and stop routines
|
|||
// that get executed in the Execute method.
|
|||
// Guarded with a mutex as it may run a different thread
|
|||
// (callback from Windows).
|
|||
runErr := svcRun(ws.Name, ws) |
|||
startStopErr := ws.getError() |
|||
if startStopErr != nil { |
|||
return startStopErr |
|||
} |
|||
if runErr != nil { |
|||
return runErr |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
err := ws.i.Start() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
signalChan := make(chan os.Signal, 1) |
|||
signalNotify(signalChan, ws.signals...) |
|||
<-signalChan |
|||
|
|||
err = ws.i.Stop() |
|||
|
|||
return err |
|||
} |
|||
|
|||
// Execute is invoked by Windows
|
|||
func (ws *windowsService) Execute(args []string, r <-chan wsvc.ChangeRequest, changes chan<- wsvc.Status) (bool, uint32) { |
|||
const cmdsAccepted = wsvc.AcceptStop | wsvc.AcceptShutdown |
|||
changes <- wsvc.Status{State: wsvc.StartPending} |
|||
|
|||
if err := ws.i.Start(); err != nil { |
|||
ws.setError(err) |
|||
return true, 1 |
|||
} |
|||
|
|||
changes <- wsvc.Status{State: wsvc.Running, Accepts: cmdsAccepted} |
|||
loop: |
|||
for { |
|||
c := <-r |
|||
switch c.Cmd { |
|||
case wsvc.Interrogate: |
|||
changes <- c.CurrentStatus |
|||
case wsvc.Stop, wsvc.Shutdown: |
|||
changes <- wsvc.Status{State: wsvc.StopPending} |
|||
err := ws.i.Stop() |
|||
if err != nil { |
|||
ws.setError(err) |
|||
return true, 2 |
|||
} |
|||
break loop |
|||
default: |
|||
continue loop |
|||
} |
|||
} |
|||
|
|||
return false, 0 |
|||
} |
@ -0,0 +1,438 @@ |
|||
// +build windows
|
|||
|
|||
package svc |
|||
|
|||
import ( |
|||
"errors" |
|||
"os" |
|||
"syscall" |
|||
"testing" |
|||
"time" |
|||
|
|||
"github.com/judwhite/go-svc/svc/internal/test" |
|||
wsvc "golang.org/x/sys/windows/svc" |
|||
) |
|||
|
|||
func setupWinServiceTest(wsf *mockWinServiceFuncs) { |
|||
// wsfWrapper allows signalNotify, svcIsInteractive, and svcRun to be set once.
|
|||
// Inidivual test functions set "wsf" to add behavior.
|
|||
wsfWrapper := &mockWinServiceFuncs{ |
|||
signalNotify: func(c chan<- os.Signal, sig ...os.Signal) { |
|||
if c == nil { |
|||
panic("os/signal: Notify using nil channel") |
|||
} |
|||
|
|||
if wsf.signalNotify != nil { |
|||
wsf.signalNotify(c, sig...) |
|||
} else { |
|||
wsf1 := *wsf |
|||
go func() { |
|||
for val := range wsf1.sigChan { |
|||
for _, registeredSig := range sig { |
|||
if val == registeredSig { |
|||
c <- val |
|||
} |
|||
} |
|||
} |
|||
}() |
|||
} |
|||
}, |
|||
svcIsInteractive: func() (bool, error) { |
|||
return wsf.svcIsInteractive() |
|||
}, |
|||
svcRun: func(name string, handler wsvc.Handler) error { |
|||
return wsf.svcRun(name, handler) |
|||
}, |
|||
} |
|||
|
|||
signalNotify = wsfWrapper.signalNotify |
|||
svcIsAnInteractiveSession = wsfWrapper.svcIsInteractive |
|||
svcRun = wsfWrapper.svcRun |
|||
} |
|||
|
|||
type mockWinServiceFuncs struct { |
|||
signalNotify func(chan<- os.Signal, ...os.Signal) |
|||
svcIsInteractive func() (bool, error) |
|||
sigChan chan os.Signal |
|||
svcRun func(string, wsvc.Handler) error |
|||
ws *windowsService |
|||
executeReturnedBool bool |
|||
executeReturnedUInt32 uint32 |
|||
changes []wsvc.Status |
|||
} |
|||
|
|||
func setWindowsServiceFuncs(isInteractive bool, onRunningSendCmd *wsvc.Cmd) (*mockWinServiceFuncs, chan<- wsvc.ChangeRequest) { |
|||
changeRequestChan := make(chan wsvc.ChangeRequest, 4) |
|||
changesChan := make(chan wsvc.Status) |
|||
done := make(chan struct{}) |
|||
|
|||
var wsf *mockWinServiceFuncs |
|||
wsf = &mockWinServiceFuncs{ |
|||
sigChan: make(chan os.Signal), |
|||
svcIsInteractive: func() (bool, error) { |
|||
return isInteractive, nil |
|||
}, |
|||
svcRun: func(name string, handler wsvc.Handler) error { |
|||
wsf.ws = handler.(*windowsService) |
|||
wsf.executeReturnedBool, wsf.executeReturnedUInt32 = handler.Execute(nil, changeRequestChan, changesChan) |
|||
done <- struct{}{} |
|||
return nil |
|||
}, |
|||
} |
|||
|
|||
var currentState wsvc.State |
|||
|
|||
go func() { |
|||
loop: |
|||
for { |
|||
select { |
|||
case change := <-changesChan: |
|||
wsf.changes = append(wsf.changes, change) |
|||
currentState = change.State |
|||
|
|||
if change.State == wsvc.Running && onRunningSendCmd != nil { |
|||
changeRequestChan <- wsvc.ChangeRequest{ |
|||
Cmd: *onRunningSendCmd, |
|||
CurrentStatus: wsvc.Status{State: currentState}, |
|||
} |
|||
} |
|||
case <-done: |
|||
break loop |
|||
} |
|||
} |
|||
}() |
|||
|
|||
setupWinServiceTest(wsf) |
|||
|
|||
return wsf, changeRequestChan |
|||
} |
|||
|
|||
func TestWinService_RunWindowsService_NonInteractive(t *testing.T) { |
|||
for _, svcCmd := range []wsvc.Cmd{wsvc.Stop, wsvc.Shutdown} { |
|||
testRunWindowsServiceNonInteractive(t, svcCmd) |
|||
} |
|||
} |
|||
|
|||
func testRunWindowsServiceNonInteractive(t *testing.T, svcCmd wsvc.Cmd) { |
|||
// arrange
|
|||
var startCalled, stopCalled, initCalled int |
|||
prg := makeProgram(&startCalled, &stopCalled, &initCalled) |
|||
|
|||
wsf, _ := setWindowsServiceFuncs(false, &svcCmd) |
|||
|
|||
// act
|
|||
if err := Run(prg); err != nil { |
|||
t.Fatal(err) |
|||
} |
|||
|
|||
// assert
|
|||
changes := wsf.changes |
|||
|
|||
test.Equal(t, 1, startCalled) |
|||
test.Equal(t, 1, stopCalled) |
|||
test.Equal(t, 1, initCalled) |
|||
|
|||
test.Equal(t, 3, len(changes)) |
|||
test.Equal(t, wsvc.StartPending, changes[0].State) |
|||
test.Equal(t, wsvc.Running, changes[1].State) |
|||
test.Equal(t, wsvc.StopPending, changes[2].State) |
|||
|
|||
test.Equal(t, false, wsf.executeReturnedBool) |
|||
test.Equal(t, uint32(0), wsf.executeReturnedUInt32) |
|||
|
|||
test.Nil(t, wsf.ws.getError()) |
|||
} |
|||
|
|||
func TestRunWindowsServiceNonInteractive_StartError(t *testing.T) { |
|||
// arrange
|
|||
var startCalled, stopCalled, initCalled int |
|||
prg := makeProgram(&startCalled, &stopCalled, &initCalled) |
|||
prg.start = func() error { |
|||
startCalled++ |
|||
return errors.New("start error") |
|||
} |
|||
|
|||
svcStop := wsvc.Stop |
|||
wsf, _ := setWindowsServiceFuncs(false, &svcStop) |
|||
|
|||
// act
|
|||
err := Run(prg) |
|||
|
|||
// assert
|
|||
test.Equal(t, "start error", err.Error()) |
|||
|
|||
changes := wsf.changes |
|||
|
|||
test.Equal(t, 1, startCalled) |
|||
test.Equal(t, 0, stopCalled) |
|||
test.Equal(t, 1, initCalled) |
|||
|
|||
test.Equal(t, 1, len(changes)) |
|||
test.Equal(t, wsvc.StartPending, changes[0].State) |
|||
|
|||
test.Equal(t, true, wsf.executeReturnedBool) |
|||
test.Equal(t, uint32(1), wsf.executeReturnedUInt32) |
|||
|
|||
test.Equal(t, "start error", wsf.ws.getError().Error()) |
|||
} |
|||
|
|||
func TestRunWindowsServiceInteractive_StartError(t *testing.T) { |
|||
// arrange
|
|||
var startCalled, stopCalled, initCalled int |
|||
prg := makeProgram(&startCalled, &stopCalled, &initCalled) |
|||
prg.start = func() error { |
|||
startCalled++ |
|||
return errors.New("start error") |
|||
} |
|||
|
|||
wsf, _ := setWindowsServiceFuncs(true, nil) |
|||
|
|||
// act
|
|||
err := Run(prg) |
|||
|
|||
// assert
|
|||
test.Equal(t, "start error", err.Error()) |
|||
|
|||
changes := wsf.changes |
|||
|
|||
test.Equal(t, 1, startCalled) |
|||
test.Equal(t, 0, stopCalled) |
|||
test.Equal(t, 1, initCalled) |
|||
|
|||
test.Equal(t, 0, len(changes)) |
|||
} |
|||
|
|||
func TestRunWindowsService_BeforeStartError(t *testing.T) { |
|||
// arrange
|
|||
var startCalled, stopCalled, initCalled int |
|||
prg := makeProgram(&startCalled, &stopCalled, &initCalled) |
|||
prg.init = func(Environment) error { |
|||
initCalled++ |
|||
return errors.New("before start error") |
|||
} |
|||
|
|||
wsf, _ := setWindowsServiceFuncs(false, nil) |
|||
|
|||
// act
|
|||
err := Run(prg) |
|||
|
|||
// assert
|
|||
test.Equal(t, "before start error", err.Error()) |
|||
|
|||
changes := wsf.changes |
|||
|
|||
test.Equal(t, 0, startCalled) |
|||
test.Equal(t, 0, stopCalled) |
|||
test.Equal(t, 1, initCalled) |
|||
|
|||
test.Equal(t, 0, len(changes)) |
|||
} |
|||
|
|||
func TestRunWindowsService_IsAnInteractiveSessionError(t *testing.T) { |
|||
// arrange
|
|||
var startCalled, stopCalled, initCalled int |
|||
prg := makeProgram(&startCalled, &stopCalled, &initCalled) |
|||
|
|||
wsf, _ := setWindowsServiceFuncs(false, nil) |
|||
wsf.svcIsInteractive = func() (bool, error) { |
|||
return false, errors.New("IsAnInteractiveSession error") |
|||
} |
|||
|
|||
// act
|
|||
err := Run(prg) |
|||
|
|||
// assert
|
|||
test.Equal(t, "IsAnInteractiveSession error", err.Error()) |
|||
|
|||
changes := wsf.changes |
|||
|
|||
test.Equal(t, 0, startCalled) |
|||
test.Equal(t, 0, stopCalled) |
|||
test.Equal(t, 0, initCalled) |
|||
|
|||
test.Equal(t, 0, len(changes)) |
|||
} |
|||
|
|||
func TestRunWindowsServiceNonInteractive_RunError(t *testing.T) { |
|||
// arrange
|
|||
var startCalled, stopCalled, initCalled int |
|||
prg := makeProgram(&startCalled, &stopCalled, &initCalled) |
|||
|
|||
svcStop := wsvc.Stop |
|||
wsf, _ := setWindowsServiceFuncs(false, &svcStop) |
|||
wsf.svcRun = func(name string, handler wsvc.Handler) error { |
|||
wsf.ws = handler.(*windowsService) |
|||
return errors.New("wsvc.Run error") |
|||
} |
|||
|
|||
// act
|
|||
err := Run(prg) |
|||
|
|||
// assert
|
|||
test.Equal(t, "wsvc.Run error", err.Error()) |
|||
|
|||
changes := wsf.changes |
|||
|
|||
test.Equal(t, 0, startCalled) |
|||
test.Equal(t, 0, stopCalled) |
|||
test.Equal(t, 1, initCalled) |
|||
|
|||
test.Equal(t, 0, len(changes)) |
|||
|
|||
test.Nil(t, wsf.ws.getError()) |
|||
} |
|||
|
|||
func TestRunWindowsServiceNonInteractive_Interrogate(t *testing.T) { |
|||
// arrange
|
|||
var startCalled, stopCalled, initCalled int |
|||
prg := makeProgram(&startCalled, &stopCalled, &initCalled) |
|||
|
|||
wsf, changeRequest := setWindowsServiceFuncs(false, nil) |
|||
|
|||
time.AfterFunc(50*time.Millisecond, func() { |
|||
// ignored, PausePending won't be in changes slice
|
|||
// make sure we don't panic/err on unexpected values
|
|||
changeRequest <- wsvc.ChangeRequest{ |
|||
Cmd: wsvc.Pause, |
|||
CurrentStatus: wsvc.Status{State: wsvc.PausePending}, |
|||
} |
|||
}) |
|||
|
|||
time.AfterFunc(100*time.Millisecond, func() { |
|||
// handled, Paused will be in changes slice
|
|||
changeRequest <- wsvc.ChangeRequest{ |
|||
Cmd: wsvc.Interrogate, |
|||
CurrentStatus: wsvc.Status{State: wsvc.Paused}, |
|||
} |
|||
}) |
|||
|
|||
time.AfterFunc(200*time.Millisecond, func() { |
|||
// handled, but CurrentStatus overridden with StopPending;
|
|||
// ContinuePending won't be in changes slice
|
|||
changeRequest <- wsvc.ChangeRequest{ |
|||
Cmd: wsvc.Stop, |
|||
CurrentStatus: wsvc.Status{State: wsvc.ContinuePending}, |
|||
} |
|||
}) |
|||
|
|||
// act
|
|||
if err := Run(prg); err != nil { |
|||
t.Fatal(err) |
|||
} |
|||
|
|||
// assert
|
|||
changes := wsf.changes |
|||
|
|||
test.Equal(t, 1, startCalled) |
|||
test.Equal(t, 1, stopCalled) |
|||
test.Equal(t, 1, initCalled) |
|||
|
|||
test.Equal(t, 4, len(changes)) |
|||
test.Equal(t, wsvc.StartPending, changes[0].State) |
|||
test.Equal(t, wsvc.Running, changes[1].State) |
|||
test.Equal(t, wsvc.Paused, changes[2].State) |
|||
test.Equal(t, wsvc.StopPending, changes[3].State) |
|||
|
|||
test.Equal(t, false, wsf.executeReturnedBool) |
|||
test.Equal(t, uint32(0), wsf.executeReturnedUInt32) |
|||
|
|||
test.Nil(t, wsf.ws.getError()) |
|||
} |
|||
|
|||
func TestRunWindowsServiceInteractive_StopError(t *testing.T) { |
|||
// arrange
|
|||
var startCalled, stopCalled, initCalled int |
|||
prg := makeProgram(&startCalled, &stopCalled, &initCalled) |
|||
prg.stop = func() error { |
|||
stopCalled++ |
|||
return errors.New("stop error") |
|||
} |
|||
|
|||
wsf, _ := setWindowsServiceFuncs(true, nil) |
|||
|
|||
go func() { |
|||
wsf.sigChan <- os.Interrupt |
|||
}() |
|||
|
|||
// act
|
|||
err := Run(prg) |
|||
|
|||
// assert
|
|||
test.Equal(t, "stop error", err.Error()) |
|||
test.Equal(t, 1, startCalled) |
|||
test.Equal(t, 1, stopCalled) |
|||
test.Equal(t, 1, initCalled) |
|||
test.Equal(t, 0, len(wsf.changes)) |
|||
} |
|||
|
|||
func TestRunWindowsServiceNonInteractive_StopError(t *testing.T) { |
|||
// arrange
|
|||
var startCalled, stopCalled, initCalled int |
|||
prg := makeProgram(&startCalled, &stopCalled, &initCalled) |
|||
prg.stop = func() error { |
|||
stopCalled++ |
|||
return errors.New("stop error") |
|||
} |
|||
|
|||
shutdownCmd := wsvc.Shutdown |
|||
wsf, _ := setWindowsServiceFuncs(false, &shutdownCmd) |
|||
|
|||
// act
|
|||
err := Run(prg) |
|||
|
|||
// assert
|
|||
changes := wsf.changes |
|||
|
|||
test.Equal(t, "stop error", err.Error()) |
|||
|
|||
test.Equal(t, 1, startCalled) |
|||
test.Equal(t, 1, stopCalled) |
|||
test.Equal(t, 1, initCalled) |
|||
|
|||
test.Equal(t, 3, len(changes)) |
|||
test.Equal(t, wsvc.StartPending, changes[0].State) |
|||
test.Equal(t, wsvc.Running, changes[1].State) |
|||
test.Equal(t, wsvc.StopPending, changes[2].State) |
|||
|
|||
test.Equal(t, true, wsf.executeReturnedBool) |
|||
test.Equal(t, uint32(2), wsf.executeReturnedUInt32) |
|||
|
|||
test.Equal(t, "stop error", wsf.ws.getError().Error()) |
|||
} |
|||
|
|||
func TestDefaultSignalHandling(t *testing.T) { |
|||
signals := []os.Signal{syscall.SIGINT} // default signal handled
|
|||
for _, signal := range signals { |
|||
testSignalNotify(t, signal) |
|||
} |
|||
} |
|||
|
|||
func TestUserDefinedSignalHandling(t *testing.T) { |
|||
signals := []os.Signal{syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP} |
|||
for _, signal := range signals { |
|||
testSignalNotify(t, signal, signals...) |
|||
} |
|||
} |
|||
|
|||
func testSignalNotify(t *testing.T, signal os.Signal, sig ...os.Signal) { |
|||
// arrange
|
|||
var startCalled, stopCalled, initCalled int |
|||
prg := makeProgram(&startCalled, &stopCalled, &initCalled) |
|||
|
|||
wsf, _ := setWindowsServiceFuncs(true, nil) |
|||
|
|||
go func() { |
|||
wsf.sigChan <- signal |
|||
}() |
|||
|
|||
// act
|
|||
if err := Run(prg, sig...); err != nil { |
|||
t.Fatal(err) |
|||
} |
|||
|
|||
// assert
|
|||
test.Equal(t, 1, startCalled) |
|||
test.Equal(t, 1, stopCalled) |
|||
test.Equal(t, 1, initCalled) |
|||
test.Equal(t, 0, len(wsf.changes)) |
|||
} |
@ -0,0 +1,148 @@ |
|||
#!/bin/bash |
|||
|
|||
# go get -u github.com/kisielk/errcheck |
|||
# go get -u github.com/golang/lint/golint |
|||
# go get -u honnef.co/go/simple/cmd/gosimple |
|||
# go get -u honnef.co/go/unused/cmd/unused |
|||
# go get -u github.com/mdempsky/unconvert |
|||
# go get -u github.com/client9/misspell/cmd/misspell |
|||
# go get -u github.com/gordonklaus/ineffassign |
|||
# go get -u github.com/fzipp/gocyclo |
|||
|
|||
FILES=$(ls *.go) |
|||
|
|||
echo "Checking gofmt..." |
|||
fmtRes=$(gofmt -l -s -d $FILES) |
|||
if [ -n "${fmtRes}" ]; then |
|||
echo "gofmt checking failed: ${fmtRes}" |
|||
exit 255 |
|||
fi |
|||
|
|||
echo "Checking errcheck..." |
|||
# buffer.WriteString always returns nil error; panics if buffer too large |
|||
errRes=$(errcheck -blank -ignore 'Write[String|Rune|Byte],os:Close') |
|||
# TODO: add -asserts flag (maybe) |
|||
if [ $? -ne 0 ]; then |
|||
echo "errcheck checking failed: ${errRes}!" |
|||
exit 255 |
|||
fi |
|||
if [ -n "${errRes}" ]; then |
|||
echo "errcheck checking failed: ${errRes}" |
|||
exit 255 |
|||
fi |
|||
|
|||
echo "Checking govet..." |
|||
go vet $FILES |
|||
if [ $? -ne 0 ]; then |
|||
exit 255 |
|||
fi |
|||
|
|||
echo "Checking govet -shadow..." |
|||
for path in $FILES; do |
|||
go tool vet -shadow ${path} |
|||
if [ $? -ne 0 ]; then |
|||
exit 255 |
|||
fi |
|||
done |
|||
|
|||
echo "Checking golint..." |
|||
lintError=0 |
|||
for path in $FILES; do |
|||
lintRes=$(golint ${path}) |
|||
if [ -n "${lintRes}" ]; then |
|||
echo "golint checking ${path} failed: ${lintRes}" |
|||
lintError=1 |
|||
fi |
|||
done |
|||
|
|||
if [ ${lintError} -ne 0 ]; then |
|||
exit 255 |
|||
fi |
|||
|
|||
echo "Checking gosimple..." |
|||
gosimpleRes=$(gosimple .) |
|||
if [ $? -ne 0 ]; then |
|||
echo "gosimple checking failed: ${gosimpleRes}!" |
|||
exit 255 |
|||
fi |
|||
if [ -n "${gosimpleRes}" ]; then |
|||
echo "gosimple checking failed: ${gosimpleRes}" |
|||
exit 255 |
|||
fi |
|||
|
|||
echo "Checking unused..." |
|||
unusedRes=$(unused .) |
|||
if [ $? -ne 0 ]; then |
|||
echo "unused checking failed: ${unusedRes}!" |
|||
exit 255 |
|||
fi |
|||
if [ -n "${unusedRes}" ]; then |
|||
echo "unused checking failed: ${unusedRes}" |
|||
exit 255 |
|||
fi |
|||
|
|||
echo "Checking unconvert..." |
|||
unconvertRes=$(unconvert .) |
|||
if [ $? -ne 0 ]; then |
|||
echo "unconvert checking failed: ${unconvertRes}!" |
|||
exit 255 |
|||
fi |
|||
if [ -n "${unconvertRes}" ]; then |
|||
echo "unconvert checking failed: ${unconvertRes}" |
|||
exit 255 |
|||
fi |
|||
|
|||
echo "Checking misspell..." |
|||
misspellRes=$(misspell $FILES) |
|||
if [ $? -ne 0 ]; then |
|||
echo "misspell checking failed: ${misspellRes}!" |
|||
exit 255 |
|||
fi |
|||
if [ -n "${misspellRes}" ]; then |
|||
echo "misspell checking failed: ${misspellRes}" |
|||
exit 255 |
|||
fi |
|||
|
|||
echo "Checking ineffassign..." |
|||
ineffassignRes=$(ineffassign -n .) |
|||
if [ $? -ne 0 ]; then |
|||
echo "ineffassign checking failed: ${ineffassignRes}!" |
|||
exit 255 |
|||
fi |
|||
if [ -n "${ineffassignRes}" ]; then |
|||
echo "ineffassign checking failed: ${ineffassignRes}" |
|||
exit 255 |
|||
fi |
|||
|
|||
echo "Checking gocyclo..." |
|||
gocycloRes=$(gocyclo -over 20 $FILES) |
|||
if [ -n "${gocycloRes}" ]; then |
|||
echo "gocyclo warning: ${gocycloRes}" |
|||
fi |
|||
|
|||
echo "Running tests..." |
|||
if [ -f cover.out ]; then |
|||
rm cover.out |
|||
fi |
|||
|
|||
go test -timeout 3m --race -cpu 1 |
|||
if [ $? -ne 0 ]; then |
|||
exit 255 |
|||
fi |
|||
|
|||
go test -timeout 3m --race -cpu 2 |
|||
if [ $? -ne 0 ]; then |
|||
exit 255 |
|||
fi |
|||
|
|||
go test -timeout 3m --race -cpu 4 |
|||
if [ $? -ne 0 ]; then |
|||
exit 255 |
|||
fi |
|||
|
|||
go test -timeout 3m -coverprofile cover.out |
|||
if [ $? -ne 0 ]; then |
|||
exit 255 |
|||
fi |
|||
|
|||
echo "Success" |
@ -0,0 +1,9 @@ |
|||
if [ ! -f cover.out ]; then |
|||
echo "Running tests..." |
|||
go test -timeout 3m -coverprofile cover.out |
|||
if [ $? -ne 0 ]; then |
|||
exit 255 |
|||
fi |
|||
fi |
|||
|
|||
go tool cover -html=cover.out |
@ -0,0 +1,4 @@ |
|||
MODULE VERSION |
|||
github.com/productionwentdown/forward - |
|||
github.com/judwhite/go-svc v1.0.0 |
|||
golang.org/x/sys v0.0.0-20180322165403-91ee8cde4354 |
Write
Preview
Loading…
Cancel
Save
Reference in new issue