diff options
Diffstat (limited to 'libgo/go/os/exec/exec_test.go')
-rw-r--r-- | libgo/go/os/exec/exec_test.go | 193 |
1 files changed, 183 insertions, 10 deletions
diff --git a/libgo/go/os/exec/exec_test.go b/libgo/go/os/exec/exec_test.go index 1576e8f156..f13635138a 100644 --- a/libgo/go/os/exec/exec_test.go +++ b/libgo/go/os/exec/exec_test.go @@ -10,6 +10,7 @@ package exec_test import ( "bufio" "bytes" + "context" "fmt" "internal/testenv" "io" @@ -28,12 +29,16 @@ import ( "time" ) -func helperCommand(t *testing.T, s ...string) *exec.Cmd { +func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) { testenv.MustHaveExec(t) cs := []string{"-test.run=TestHelperProcess", "--"} cs = append(cs, s...) - cmd := exec.Command(os.Args[0], cs...) + if ctx != nil { + cmd = exec.CommandContext(ctx, os.Args[0], cs...) + } else { + cmd = exec.Command(os.Args[0], cs...) + } cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} path := os.Getenv("LD_LIBRARY_PATH") if path != "" { @@ -42,6 +47,10 @@ func helperCommand(t *testing.T, s ...string) *exec.Cmd { return cmd } +func helperCommand(t *testing.T, s ...string) *exec.Cmd { + return helperCommandContext(t, nil, s...) +} + func TestEcho(t *testing.T) { bs, err := helperCommand(t, "echo", "foo bar", "baz").Output() if err != nil { @@ -96,6 +105,26 @@ func TestCatStdin(t *testing.T) { } } +func TestEchoFileRace(t *testing.T) { + cmd := helperCommand(t, "echo") + stdin, err := cmd.StdinPipe() + if err != nil { + t.Fatalf("StdinPipe: %v", err) + } + if err := cmd.Start(); err != nil { + t.Fatalf("Start: %v", err) + } + wrote := make(chan bool) + go func() { + defer close(wrote) + fmt.Fprint(stdin, "echo\n") + }() + if err := cmd.Wait(); err != nil { + t.Fatalf("Wait: %v", err) + } + <-wrote +} + func TestCatGoodAndBadFile(t *testing.T) { // Testing combined output and error values. bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput() @@ -221,6 +250,46 @@ func TestStdinClose(t *testing.T) { check("Wait", cmd.Wait()) } +// Issue 17647. +// It used to be the case that TestStdinClose, above, would fail when +// run under the race detector. This test is a variant of TestStdinClose +// that also used to fail when run under the race detector. +// This test is run by cmd/dist under the race detector to verify that +// the race detector no longer reports any problems. +func TestStdinCloseRace(t *testing.T) { + cmd := helperCommand(t, "stdinClose") + stdin, err := cmd.StdinPipe() + if err != nil { + t.Fatalf("StdinPipe: %v", err) + } + if err := cmd.Start(); err != nil { + t.Fatalf("Start: %v", err) + } + go func() { + // We don't check the error return of Kill. It is + // possible that the process has already exited, in + // which case Kill will return an error "process + // already finished". The purpose of this test is to + // see whether the race detector reports an error; it + // doesn't matter whether this Kill succeeds or not. + cmd.Process.Kill() + }() + go func() { + // Send the wrong string, so that the child fails even + // if the other goroutine doesn't manage to kill it first. + // This test is to check that the race detector does not + // falsely report an error, so it doesn't matter how the + // child process fails. + io.Copy(stdin, strings.NewReader("unexpected string")) + if err := stdin.Close(); err != nil { + t.Errorf("stdin.Close: %v", err) + } + }() + if err := cmd.Wait(); err == nil { + t.Fatalf("Wait: succeeded unexpectedly") + } +} + // Issue 5071 func TestPipeLookPathLeak(t *testing.T) { fd0, lsof0 := numOpenFDS(t) @@ -345,7 +414,7 @@ func TestExtraFilesFDShuffle(t *testing.T) { // // We want to test that FDs in the child do not get overwritten // by one another as this shuffle occurs. The original implementation - // was buggy in that in some data dependent cases it would ovewrite + // was buggy in that in some data dependent cases it would overwrite // stderr in the child with one of the ExtraFile members. // Testing for this case is difficult because it relies on using // the same FD values as that case. In particular, an FD of 3 @@ -407,7 +476,7 @@ func TestExtraFilesFDShuffle(t *testing.T) { buf := make([]byte, 512) n, err := stderr.Read(buf) if err != nil { - t.Fatalf("Read: %s", err) + t.Errorf("Read: %s", err) ch <- err.Error() } else { ch <- string(buf[:n]) @@ -483,7 +552,7 @@ func TestExtraFiles(t *testing.T) { if err != nil { t.Fatalf("Write: %v", err) } - _, err = tf.Seek(0, os.SEEK_SET) + _, err = tf.Seek(0, io.SeekStart) if err != nil { t.Fatalf("Seek: %v", err) } @@ -663,10 +732,6 @@ func TestHelperProcess(*testing.T) { // the cloned file descriptors that result from opening // /dev/urandom. // https://golang.org/issue/3955 - case "plan9": - // TODO(0intro): Determine why Plan 9 is leaking - // file descriptors. - // https://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, @@ -701,7 +766,7 @@ func TestHelperProcess(*testing.T) { } // Referring to fd3 here ensures that it is not // garbage collected, and therefore closed, while - // executing the wantfd loop above. It doesn't matter + // executing the wantfd loop above. It doesn't matter // what we do with fd3 as long as we refer to it; // closing it is the easy choice. fd3.Close() @@ -839,3 +904,111 @@ func TestOutputStderrCapture(t *testing.T) { t.Errorf("ExitError.Stderr = %q; want %q", got, want) } } + +func TestContext(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + c := helperCommandContext(t, ctx, "pipetest") + stdin, err := c.StdinPipe() + if err != nil { + t.Fatal(err) + } + stdout, err := c.StdoutPipe() + if err != nil { + t.Fatal(err) + } + if err := c.Start(); err != nil { + t.Fatal(err) + } + + if _, err := stdin.Write([]byte("O:hi\n")); err != nil { + t.Fatal(err) + } + buf := make([]byte, 5) + n, err := io.ReadFull(stdout, buf) + if n != len(buf) || err != nil || string(buf) != "O:hi\n" { + t.Fatalf("ReadFull = %d, %v, %q", n, err, buf[:n]) + } + waitErr := make(chan error, 1) + go func() { + waitErr <- c.Wait() + }() + cancel() + select { + case err := <-waitErr: + if err == nil { + t.Fatal("expected Wait failure") + } + case <-time.After(3 * time.Second): + t.Fatal("timeout waiting for child process death") + } +} + +func TestContextCancel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + c := helperCommandContext(t, ctx, "cat") + + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + c.Stdin = r + + stdout, err := c.StdoutPipe() + if err != nil { + t.Fatal(err) + } + readDone := make(chan struct{}) + go func() { + defer close(readDone) + var a [1024]byte + for { + n, err := stdout.Read(a[:]) + if err != nil { + if err != io.EOF { + t.Errorf("unexpected read error: %v", err) + } + return + } + t.Logf("%s", a[:n]) + } + }() + + if err := c.Start(); err != nil { + t.Fatal(err) + } + + if err := r.Close(); err != nil { + t.Fatal(err) + } + + if _, err := io.WriteString(w, "echo"); err != nil { + t.Fatal(err) + } + + cancel() + + // Calling cancel should have killed the process, so writes + // should now fail. Give the process a little while to die. + start := time.Now() + for { + if _, err := io.WriteString(w, "echo"); err != nil { + break + } + if time.Since(start) > time.Second { + t.Fatal("canceling context did not stop program") + } + time.Sleep(time.Millisecond) + } + + if err := w.Close(); err != nil { + t.Errorf("error closing write end of pipe: %v", err) + } + <-readDone + + if err := c.Wait(); err == nil { + t.Error("program unexpectedly exited successfully") + } else { + t.Logf("exit status: %v", err) + } +} |