mirror of https://github.com/docker/cli.git
255 lines
6.2 KiB
Go
255 lines
6.2 KiB
Go
package command_test
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/docker/cli/cli/command"
|
|
"github.com/docker/cli/internal/test"
|
|
"github.com/pkg/errors"
|
|
"gotest.tools/v3/assert"
|
|
)
|
|
|
|
func TestStringSliceReplaceAt(t *testing.T) {
|
|
out, ok := command.StringSliceReplaceAt([]string{"abc", "foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, -1)
|
|
assert.Assert(t, ok)
|
|
assert.DeepEqual(t, []string{"abc", "baz", "bax"}, out)
|
|
|
|
out, ok = command.StringSliceReplaceAt([]string{"foo"}, []string{"foo", "bar"}, []string{"baz"}, -1)
|
|
assert.Assert(t, !ok)
|
|
assert.DeepEqual(t, []string{"foo"}, out)
|
|
|
|
out, ok = command.StringSliceReplaceAt([]string{"abc", "foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, 0)
|
|
assert.Assert(t, !ok)
|
|
assert.DeepEqual(t, []string{"abc", "foo", "bar", "bax"}, out)
|
|
|
|
out, ok = command.StringSliceReplaceAt([]string{"foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, 0)
|
|
assert.Assert(t, ok)
|
|
assert.DeepEqual(t, []string{"baz", "bax"}, out)
|
|
|
|
out, ok = command.StringSliceReplaceAt([]string{"abc", "foo", "bar", "baz"}, []string{"foo", "bar"}, nil, -1)
|
|
assert.Assert(t, ok)
|
|
assert.DeepEqual(t, []string{"abc", "baz"}, out)
|
|
|
|
out, ok = command.StringSliceReplaceAt([]string{"foo"}, nil, []string{"baz"}, -1)
|
|
assert.Assert(t, !ok)
|
|
assert.DeepEqual(t, []string{"foo"}, out)
|
|
}
|
|
|
|
func TestValidateOutputPath(t *testing.T) {
|
|
basedir := t.TempDir()
|
|
dir := filepath.Join(basedir, "dir")
|
|
notexist := filepath.Join(basedir, "notexist")
|
|
err := os.MkdirAll(dir, 0o755)
|
|
assert.NilError(t, err)
|
|
file := filepath.Join(dir, "file")
|
|
err = os.WriteFile(file, []byte("hi"), 0o644)
|
|
assert.NilError(t, err)
|
|
testcases := []struct {
|
|
path string
|
|
err error
|
|
}{
|
|
{basedir, nil},
|
|
{file, nil},
|
|
{dir, nil},
|
|
{dir + string(os.PathSeparator), nil},
|
|
{notexist, nil},
|
|
{notexist + string(os.PathSeparator), nil},
|
|
{filepath.Join(notexist, "file"), errors.New("does not exist")},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.path, func(t *testing.T) {
|
|
err := command.ValidateOutputPath(testcase.path)
|
|
if testcase.err == nil {
|
|
assert.NilError(t, err)
|
|
} else {
|
|
assert.ErrorContains(t, err, testcase.err.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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(*testing.T, context.Context, chan promptResult)
|
|
}{
|
|
{"SIGINT", func(t *testing.T, ctx context.Context, c chan promptResult) {
|
|
t.Helper()
|
|
|
|
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
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")
|
|
assert.NilError(t, err)
|
|
|
|
select {
|
|
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")
|
|
assert.NilError(t, err)
|
|
|
|
select {
|
|
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")
|
|
assert.NilError(t, err)
|
|
|
|
select {
|
|
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")
|
|
assert.NilError(t, err)
|
|
|
|
select {
|
|
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)
|
|
}
|
|
}},
|
|
{"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) {
|
|
buf.Reset()
|
|
promptReader, promptWriter = io.Pipe()
|
|
|
|
wroteHook := make(chan struct{}, 1)
|
|
defer close(wroteHook)
|
|
promptOut := test.NewWriterWithHook(bufioWriter, func(p []byte) {
|
|
wroteHook <- struct{}{}
|
|
})
|
|
|
|
result := make(chan promptResult, 1)
|
|
defer close(result)
|
|
go func() {
|
|
r, err := command.PromptForConfirmation(ctx, promptReader, promptOut, "")
|
|
result <- promptResult{r, err}
|
|
}()
|
|
|
|
// wait for the Prompt to write to the buffer
|
|
pollForPromptOutput(ctx, t, wroteHook)
|
|
drainChannel(ctx, wroteHook)
|
|
|
|
assert.NilError(t, bufioWriter.Flush())
|
|
assert.Equal(t, strings.TrimSpace(buf.String()), "Are you sure you want to proceed? [y/N]")
|
|
|
|
resultCtx, resultCancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
|
defer resultCancel()
|
|
|
|
tc.f(t, resultCtx, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func drainChannel(ctx context.Context, ch <-chan struct{}) {
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ch:
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|