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