Vendor and drone
parent
f6449feb55
commit
9c40619f41
|
@ -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
|
Loading…
Reference in New Issue