diff --git a/Makefile b/Makefile index 68ae4d2..9196b06 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,6 @@ lint: deps golint -set_exit_status ./... cover: deps - go get github.com/axw/gocov/gocov goveralls .PHONY: test deps lint cover diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..8e7c5f8 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,9 @@ +version: "{build}" +clone_folder: c:\gopath\src\github.com\Songmu\timeout +environment: + GOPATH: c:\gopath +build: false +test_script: +- go get -d -v -t ./... +- go tool vet -all . +- go test ./... diff --git a/test/countup.pl b/testdata/countup.pl similarity index 100% rename from test/countup.pl rename to testdata/countup.pl diff --git a/test/dummy b/testdata/dummy similarity index 100% rename from test/dummy rename to testdata/dummy diff --git a/test/exit_with_23.pl b/testdata/exit_with_23.pl similarity index 100% rename from test/exit_with_23.pl rename to testdata/exit_with_23.pl diff --git a/test/ignore_sigterm.pl b/testdata/ignore_sigterm.pl similarity index 100% rename from test/ignore_sigterm.pl rename to testdata/ignore_sigterm.pl diff --git a/timeout.go b/timeout.go index 23c5a3b..c9c0d47 100644 --- a/timeout.go +++ b/timeout.go @@ -8,7 +8,6 @@ import ( "os" "os/exec" "runtime" - "strconv" "syscall" "time" @@ -104,7 +103,7 @@ func (tio *Timeout) signal() os.Signal { // Run is synchronous interface of executing command and returning information func (tio *Timeout) Run() (ExitStatus, string, string, error) { - cmd := tio.Cmd + cmd := tio.getCmd() var outBuffer, errBuffer bytes.Buffer cmd.Stdout = &outBuffer cmd.Stderr = &errBuffer @@ -120,7 +119,7 @@ func (tio *Timeout) Run() (ExitStatus, string, string, error) { // RunSimple executes command and only returns integer as exit code. It is mainly for go-timeout command func (tio *Timeout) RunSimple(preserveStatus bool) int { - cmd := tio.Cmd + cmd := tio.getCmd() stdoutPipe, err := cmd.StdoutPipe() if err != nil { @@ -168,7 +167,7 @@ func getExitCodeFromErr(err error) int { // RunCommand is executing the command and handling timeout. This is primitive interface of Timeout func (tio *Timeout) RunCommand() (chan ExitStatus, error) { - cmd := tio.Cmd + cmd := tio.getCmd() if err := cmd.Start(); err != nil { switch { @@ -199,7 +198,7 @@ func (tio *Timeout) RunCommand() (chan ExitStatus, error) { } func (tio *Timeout) handleTimeout() (ex ExitStatus) { - cmd := tio.Cmd + cmd := tio.getCmd() exitChan := getExitChan(cmd) select { case exitCode := <-exitChan: @@ -215,9 +214,8 @@ func (tio *Timeout) handleTimeout() (ex ExitStatus) { select { case ex.Code = <-exitChan: case <-time.After(tio.KillAfter): - if runtime.GOOS == "windows" { - exec.Command("taskkill", "/F", "/T", "/PID", strconv.Itoa(cmd.Process.Pid)).Run() - } + tio.killall() + // just to make sure cmd.Process.Kill() ex.Code = exitKilled ex.typ = exitTypeKilled diff --git a/timeout_test.go b/timeout_test.go index 7b6ec6e..1958a9b 100644 --- a/timeout_test.go +++ b/timeout_test.go @@ -72,7 +72,7 @@ func TestPreserveStatus(t *testing.T) { t.Skipf("skip test on Windows") } tio := &Timeout{ - Cmd: exec.Command("perl", "test/exit_with_23.pl"), + Cmd: exec.Command("perl", "testdata/exit_with_23.pl"), Duration: 1 * time.Second, } @@ -84,7 +84,7 @@ func TestPreserveStatus(t *testing.T) { func TestKillAfter(t *testing.T) { tio := &Timeout{ - Cmd: exec.Command("perl", "test/ignore_sigterm.pl"), + Cmd: exec.Command("perl", "testdata/ignore_sigterm.pl"), Signal: syscall.SIGTERM, Duration: 1 * time.Second, KillAfter: 1 * time.Second, @@ -98,7 +98,7 @@ func TestKillAfter(t *testing.T) { func TestKillAfterNotKilled(t *testing.T) { tio := &Timeout{ - Cmd: exec.Command("perl", "test/ignore_sigterm.pl"), + Cmd: exec.Command("perl", "testdata/ignore_sigterm.pl"), Signal: syscall.SIGTERM, Duration: 1 * time.Second, KillAfter: 5 * time.Second, @@ -116,7 +116,7 @@ func TestCommandCannotBeInvoked(t *testing.T) { t.Skipf("skip test on Windows") } tio := &Timeout{ - Cmd: exec.Command("test/dummy"), + Cmd: exec.Command("testdata/dummy"), Duration: 1 * time.Second, } exit := tio.RunSimple(false) @@ -132,7 +132,7 @@ func TestCommandNotFound(t *testing.T) { t.Skipf("skip test on Windows") } tio := &Timeout{ - Cmd: exec.Command("test/ignore_sigterm.pl-xxxxxxxxxxxxxxxxxxxxx"), + Cmd: exec.Command("testdata/ignore_sigterm.pl-xxxxxxxxxxxxxxxxxxxxx"), Duration: 1 * time.Second, } exit := tio.RunSimple(false) diff --git a/timeout_unix.go b/timeout_unix.go new file mode 100644 index 0000000..460c3c3 --- /dev/null +++ b/timeout_unix.go @@ -0,0 +1,19 @@ +// +build !windows + +package timeout + +import ( + "os/exec" + "syscall" +) + +func (tio *Timeout) getCmd() *exec.Cmd { + if tio.Cmd.SysProcAttr == nil { + tio.Cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + } + return tio.Cmd +} + +func (tio *Timeout) killall() error { + return syscall.Kill(-tio.Cmd.Process.Pid, syscall.SIGKILL) +} diff --git a/timeout_windows.go b/timeout_windows.go new file mode 100644 index 0000000..12b1a3c --- /dev/null +++ b/timeout_windows.go @@ -0,0 +1,20 @@ +package timeout + +import ( + "os/exec" + "strconv" + "syscall" +) + +func (tio *Timeout) getCmd() *exec.Cmd { + if tio.Cmd.SysProcAttr == nil { + tio.Cmd.SysProcAttr = &syscall.SysProcAttr{ + CreationFlags: syscall.CREATE_UNICODE_ENVIRONMENT | 0x00000200, + } + } + return tio.Cmd +} + +func (tio *Timeout) killall() error { + return exec.Command("taskkill", "/F", "/T", "/PID", strconv.Itoa(tio.Cmd.Process.Pid)).Run() +}