cli/command: move prompt utilities to separate package
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@ -1,23 +1,12 @@
|
||||
package command_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@ -54,171 +43,3 @@ func TestValidateOutputPath(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromptForInput(t *testing.T) {
|
||||
t.Run("cancelling the context", func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
reader, _ := io.Pipe()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
bufioWriter := bufio.NewWriter(buf)
|
||||
|
||||
wroteHook := make(chan struct{}, 1)
|
||||
promptOut := test.NewWriterWithHook(bufioWriter, func(p []byte) {
|
||||
wroteHook <- struct{}{}
|
||||
})
|
||||
|
||||
promptErr := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := command.PromptForInput(ctx, streams.NewIn(reader), streams.NewOut(promptOut), "Enter something")
|
||||
promptErr <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("timeout waiting for prompt to write to buffer")
|
||||
case <-wroteHook:
|
||||
cancel()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("timeout waiting for prompt to be canceled")
|
||||
case err := <-promptErr:
|
||||
assert.ErrorIs(t, err, command.ErrPromptTerminated)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("user input should be properly trimmed", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
reader, writer := io.Pipe()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
bufioWriter := bufio.NewWriter(buf)
|
||||
|
||||
wroteHook := make(chan struct{}, 1)
|
||||
promptOut := test.NewWriterWithHook(bufioWriter, func(p []byte) {
|
||||
wroteHook <- struct{}{}
|
||||
})
|
||||
|
||||
go func() {
|
||||
<-wroteHook
|
||||
writer.Write([]byte(" foo \n"))
|
||||
}()
|
||||
|
||||
answer, err := command.PromptForInput(ctx, streams.NewIn(reader), streams.NewOut(promptOut), "Enter something")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, answer, "foo")
|
||||
})
|
||||
}
|
||||
|
||||
func TestPromptForConfirmation(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
type promptResult struct {
|
||||
result bool
|
||||
err error
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
bufioWriter := bufio.NewWriter(buf)
|
||||
|
||||
var (
|
||||
promptWriter *io.PipeWriter
|
||||
promptReader *io.PipeReader
|
||||
)
|
||||
|
||||
defer func() {
|
||||
if promptWriter != nil {
|
||||
promptWriter.Close()
|
||||
}
|
||||
if promptReader != nil {
|
||||
promptReader.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
f func() error
|
||||
expected promptResult
|
||||
}{
|
||||
{"SIGINT", func() error {
|
||||
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
||||
return nil
|
||||
}, promptResult{false, command.ErrPromptTerminated}},
|
||||
{"no", func() error {
|
||||
_, err := fmt.Fprintln(promptWriter, "n")
|
||||
return err
|
||||
}, promptResult{false, nil}},
|
||||
{"yes", func() error {
|
||||
_, err := fmt.Fprintln(promptWriter, "y")
|
||||
return err
|
||||
}, promptResult{true, nil}},
|
||||
{"any", func() error {
|
||||
_, err := fmt.Fprintln(promptWriter, "a")
|
||||
return err
|
||||
}, promptResult{false, nil}},
|
||||
{"with space", func() error {
|
||||
_, err := fmt.Fprintln(promptWriter, " y")
|
||||
return err
|
||||
}, promptResult{true, nil}},
|
||||
{"reader closed", func() error {
|
||||
return promptReader.Close()
|
||||
}, promptResult{false, nil}},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
notifyCtx, notifyCancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||
t.Cleanup(notifyCancel)
|
||||
|
||||
buf.Reset()
|
||||
promptReader, promptWriter = io.Pipe()
|
||||
|
||||
wroteHook := make(chan struct{}, 1)
|
||||
promptOut := test.NewWriterWithHook(bufioWriter, func(p []byte) {
|
||||
wroteHook <- struct{}{}
|
||||
})
|
||||
|
||||
result := make(chan promptResult, 1)
|
||||
go func() {
|
||||
r, err := command.PromptForConfirmation(notifyCtx, promptReader, promptOut, "")
|
||||
result <- promptResult{r, err}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
case <-wroteHook:
|
||||
}
|
||||
|
||||
assert.NilError(t, bufioWriter.Flush())
|
||||
assert.Equal(t, strings.TrimSpace(buf.String()), "Are you sure you want to proceed? [y/N]")
|
||||
|
||||
// wait for the Prompt to write to the buffer
|
||||
drainChannel(ctx, wroteHook)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func drainChannel(ctx context.Context, ch <-chan struct{}) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ch:
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user