mirror of
https://github.com/rclone/rclone.git
synced 2025-12-12 06:24:14 +01:00
cmd/gitannex: Add explicit timeout for mock stdout reads in tests
It seems like (*testState).readLine() hangs indefinitely when it's waiting for a line that will never be written [1]. This commit adds an explicit 30-second timeout when reading from the internal mock stdout. Given that we integrate with fstest, this timeout needs to be sufficiently long that it accommodates slow-but-successful operations on real remotes. [1]: https://github.com/rclone/rclone/pull/8423#issuecomment-2701601290
This commit is contained in:
committed by
Nick Craig-Wood
parent
44e83d77d7
commit
53624222c9
@@ -252,6 +252,9 @@ type testState struct {
|
|||||||
server *server
|
server *server
|
||||||
mockStdinW *io.PipeWriter
|
mockStdinW *io.PipeWriter
|
||||||
mockStdoutReader *bufio.Reader
|
mockStdoutReader *bufio.Reader
|
||||||
|
// readLineTimeout is the maximum duration of time to wait for [server] to
|
||||||
|
// write a line to be written to the mock stdout.
|
||||||
|
readLineTimeout time.Duration
|
||||||
|
|
||||||
fstestRun *fstest.Run
|
fstestRun *fstest.Run
|
||||||
remoteName string
|
remoteName string
|
||||||
@@ -270,6 +273,11 @@ func makeTestState(t *testing.T) testState {
|
|||||||
},
|
},
|
||||||
mockStdinW: stdinW,
|
mockStdinW: stdinW,
|
||||||
mockStdoutReader: bufio.NewReader(stdoutR),
|
mockStdoutReader: bufio.NewReader(stdoutR),
|
||||||
|
|
||||||
|
// The default readLineTimeout must be large enough to accommodate slow
|
||||||
|
// operations on real remotes. Without a timeout, attempts to read a
|
||||||
|
// line that's never written would block indefinitely.
|
||||||
|
readLineTimeout: time.Second * 30,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,18 +285,52 @@ func (h *testState) requireRemoteIsEmpty() {
|
|||||||
h.fstestRun.CheckRemoteItems(h.t)
|
h.fstestRun.CheckRemoteItems(h.t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *testState) requireReadLineExact(line string) {
|
// readLineWithTimeout attempts to read a line from the mock stdout. Returns an
|
||||||
receivedLine, err := h.mockStdoutReader.ReadString('\n')
|
// error if the read operation times out or fails for any reason.
|
||||||
require.NoError(h.t, err)
|
func (h *testState) readLineWithTimeout() (string, error) {
|
||||||
require.Equal(h.t, line+"\n", receivedLine)
|
ctx, cancel := context.WithTimeout(context.Background(), h.readLineTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
lineChan := make(chan string)
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
line, err := h.mockStdoutReader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
} else {
|
||||||
|
lineChan <- line
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case line := <-lineChan:
|
||||||
|
return line, nil
|
||||||
|
case err := <-errChan:
|
||||||
|
return "", err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return "", fmt.Errorf("attempt to read line timed out: %w", ctx.Err())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// requireReadLineExact requires that a line matching wantLine can be read from
|
||||||
|
// the mock stdout.
|
||||||
|
func (h *testState) requireReadLineExact(wantLine string) {
|
||||||
|
receivedLine, err := h.readLineWithTimeout()
|
||||||
|
require.NoError(h.t, err)
|
||||||
|
require.Equal(h.t, wantLine+"\n", receivedLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// requireReadLine requires that a line can be read from the mock stdout and
|
||||||
|
// returns the line.
|
||||||
func (h *testState) requireReadLine() string {
|
func (h *testState) requireReadLine() string {
|
||||||
receivedLine, err := h.mockStdoutReader.ReadString('\n')
|
receivedLine, err := h.readLineWithTimeout()
|
||||||
require.NoError(h.t, err)
|
require.NoError(h.t, err)
|
||||||
return receivedLine
|
return receivedLine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// requireWriteLine requires that the given line is successfully written to the
|
||||||
|
// mock stdin.
|
||||||
func (h *testState) requireWriteLine(line string) {
|
func (h *testState) requireWriteLine(line string) {
|
||||||
_, err := h.mockStdinW.Write([]byte(line + "\n"))
|
_, err := h.mockStdinW.Write([]byte(line + "\n"))
|
||||||
require.NoError(h.t, err)
|
require.NoError(h.t, err)
|
||||||
@@ -1281,6 +1323,46 @@ var fstestTestCases = []testCase{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestReadLineHasShortDeadline verifies that [testState.readLineWithTimeout]
|
||||||
|
// does not block indefinitely when a line is never written.
|
||||||
|
func TestReadLineHasShortDeadline(t *testing.T) {
|
||||||
|
const timeoutForRead = time.Millisecond * 50
|
||||||
|
const timeoutForTest = time.Millisecond * 100
|
||||||
|
const tickDuration = time.Millisecond * 10
|
||||||
|
|
||||||
|
type readLineResult struct {
|
||||||
|
line string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
resultChan := make(chan readLineResult)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(resultChan)
|
||||||
|
|
||||||
|
h := makeTestState(t)
|
||||||
|
h.readLineTimeout = timeoutForRead
|
||||||
|
|
||||||
|
line, err := h.readLineWithTimeout()
|
||||||
|
resultChan <- readLineResult{line, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// This closure will be run periodically until time runs out or until all of
|
||||||
|
// its assertions pass.
|
||||||
|
idempotentConditionFunc := func(c *assert.CollectT) {
|
||||||
|
result, ok := <-resultChan
|
||||||
|
require.True(c, ok, "The goroutine should send a result")
|
||||||
|
|
||||||
|
require.Empty(c, result.line, "No line should be read")
|
||||||
|
require.ErrorIs(c, result.err, context.DeadlineExceeded)
|
||||||
|
|
||||||
|
_, ok = <-resultChan
|
||||||
|
require.False(c, ok, "The channel should be closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
require.EventuallyWithT(t, idempotentConditionFunc, timeoutForTest, tickDuration)
|
||||||
|
}
|
||||||
|
|
||||||
// TestMain drives the tests
|
// TestMain drives the tests
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
fstest.TestMain(m)
|
fstest.TestMain(m)
|
||||||
|
|||||||
Reference in New Issue
Block a user