diff options
Diffstat (limited to 'src/pkg/os/exec/exec_test.go')
-rw-r--r-- | src/pkg/os/exec/exec_test.go | 151 |
1 files changed, 118 insertions, 33 deletions
diff --git a/src/pkg/os/exec/exec_test.go b/src/pkg/os/exec/exec_test.go index c380d6506..6f77ac38a 100644 --- a/src/pkg/os/exec/exec_test.go +++ b/src/pkg/os/exec/exec_test.go @@ -2,7 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package exec +// Use an external test to avoid os/exec -> net/http -> crypto/x509 -> os/exec +// circular dependency on non-cgo darwin. + +package exec_test import ( "bufio" @@ -10,10 +13,12 @@ import ( "fmt" "io" "io/ioutil" + "log" "net" "net/http" "net/http/httptest" "os" + "os/exec" "path/filepath" "runtime" "strconv" @@ -22,16 +27,19 @@ import ( "time" ) -func helperCommand(s ...string) *Cmd { +func helperCommand(t *testing.T, s ...string) *exec.Cmd { + if runtime.GOOS == "nacl" { + t.Skip("skipping on nacl") + } cs := []string{"-test.run=TestHelperProcess", "--"} cs = append(cs, s...) - cmd := Command(os.Args[0], cs...) + cmd := exec.Command(os.Args[0], cs...) cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} return cmd } func TestEcho(t *testing.T) { - bs, err := helperCommand("echo", "foo bar", "baz").Output() + bs, err := helperCommand(t, "echo", "foo bar", "baz").Output() if err != nil { t.Errorf("echo: %v", err) } @@ -40,10 +48,37 @@ func TestEcho(t *testing.T) { } } +func TestCommandRelativeName(t *testing.T) { + // Run our own binary as a relative path + // (e.g. "_test/exec.test") our parent directory. + base := filepath.Base(os.Args[0]) // "exec.test" + dir := filepath.Dir(os.Args[0]) // "/tmp/go-buildNNNN/os/exec/_test" + if dir == "." { + t.Skip("skipping; running test at root somehow") + } + parentDir := filepath.Dir(dir) // "/tmp/go-buildNNNN/os/exec" + dirBase := filepath.Base(dir) // "_test" + if dirBase == "." { + t.Skipf("skipping; unexpected shallow dir of %q", dir) + } + + cmd := exec.Command(filepath.Join(dirBase, base), "-test.run=TestHelperProcess", "--", "echo", "foo") + cmd.Dir = parentDir + cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} + + out, err := cmd.Output() + if err != nil { + t.Errorf("echo: %v", err) + } + if g, e := string(out), "foo\n"; g != e { + t.Errorf("echo: want %q, got %q", e, g) + } +} + func TestCatStdin(t *testing.T) { // Cat, testing stdin and stdout. input := "Input string\nLine 2" - p := helperCommand("cat") + p := helperCommand(t, "cat") p.Stdin = strings.NewReader(input) bs, err := p.Output() if err != nil { @@ -57,9 +92,9 @@ func TestCatStdin(t *testing.T) { func TestCatGoodAndBadFile(t *testing.T) { // Testing combined output and error values. - bs, err := helperCommand("cat", "/bogus/file.foo", "exec_test.go").CombinedOutput() - if _, ok := err.(*ExitError); !ok { - t.Errorf("expected *ExitError from cat combined; got %T: %v", err, err) + bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput() + if _, ok := err.(*exec.ExitError); !ok { + t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err) } s := string(bs) sp := strings.SplitN(s, "\n", 2) @@ -77,7 +112,7 @@ func TestCatGoodAndBadFile(t *testing.T) { func TestNoExistBinary(t *testing.T) { // Can't run a non-existent binary - err := Command("/no-exist-binary").Run() + err := exec.Command("/no-exist-binary").Run() if err == nil { t.Error("expected error from /no-exist-binary") } @@ -85,19 +120,19 @@ func TestNoExistBinary(t *testing.T) { func TestExitStatus(t *testing.T) { // Test that exit values are returned correctly - cmd := helperCommand("exit", "42") + cmd := helperCommand(t, "exit", "42") err := cmd.Run() want := "exit status 42" switch runtime.GOOS { case "plan9": want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid()) } - if werr, ok := err.(*ExitError); ok { + if werr, ok := err.(*exec.ExitError); ok { if s := werr.Error(); s != want { t.Errorf("from exit 42 got exit %q, want %q", s, want) } } else { - t.Fatalf("expected *ExitError from exit 42; got %T: %v", err, err) + t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err) } } @@ -108,7 +143,7 @@ func TestPipes(t *testing.T) { } } // Cat, testing stdin and stdout. - c := helperCommand("pipetest") + c := helperCommand(t, "pipetest") stdin, err := c.StdinPipe() check("StdinPipe", err) stdout, err := c.StdoutPipe() @@ -161,7 +196,7 @@ func TestStdinClose(t *testing.T) { t.Fatalf("%s: %v", what, err) } } - cmd := helperCommand("stdinClose") + cmd := helperCommand(t, "stdinClose") stdin, err := cmd.StdinPipe() check("StdinPipe", err) // Check that we can access methods of the underlying os.File.` @@ -182,9 +217,9 @@ func TestStdinClose(t *testing.T) { // Issue 5071 func TestPipeLookPathLeak(t *testing.T) { - fd0 := numOpenFDS(t) + fd0, lsof0 := numOpenFDS(t) for i := 0; i < 4; i++ { - cmd := Command("something-that-does-not-exist-binary") + cmd := exec.Command("something-that-does-not-exist-binary") cmd.StdoutPipe() cmd.StderrPipe() cmd.StdinPipe() @@ -192,19 +227,30 @@ func TestPipeLookPathLeak(t *testing.T) { t.Fatal("unexpected success") } } - fdGrowth := numOpenFDS(t) - fd0 - if fdGrowth > 2 { - t.Errorf("leaked %d fds; want ~0", fdGrowth) + for triesLeft := 3; triesLeft >= 0; triesLeft-- { + open, lsof := numOpenFDS(t) + fdGrowth := open - fd0 + if fdGrowth > 2 { + if triesLeft > 0 { + // Work around what appears to be a race with Linux's + // proc filesystem (as used by lsof). It seems to only + // be eventually consistent. Give it awhile to settle. + // See golang.org/issue/7808 + time.Sleep(100 * time.Millisecond) + continue + } + t.Errorf("leaked %d fds; want ~0; have:\n%s\noriginally:\n%s", fdGrowth, lsof, lsof0) + } + break } } -func numOpenFDS(t *testing.T) int { - lsof, err := Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output() +func numOpenFDS(t *testing.T) (n int, lsof []byte) { + lsof, err := exec.Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output() if err != nil { t.Skip("skipping test; error finding or running lsof") - return 0 } - return bytes.Count(lsof, []byte("\n")) + return bytes.Count(lsof, []byte("\n")), lsof } var testedAlreadyLeaked = false @@ -270,7 +316,7 @@ func TestExtraFilesFDShuffle(t *testing.T) { // Moving this test case around within the overall tests may // affect the FDs obtained and hence the checks to catch these cases. npipes := 2 - c := helperCommand("extraFilesAndPipes", strconv.Itoa(npipes+1)) + c := helperCommand(t, "extraFilesAndPipes", strconv.Itoa(npipes+1)) rd, wr, _ := os.Pipe() defer rd.Close() if rd.Fd() != 3 { @@ -370,11 +416,15 @@ func TestExtraFiles(t *testing.T) { // Force TLS root certs to be loaded (which might involve // cgo), to make sure none of that potential C code leaks fds. - ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Hello")) - })) + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + // quiet expected TLS handshake error "remote error: bad certificate" + ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) + ts.StartTLS() defer ts.Close() - http.Get(ts.URL) // ignore result; just calling to force root cert loading + _, err = http.Get(ts.URL) + if err == nil { + t.Errorf("success trying to fetch %s; want an error", ts.URL) + } tf, err := ioutil.TempFile("", "") if err != nil { @@ -393,7 +443,7 @@ func TestExtraFiles(t *testing.T) { t.Fatalf("Seek: %v", err) } - c := helperCommand("read3") + c := helperCommand(t, "read3") var stdout, stderr bytes.Buffer c.Stdout = &stdout c.Stderr = &stderr @@ -425,7 +475,7 @@ func TestExtraFilesRace(t *testing.T) { } return f } - runCommand := func(c *Cmd, out chan<- string) { + runCommand := func(c *exec.Cmd, out chan<- string) { bout, err := c.CombinedOutput() if err != nil { out <- "ERROR:" + err.Error() @@ -436,10 +486,10 @@ func TestExtraFilesRace(t *testing.T) { for i := 0; i < 10; i++ { la := listen() - ca := helperCommand("describefiles") + ca := helperCommand(t, "describefiles") ca.ExtraFiles = []*os.File{listenerFile(la)} lb := listen() - cb := helperCommand("describefiles") + cb := helperCommand(t, "describefiles") cb.ExtraFiles = []*os.File{listenerFile(lb)} ares := make(chan string) bres := make(chan string) @@ -476,6 +526,8 @@ func TestHelperProcess(*testing.T) { switch runtime.GOOS { case "dragonfly", "freebsd", "netbsd", "openbsd": ofcmd = "fstat" + case "plan9": + ofcmd = "/bin/cat" } args := os.Args @@ -566,6 +618,14 @@ func TestHelperProcess(*testing.T) { // the cloned file descriptors that result from opening // /dev/urandom. // http://golang.org/issue/3955 + case "plan9": + // TODO(0intro): Determine why Plan 9 is leaking + // file descriptors. + // http://golang.org/issue/7118 + case "solaris": + // TODO(aram): This fails on Solaris because libc opens + // its own files, as it sees fit. Darwin does the same, + // see: http://golang.org/issue/2603 default: // Now verify that there are no other open fds. var files []*os.File @@ -577,7 +637,14 @@ func TestHelperProcess(*testing.T) { } if got := f.Fd(); got != wantfd { fmt.Printf("leaked parent file. fd = %d; want %d\n", got, wantfd) - out, _ := Command(ofcmd, "-p", fmt.Sprint(os.Getpid())).CombinedOutput() + var args []string + switch runtime.GOOS { + case "plan9": + args = []string{fmt.Sprintf("/proc/%d/fd", os.Getpid())} + default: + args = []string{"-p", fmt.Sprint(os.Getpid())} + } + out, _ := exec.Command(ofcmd, args...).CombinedOutput() fmt.Print(string(out)) os.Exit(1) } @@ -634,6 +701,24 @@ func TestHelperProcess(*testing.T) { } fmt.Fprintf(os.Stderr, "child: %s", response) os.Exit(0) + case "exec": + cmd := exec.Command(args[1]) + cmd.Dir = args[0] + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output)) + os.Exit(1) + } + fmt.Printf("%s", string(output)) + os.Exit(0) + case "lookpath": + p, err := exec.LookPath(args[0]) + if err != nil { + fmt.Fprintf(os.Stderr, "LookPath failed: %v\n", err) + os.Exit(1) + } + fmt.Print(p) + os.Exit(0) default: fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd) os.Exit(2) |