Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit f5a9c7e

Browse files
authored
transport/file: fix race condition on test (#267)
Sometimes, the `TestClone` and `TestPush` tests of `transport/file` fail in travis. This is due to a race condition caused by an incorrect usage of `Cmd.Wait()` while reading from the output and error pipes of the command. This patch fixes the problem by using `Cmd.CombinedOutput()` instead of calling `Cmd.Start()` and `Cmd.Wait()` while reading from the output and error pipes. Details: From the `exec` package documentation: ``` Wait will close the pipe after seeing the command exit, so most callers need not close the pipe themselves; however, an implication is that it is incorrect to call Wait before all reads from the pipe have completed. For the same reason, it is incorrect to call Run when using StdoutPipe. ``` In our tests, the old `execAndGetOutput` function was creating two gorutines to read from the stderr and stdout pipes of the command and then call `Wait` on the command. This caused a race condition: when the `Wait` call finished before the gorutines have read from the pipes, they returned caused an error on `outErr`. The problem only happens sometimes on travis. To reproduce the problem locally, just add a call to time.Sleep(time.Second) to the gorutine before its `ioutil.ReadAll` call to delay them, then `Wait` will always finish before them, closing the pipes, and the gorutines will fail. The returned error detected by the test will be: ``` FAIL: server_test.go:55: ServerSuite.TestClone server_test.go:65: c.Assert(err, IsNil, Commentf("STDOUT:\n%s\nSTDERR:\n%s\n", stdout, stderr)) ... value *os.PathError = &os.PathError{Op:"read", Path:"|0", Err:0x9} ("read |0: bad file descriptor") ... STDOUT: STDERR: ```
1 parent 48fcea2 commit f5a9c7e

File tree

1 file changed

+4
-45
lines changed

1 file changed

+4
-45
lines changed

plumbing/transport/file/server_test.go

Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package file
22

33
import (
44
"fmt"
5-
"io"
6-
"io/ioutil"
75
"os"
86
"os/exec"
97

@@ -48,8 +46,8 @@ func (s *ServerSuite) TestPush(c *C) {
4846
cmd.Dir = s.SrcPath
4947
cmd.Env = os.Environ()
5048
cmd.Env = append(cmd.Env, "GIT_TRACE=true", "GIT_TRACE_PACKET=true")
51-
stdout, stderr, err := execAndGetOutput(c, cmd)
52-
c.Assert(err, IsNil, Commentf("STDOUT:\n%s\nSTDERR:\n%s\n", stdout, stderr))
49+
out, err := cmd.CombinedOutput()
50+
c.Assert(err, IsNil, Commentf("combined stdout and stderr:\n%s\n", out))
5351
}
5452

5553
func (s *ServerSuite) TestClone(c *C) {
@@ -61,45 +59,6 @@ func (s *ServerSuite) TestClone(c *C) {
6159
)
6260
cmd.Env = os.Environ()
6361
cmd.Env = append(cmd.Env, "GIT_TRACE=true", "GIT_TRACE_PACKET=true")
64-
stdout, stderr, err := execAndGetOutput(c, cmd)
65-
c.Assert(err, IsNil, Commentf("STDOUT:\n%s\nSTDERR:\n%s\n", stdout, stderr))
66-
}
67-
68-
func execAndGetOutput(c *C, cmd *exec.Cmd) (stdout, stderr string, err error) {
69-
sout, err := cmd.StdoutPipe()
70-
c.Assert(err, IsNil)
71-
serr, err := cmd.StderrPipe()
72-
c.Assert(err, IsNil)
73-
74-
outChan, outErr := readAllAsync(sout)
75-
errChan, errErr := readAllAsync(serr)
76-
77-
c.Assert(cmd.Start(), IsNil)
78-
79-
if err = cmd.Wait(); err != nil {
80-
return <-outChan, <-errChan, err
81-
}
82-
83-
if err := <-outErr; err != nil {
84-
return <-outChan, <-errChan, err
85-
}
86-
87-
return <-outChan, <-errChan, <-errErr
88-
}
89-
90-
func readAllAsync(r io.Reader) (out chan string, err chan error) {
91-
out = make(chan string, 1)
92-
err = make(chan error, 1)
93-
go func() {
94-
b, e := ioutil.ReadAll(r)
95-
if e != nil {
96-
err <- e
97-
} else {
98-
err <- nil
99-
}
100-
101-
out <- string(b)
102-
}()
103-
104-
return out, err
62+
out, err := cmd.CombinedOutput()
63+
c.Assert(err, IsNil, Commentf("combined stdout and stderr:\n%s\n", out))
10564
}

0 commit comments

Comments
 (0)