diff --git a/exitcode_plan9.go b/exitcode_plan9.go new file mode 100644 index 0000000..d2a2308 --- /dev/null +++ b/exitcode_plan9.go @@ -0,0 +1,16 @@ +package main + +import ( + "os" + "syscall" +) + +// ExitCode returns the exit code of the exited process, or -1 +// if the process hasn't exited or was terminated by a signal. +func ExitCode(p *os.ProcessState) int { + // return -1 if the process hasn't started. + if p == nil { + return -1 + } + return p.Sys().(syscall.WaitStatus).ExitStatus() +} diff --git a/exitcode_posix.go b/exitcode_posix.go new file mode 100644 index 0000000..fda6446 --- /dev/null +++ b/exitcode_posix.go @@ -0,0 +1,18 @@ +// +build darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris windows + +package main + +import ( + "os" + "syscall" +) + +// ExitCode returns the exit code of the exited process, or -1 +// if the process hasn't exited or was terminated by a signal. +func ExitCode(p *os.ProcessState) int { + // return -1 if the process hasn't started. + if p == nil { + return -1 + } + return p.Sys().(syscall.WaitStatus).ExitStatus() +} diff --git a/main.go b/main.go index b798030..f823830 100644 --- a/main.go +++ b/main.go @@ -54,6 +54,9 @@ func main() { func main1() int { if err := mainerr(); err != nil { + if ee, ok := err.(*exec.ExitError); ok { + return ExitCode(ee.ProcessState) + } fmt.Fprintln(os.Stderr, err) return 1 } diff --git a/script_test.go b/script_test.go index 898f8ac..37b7331 100644 --- a/script_test.go +++ b/script_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "io/ioutil" "os" "os/exec" "runtime" @@ -39,6 +40,41 @@ func (m gobinMain) Run() int { return m.m.Run() } +func TestExitCode(t *testing.T) { + var err error + self, err := os.Executable() + if err != nil { + t.Fatalf("failed to determine os.Executable: %v", err) + } + + temp, err := ioutil.TempDir("", "gobin-exitcode-test") + if err != nil { + t.Fatalf("failed to create temp directory for home: %v", err) + } + defer os.RemoveAll(temp) + + cmd := exec.Command(self, "-run", "example.com/fail") + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, homeEnv(temp)...) + cmd.Env = append(cmd.Env, + "GOPROXY="+proxyURL, + "TESTSCRIPT_COMMAND=gobin", + ) + + err = cmd.Run() + if err == nil { + t.Fatalf("unexpected success") + } + ee, ok := err.(*exec.ExitError) + if !ok { + t.Fatalf("expected *exec.ExitError; got %T: %v", err, err) + } + want := 42 + if got := ExitCode(ee.ProcessState); want != got { + t.Fatalf("expected exit code %v; got %v", want, got) + } +} + func TestScripts(t *testing.T) { var ( pathToMod string // local path to this module @@ -93,17 +129,8 @@ func TestScripts(t *testing.T) { "USERCACHEDIR="+ucd, ) - if runtime.GOOS == "windows" { - e.Vars = append(e.Vars, - "USERPROFILE="+wd+"\\home", - "LOCALAPPDATA="+wd+"\\appdata", - "HOME="+wd+"\\home", // match USERPROFILE - ) - } else { - e.Vars = append(e.Vars, - "HOME="+wd+"/home", - ) - } + e.Vars = append(e.Vars, homeEnv(wd)...) + return nil }, } @@ -112,3 +139,15 @@ func TestScripts(t *testing.T) { } testscript.Run(t, p) } + +func homeEnv(base string) []string { + if runtime.GOOS == "windows" { + return []string{ + "USERPROFILE=" + base + "\\home", + "LOCALAPPDATA=" + base + "\\appdata", + "HOME=" + base + "\\home", // match USERPROFILE + } + } else { + return []string{"HOME=" + base + "/home"} + } +} diff --git a/testdata/failing_run.txt b/testdata/failing_run.txt index 6ed5118..84f3eb2 100644 --- a/testdata/failing_run.txt +++ b/testdata/failing_run.txt @@ -1,5 +1,5 @@ ! gobin -run example.com/fail stdout '^This will fail$' -stderr '^exit status 42$' +stderr '^It''s bad$' diff --git a/testdata/mod/example.com_fail_v1.0.0.txt b/testdata/mod/example.com_fail_v1.0.0.txt index fda3873..ec4e396 100644 --- a/testdata/mod/example.com_fail_v1.0.0.txt +++ b/testdata/mod/example.com_fail_v1.0.0.txt @@ -14,6 +14,7 @@ import "fmt" import "os" func main() { - fmt.Println("This will fail") - os.Exit(42) + fmt.Println("This will fail") + fmt.Fprintln(os.Stderr, "It's bad") + os.Exit(42) }