Merge pull request #4957 from Benehiko/prompt-test-flakiness

fix: flaky prompt termination on reader close test
This commit is contained in:
Laura Brehm 2024-03-26 13:03:02 +00:00 committed by GitHub
commit b8d5454963
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 35 additions and 104 deletions

View File

@ -106,120 +106,68 @@ func TestPromptForConfirmation(t *testing.T) {
}() }()
for _, tc := range []struct { for _, tc := range []struct {
desc string desc string
f func(*testing.T, context.Context, chan promptResult) f func() error
expected promptResult
}{ }{
{"SIGINT", func(t *testing.T, ctx context.Context, c chan promptResult) { {"SIGINT", func() error {
t.Helper()
syscall.Kill(syscall.Getpid(), syscall.SIGINT) syscall.Kill(syscall.Getpid(), syscall.SIGINT)
return nil
select { }, promptResult{false, command.ErrPromptTerminated}},
case <-ctx.Done(): {"no", func() error {
t.Fatal("PromptForConfirmation did not return after SIGINT")
case r := <-c:
assert.Check(t, !r.result)
assert.ErrorContains(t, r.err, "prompt terminated")
}
}},
{"no", func(t *testing.T, ctx context.Context, c chan promptResult) {
t.Helper()
_, err := fmt.Fprint(promptWriter, "n\n") _, err := fmt.Fprint(promptWriter, "n\n")
assert.NilError(t, err) return err
}, promptResult{false, nil}},
select { {"yes", func() error {
case <-ctx.Done():
t.Fatal("PromptForConfirmation did not return after user input `n`")
case r := <-c:
assert.Check(t, !r.result)
assert.NilError(t, r.err)
}
}},
{"yes", func(t *testing.T, ctx context.Context, c chan promptResult) {
t.Helper()
_, err := fmt.Fprint(promptWriter, "y\n") _, err := fmt.Fprint(promptWriter, "y\n")
assert.NilError(t, err) return err
}, promptResult{true, nil}},
select { {"any", func() error {
case <-ctx.Done():
t.Fatal("PromptForConfirmation did not return after user input `y`")
case r := <-c:
assert.Check(t, r.result)
assert.NilError(t, r.err)
}
}},
{"any", func(t *testing.T, ctx context.Context, c chan promptResult) {
t.Helper()
_, err := fmt.Fprint(promptWriter, "a\n") _, err := fmt.Fprint(promptWriter, "a\n")
assert.NilError(t, err) return err
}, promptResult{false, nil}},
select { {"with space", func() error {
case <-ctx.Done():
t.Fatal("PromptForConfirmation did not return after user input `a`")
case r := <-c:
assert.Check(t, !r.result)
assert.NilError(t, r.err)
}
}},
{"with space", func(t *testing.T, ctx context.Context, c chan promptResult) {
t.Helper()
_, err := fmt.Fprint(promptWriter, " y\n") _, err := fmt.Fprint(promptWriter, " y\n")
assert.NilError(t, err) return err
}, promptResult{true, nil}},
select { {"reader closed", func() error {
case <-ctx.Done(): return promptReader.Close()
t.Fatal("PromptForConfirmation did not return after user input ` y`") }, promptResult{false, nil}},
case r := <-c:
assert.Check(t, r.result)
assert.NilError(t, r.err)
}
}},
{"reader closed", func(t *testing.T, ctx context.Context, c chan promptResult) {
t.Helper()
assert.NilError(t, promptReader.Close())
select {
case <-ctx.Done():
t.Fatal("PromptForConfirmation did not return after promptReader was closed")
case r := <-c:
assert.Check(t, !r.result)
assert.NilError(t, r.err)
}
}},
} { } {
t.Run("case="+tc.desc, func(t *testing.T) { t.Run("case="+tc.desc, func(t *testing.T) {
buf.Reset() buf.Reset()
promptReader, promptWriter = io.Pipe() promptReader, promptWriter = io.Pipe()
wroteHook := make(chan struct{}, 1) wroteHook := make(chan struct{}, 1)
defer close(wroteHook)
promptOut := test.NewWriterWithHook(bufioWriter, func(p []byte) { promptOut := test.NewWriterWithHook(bufioWriter, func(p []byte) {
wroteHook <- struct{}{} wroteHook <- struct{}{}
}) })
result := make(chan promptResult, 1) result := make(chan promptResult, 1)
defer close(result)
go func() { go func() {
r, err := command.PromptForConfirmation(ctx, promptReader, promptOut, "") r, err := command.PromptForConfirmation(ctx, promptReader, promptOut, "")
result <- promptResult{r, err} result <- promptResult{r, err}
}() }()
// wait for the Prompt to write to the buffer select {
pollForPromptOutput(ctx, t, wroteHook) case <-time.After(100 * time.Millisecond):
drainChannel(ctx, wroteHook) case <-wroteHook:
}
assert.NilError(t, bufioWriter.Flush()) assert.NilError(t, bufioWriter.Flush())
assert.Equal(t, strings.TrimSpace(buf.String()), "Are you sure you want to proceed? [y/N]") assert.Equal(t, strings.TrimSpace(buf.String()), "Are you sure you want to proceed? [y/N]")
resultCtx, resultCancel := context.WithTimeout(ctx, 100*time.Millisecond) // wait for the Prompt to write to the buffer
defer resultCancel() drainChannel(ctx, wroteHook)
tc.f(t, resultCtx, result) assert.NilError(t, tc.f())
select {
case <-time.After(500 * time.Millisecond):
t.Fatal("timeout waiting for prompt result")
case r := <-result:
assert.Equal(t, r, tc.expected)
}
}) })
} }
} }
@ -235,20 +183,3 @@ func drainChannel(ctx context.Context, ch <-chan struct{}) {
} }
}() }()
} }
func pollForPromptOutput(ctx context.Context, t *testing.T, wroteHook <-chan struct{}) {
t.Helper()
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancel()
for {
select {
case <-ctx.Done():
t.Fatal("Prompt output was not written to before ctx was cancelled")
return
case <-wroteHook:
return
}
}
}