Add unit tests to cli/command/volume package

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
Vincent Demeester 2017-02-27 18:39:35 +01:00
parent 798ed3eb6c
commit 407d65df9d
29 changed files with 815 additions and 34 deletions

View File

@ -38,6 +38,7 @@ type Cli interface {
Out() *OutStream Out() *OutStream
Err() io.Writer Err() io.Writer
In() *InStream In() *InStream
ConfigFile() *configfile.ConfigFile
} }
// DockerCli is an instance the docker command line client. // DockerCli is an instance the docker command line client.

View File

@ -0,0 +1,53 @@
package volume
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
volumetypes "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
volumeCreateFunc func(volumetypes.VolumesCreateBody) (types.Volume, error)
volumeInspectFunc func(volumeID string) (types.Volume, error)
volumeListFunc func(filter filters.Args) (volumetypes.VolumesListOKBody, error)
volumeRemoveFunc func(volumeID string, force bool) error
volumePruneFunc func(filter filters.Args) (types.VolumesPruneReport, error)
}
func (c *fakeClient) VolumeCreate(ctx context.Context, options volumetypes.VolumesCreateBody) (types.Volume, error) {
if c.volumeCreateFunc != nil {
return c.volumeCreateFunc(options)
}
return types.Volume{}, nil
}
func (c *fakeClient) VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) {
if c.volumeInspectFunc != nil {
return c.volumeInspectFunc(volumeID)
}
return types.Volume{}, nil
}
func (c *fakeClient) VolumeList(ctx context.Context, filter filters.Args) (volumetypes.VolumesListOKBody, error) {
if c.volumeListFunc != nil {
return c.volumeListFunc(filter)
}
return volumetypes.VolumesListOKBody{}, nil
}
func (c *fakeClient) VolumesPrune(ctx context.Context, filter filters.Args) (types.VolumesPruneReport, error) {
if c.volumePruneFunc != nil {
return c.volumePruneFunc(filter)
}
return types.VolumesPruneReport{}, nil
}
func (c *fakeClient) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
if c.volumeRemoveFunc != nil {
return c.volumeRemoveFunc(volumeID, force)
}
return nil
}

View File

@ -1,10 +1,9 @@
package volume package volume
import ( import (
"github.com/spf13/cobra"
"github.com/docker/docker/cli" "github.com/docker/docker/cli"
"github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command"
"github.com/spf13/cobra"
) )
// NewVolumeCommand returns a cobra command for `volume` subcommands // NewVolumeCommand returns a cobra command for `volume` subcommands

View File

@ -19,7 +19,7 @@ type createOptions struct {
labels opts.ListOpts labels opts.ListOpts
} }
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { func newCreateCommand(dockerCli command.Cli) *cobra.Command {
opts := createOptions{ opts := createOptions{
driverOpts: *opts.NewMapOpts(nil, nil), driverOpts: *opts.NewMapOpts(nil, nil),
labels: opts.NewListOpts(opts.ValidateEnv), labels: opts.NewListOpts(opts.ValidateEnv),
@ -32,8 +32,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 1 { if len(args) == 1 {
if opts.name != "" { if opts.name != "" {
fmt.Fprint(dockerCli.Err(), "Conflicting options: either specify --name or provide positional arg, not both\n") return fmt.Errorf("Conflicting options: either specify --name or provide positional arg, not both\n")
return cli.StatusError{StatusCode: 1}
} }
opts.name = args[0] opts.name = args[0]
} }
@ -50,7 +49,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runCreate(dockerCli *command.DockerCli, opts createOptions) error { func runCreate(dockerCli command.Cli, opts createOptions) error {
client := dockerCli.Client() client := dockerCli.Client()
volReq := volumetypes.VolumesCreateBody{ volReq := volumetypes.VolumesCreateBody{

View File

@ -0,0 +1,142 @@
package volume
import (
"bytes"
"fmt"
"io/ioutil"
"strings"
"testing"
"github.com/docker/docker/api/types"
volumetypes "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/cli/internal/test"
"github.com/docker/docker/pkg/testutil/assert"
)
func TestVolumeCreateErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
volumeCreateFunc func(volumetypes.VolumesCreateBody) (types.Volume, error)
expectedError string
}{
{
args: []string{"volumeName"},
flags: map[string]string{
"name": "volumeName",
},
expectedError: "Conflicting options: either specify --name or provide positional arg, not both",
},
{
args: []string{"too", "many"},
expectedError: "requires at most 1 argument(s)",
},
{
volumeCreateFunc: func(createBody volumetypes.VolumesCreateBody) (types.Volume, error) {
return types.Volume{}, fmt.Errorf("error creating volume")
},
expectedError: "error creating volume",
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newCreateCommand(
test.NewFakeCli(&fakeClient{
volumeCreateFunc: tc.volumeCreateFunc,
}, buf),
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
cmd.SetOutput(ioutil.Discard)
assert.Error(t, cmd.Execute(), tc.expectedError)
}
}
func TestVolumeCreateWithName(t *testing.T) {
name := "foo"
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
if body.Name != name {
return types.Volume{}, fmt.Errorf("expected name %q, got %q", name, body.Name)
}
return types.Volume{
Name: body.Name,
}, nil
},
}, buf)
// Test by flags
cmd := newCreateCommand(cli)
cmd.Flags().Set("name", name)
assert.NilError(t, cmd.Execute())
assert.Equal(t, strings.TrimSpace(buf.String()), name)
// Then by args
buf.Reset()
cmd = newCreateCommand(cli)
cmd.SetArgs([]string{name})
assert.NilError(t, cmd.Execute())
assert.Equal(t, strings.TrimSpace(buf.String()), name)
}
func TestVolumeCreateWithFlags(t *testing.T) {
expectedDriver := "foo"
expectedOpts := map[string]string{
"bar": "1",
"baz": "baz",
}
expectedLabels := map[string]string{
"lbl1": "v1",
"lbl2": "v2",
}
name := "banana"
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
if body.Name != "" {
return types.Volume{}, fmt.Errorf("expected empty name, got %q", body.Name)
}
if body.Driver != expectedDriver {
return types.Volume{}, fmt.Errorf("expected driver %q, got %q", expectedDriver, body.Driver)
}
if !compareMap(body.DriverOpts, expectedOpts) {
return types.Volume{}, fmt.Errorf("expected drivers opts %v, got %v", expectedOpts, body.DriverOpts)
}
if !compareMap(body.Labels, expectedLabels) {
return types.Volume{}, fmt.Errorf("expected labels %v, got %v", expectedLabels, body.Labels)
}
return types.Volume{
Name: name,
}, nil
},
}, buf)
cmd := newCreateCommand(cli)
cmd.Flags().Set("driver", "foo")
cmd.Flags().Set("opt", "bar=1")
cmd.Flags().Set("opt", "baz=baz")
cmd.Flags().Set("label", "lbl1=v1")
cmd.Flags().Set("label", "lbl2=v2")
assert.NilError(t, cmd.Execute())
assert.Equal(t, strings.TrimSpace(buf.String()), name)
}
func compareMap(actual map[string]string, expected map[string]string) bool {
if len(actual) != len(expected) {
return false
}
for key, value := range actual {
if expectedValue, ok := expected[key]; ok {
if expectedValue != value {
return false
}
} else {
return false
}
}
return true
}

View File

@ -1,12 +1,11 @@
package volume package volume
import ( import (
"golang.org/x/net/context"
"github.com/docker/docker/cli" "github.com/docker/docker/cli"
"github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/inspect" "github.com/docker/docker/cli/command/inspect"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/net/context"
) )
type inspectOptions struct { type inspectOptions struct {
@ -14,7 +13,7 @@ type inspectOptions struct {
names []string names []string
} }
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command { func newInspectCommand(dockerCli command.Cli) *cobra.Command {
var opts inspectOptions var opts inspectOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -32,7 +31,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error { func runInspect(dockerCli command.Cli, opts inspectOptions) error {
client := dockerCli.Client() client := dockerCli.Client()
ctx := context.Background() ctx := context.Background()

View File

@ -0,0 +1,150 @@
package volume
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cli/internal/test"
// Import builders to get the builder function as package function
. "github.com/docker/docker/cli/internal/test/builders"
"github.com/docker/docker/pkg/testutil/assert"
"github.com/docker/docker/pkg/testutil/golden"
)
func TestVolumeInspectErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
volumeInspectFunc func(volumeID string) (types.Volume, error)
expectedError string
}{
{
expectedError: "requires at least 1 argument",
},
{
args: []string{"foo"},
volumeInspectFunc: func(volumeID string) (types.Volume, error) {
return types.Volume{}, fmt.Errorf("error while inspecting the volume")
},
expectedError: "error while inspecting the volume",
},
{
args: []string{"foo"},
flags: map[string]string{
"format": "{{invalid format}}",
},
expectedError: "Template parsing error",
},
{
args: []string{"foo", "bar"},
volumeInspectFunc: func(volumeID string) (types.Volume, error) {
if volumeID == "foo" {
return types.Volume{
Name: "foo",
}, nil
}
return types.Volume{}, fmt.Errorf("error while inspecting the volume")
},
expectedError: "error while inspecting the volume",
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCli(&fakeClient{
volumeInspectFunc: tc.volumeInspectFunc,
}, buf),
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
cmd.SetOutput(ioutil.Discard)
assert.Error(t, cmd.Execute(), tc.expectedError)
}
}
func TestVolumeInspectWithoutFormat(t *testing.T) {
testCases := []struct {
name string
args []string
volumeInspectFunc func(volumeID string) (types.Volume, error)
}{
{
name: "single-volume",
args: []string{"foo"},
volumeInspectFunc: func(volumeID string) (types.Volume, error) {
if volumeID != "foo" {
return types.Volume{}, fmt.Errorf("Invalid volumeID, expected %s, got %s", "foo", volumeID)
}
return *Volume(), nil
},
},
{
name: "multiple-volume-with-labels",
args: []string{"foo", "bar"},
volumeInspectFunc: func(volumeID string) (types.Volume, error) {
return *Volume(VolumeName(volumeID), VolumeLabels(map[string]string{
"foo": "bar",
})), nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCli(&fakeClient{
volumeInspectFunc: tc.volumeInspectFunc,
}, buf),
)
cmd.SetArgs(tc.args)
assert.NilError(t, cmd.Execute())
actual := buf.String()
expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-inspect-without-format.%s.golden", tc.name))
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
}
}
func TestVolumeInspectWithFormat(t *testing.T) {
volumeInspectFunc := func(volumeID string) (types.Volume, error) {
return *Volume(VolumeLabels(map[string]string{
"foo": "bar",
})), nil
}
testCases := []struct {
name string
format string
args []string
volumeInspectFunc func(volumeID string) (types.Volume, error)
}{
{
name: "simple-template",
format: "{{.Name}}",
args: []string{"foo"},
volumeInspectFunc: volumeInspectFunc,
},
{
name: "json-template",
format: "{{json .Labels}}",
args: []string{"foo"},
volumeInspectFunc: volumeInspectFunc,
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCli(&fakeClient{
volumeInspectFunc: tc.volumeInspectFunc,
}, buf),
)
cmd.SetArgs(tc.args)
cmd.Flags().Set("format", tc.format)
assert.NilError(t, cmd.Execute())
actual := buf.String()
expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-inspect-with-format.%s.golden", tc.name))
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
}
}

View File

@ -3,14 +3,13 @@ package volume
import ( import (
"sort" "sort"
"golang.org/x/net/context"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/cli" "github.com/docker/docker/cli"
"github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/formatter" "github.com/docker/docker/cli/command/formatter"
"github.com/docker/docker/opts" "github.com/docker/docker/opts"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/net/context"
) )
type byVolumeName []*types.Volume type byVolumeName []*types.Volume
@ -27,7 +26,7 @@ type listOptions struct {
filter opts.FilterOpt filter opts.FilterOpt
} }
func newListCommand(dockerCli *command.DockerCli) *cobra.Command { func newListCommand(dockerCli command.Cli) *cobra.Command {
opts := listOptions{filter: opts.NewFilterOpt()} opts := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -48,7 +47,7 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runList(dockerCli *command.DockerCli, opts listOptions) error { func runList(dockerCli command.Cli, opts listOptions) error {
client := dockerCli.Client() client := dockerCli.Client()
volumes, err := client.VolumeList(context.Background(), opts.filter.Value()) volumes, err := client.VolumeList(context.Background(), opts.filter.Value())
if err != nil { if err != nil {

124
command/volume/list_test.go Normal file
View File

@ -0,0 +1,124 @@
package volume
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
volumetypes "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/cli/config/configfile"
"github.com/docker/docker/cli/internal/test"
// Import builders to get the builder function as package function
. "github.com/docker/docker/cli/internal/test/builders"
"github.com/docker/docker/pkg/testutil/assert"
"github.com/docker/docker/pkg/testutil/golden"
)
func TestVolumeListErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
volumeListFunc func(filter filters.Args) (volumetypes.VolumesListOKBody, error)
expectedError string
}{
{
args: []string{"foo"},
expectedError: "accepts no argument",
},
{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
return volumetypes.VolumesListOKBody{}, fmt.Errorf("error listing volumes")
},
expectedError: "error listing volumes",
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newListCommand(
test.NewFakeCli(&fakeClient{
volumeListFunc: tc.volumeListFunc,
}, buf),
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
cmd.SetOutput(ioutil.Discard)
assert.Error(t, cmd.Execute(), tc.expectedError)
}
}
func TestVolumeListWithoutFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
return volumetypes.VolumesListOKBody{
Volumes: []*types.Volume{
Volume(),
Volume(VolumeName("foo"), VolumeDriver("bar")),
Volume(VolumeName("baz"), VolumeLabels(map[string]string{
"foo": "bar",
})),
},
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
assert.NilError(t, cmd.Execute())
actual := buf.String()
expected := golden.Get(t, []byte(actual), "volume-list-without-format.golden")
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
}
func TestVolumeListWithConfigFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
return volumetypes.VolumesListOKBody{
Volumes: []*types.Volume{
Volume(),
Volume(VolumeName("foo"), VolumeDriver("bar")),
Volume(VolumeName("baz"), VolumeLabels(map[string]string{
"foo": "bar",
})),
},
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{
VolumesFormat: "{{ .Name }} {{ .Driver }} {{ .Labels }}",
})
cmd := newListCommand(cli)
assert.NilError(t, cmd.Execute())
actual := buf.String()
expected := golden.Get(t, []byte(actual), "volume-list-with-config-format.golden")
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
}
func TestVolumeListWithFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
return volumetypes.VolumesListOKBody{
Volumes: []*types.Volume{
Volume(),
Volume(VolumeName("foo"), VolumeDriver("bar")),
Volume(VolumeName("baz"), VolumeLabels(map[string]string{
"foo": "bar",
})),
},
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
cmd.Flags().Set("format", "{{ .Name }} {{ .Driver }} {{ .Labels }}")
assert.NilError(t, cmd.Execute())
actual := buf.String()
expected := golden.Get(t, []byte(actual), "volume-list-with-format.golden")
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
}

View File

@ -3,13 +3,12 @@ package volume
import ( import (
"fmt" "fmt"
"golang.org/x/net/context"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/cli" "github.com/docker/docker/cli"
"github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command"
units "github.com/docker/go-units" units "github.com/docker/go-units"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/net/context"
) )
type pruneOptions struct { type pruneOptions struct {
@ -17,7 +16,7 @@ type pruneOptions struct {
} }
// NewPruneCommand returns a new cobra prune command for volumes // NewPruneCommand returns a new cobra prune command for volumes
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command { func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
var opts pruneOptions var opts pruneOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -47,7 +46,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
const warning = `WARNING! This will remove all volumes not used by at least one container. const warning = `WARNING! This will remove all volumes not used by at least one container.
Are you sure you want to continue?` Are you sure you want to continue?`
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) { func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) { if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return return
} }

View File

@ -0,0 +1,132 @@
package volume
import (
"bytes"
"fmt"
"io/ioutil"
"runtime"
"strings"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/cli/internal/test"
"github.com/docker/docker/pkg/testutil/assert"
"github.com/docker/docker/pkg/testutil/golden"
)
func TestVolumePruneErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
expectedError string
}{
{
args: []string{"foo"},
expectedError: "accepts no argument",
},
{
flags: map[string]string{
"force": "true",
},
volumePruneFunc: func(args filters.Args) (types.VolumesPruneReport, error) {
return types.VolumesPruneReport{}, fmt.Errorf("error pruning volumes")
},
expectedError: "error pruning volumes",
},
}
for _, tc := range testCases {
cmd := NewPruneCommand(
test.NewFakeCli(&fakeClient{
volumePruneFunc: tc.volumePruneFunc,
}, ioutil.Discard),
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
cmd.SetOutput(ioutil.Discard)
assert.Error(t, cmd.Execute(), tc.expectedError)
}
}
func TestVolumePruneForce(t *testing.T) {
testCases := []struct {
name string
volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
}{
{
name: "empty",
},
{
name: "deletedVolumes",
volumePruneFunc: simplePruneFunc,
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(
test.NewFakeCli(&fakeClient{
volumePruneFunc: tc.volumePruneFunc,
}, buf),
)
cmd.Flags().Set("force", "true")
assert.NilError(t, cmd.Execute())
actual := buf.String()
expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-prune.%s.golden", tc.name))
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
}
}
func TestVolumePrunePromptYes(t *testing.T) {
if runtime.GOOS == "windows" {
// FIXME(vdemeester) make it work..
t.Skip("skipping this test on Windows")
}
for _, input := range []string{"y", "Y"} {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
volumePruneFunc: simplePruneFunc,
}, buf)
cli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
cmd := NewPruneCommand(
cli,
)
assert.NilError(t, cmd.Execute())
actual := buf.String()
expected := golden.Get(t, []byte(actual), "volume-prune-yes.golden")
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
}
}
func TestVolumePrunePromptNo(t *testing.T) {
if runtime.GOOS == "windows" {
// FIXME(vdemeester) make it work..
t.Skip("skipping this test on Windows")
}
for _, input := range []string{"n", "N", "no", "anything", "really"} {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
volumePruneFunc: simplePruneFunc,
}, buf)
cli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
cmd := NewPruneCommand(
cli,
)
assert.NilError(t, cmd.Execute())
actual := buf.String()
expected := golden.Get(t, []byte(actual), "volume-prune-no.golden")
assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
}
}
func simplePruneFunc(args filters.Args) (types.VolumesPruneReport, error) {
return types.VolumesPruneReport{
VolumesDeleted: []string{
"foo", "bar", "baz",
},
SpaceReclaimed: 2000,
}, nil
}

View File

@ -2,12 +2,12 @@ package volume
import ( import (
"fmt" "fmt"
"strings"
"golang.org/x/net/context"
"github.com/docker/docker/cli" "github.com/docker/docker/cli"
"github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/net/context"
) )
type removeOptions struct { type removeOptions struct {
@ -16,7 +16,7 @@ type removeOptions struct {
volumes []string volumes []string
} }
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command { func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
var opts removeOptions var opts removeOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -38,22 +38,22 @@ func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runRemove(dockerCli *command.DockerCli, opts *removeOptions) error { func runRemove(dockerCli command.Cli, opts *removeOptions) error {
client := dockerCli.Client() client := dockerCli.Client()
ctx := context.Background() ctx := context.Background()
status := 0
var errs []string
for _, name := range opts.volumes { for _, name := range opts.volumes {
if err := client.VolumeRemove(ctx, name, opts.force); err != nil { if err := client.VolumeRemove(ctx, name, opts.force); err != nil {
fmt.Fprintf(dockerCli.Err(), "%s\n", err) errs = append(errs, err.Error())
status = 1
continue continue
} }
fmt.Fprintf(dockerCli.Out(), "%s\n", name) fmt.Fprintf(dockerCli.Out(), "%s\n", name)
} }
if status != 0 { if len(errs) > 0 {
return cli.StatusError{StatusCode: status} return fmt.Errorf("%s", strings.Join(errs, "\n"))
} }
return nil return nil
} }

View File

@ -0,0 +1,47 @@
package volume
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/docker/cli/internal/test"
"github.com/docker/docker/pkg/testutil/assert"
)
func TestVolumeRemoveErrors(t *testing.T) {
testCases := []struct {
args []string
volumeRemoveFunc func(volumeID string, force bool) error
expectedError string
}{
{
expectedError: "requires at least 1 argument",
},
{
args: []string{"nodeID"},
volumeRemoveFunc: func(volumeID string, force bool) error {
return fmt.Errorf("error removing the volume")
},
expectedError: "error removing the volume",
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newRemoveCommand(
test.NewFakeCli(&fakeClient{
volumeRemoveFunc: tc.volumeRemoveFunc,
}, buf))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
assert.Error(t, cmd.Execute(), tc.expectedError)
}
}
func TestNodeRemoveMultiple(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetArgs([]string{"volume1", "volume2"})
assert.NilError(t, cmd.Execute())
}

View File

@ -0,0 +1 @@
{"foo":"bar"}

View File

@ -0,0 +1 @@
volume

View File

@ -0,0 +1,22 @@
[
{
"Driver": "local",
"Labels": {
"foo": "bar"
},
"Mountpoint": "/data/volume",
"Name": "foo",
"Options": null,
"Scope": "local"
},
{
"Driver": "local",
"Labels": {
"foo": "bar"
},
"Mountpoint": "/data/volume",
"Name": "bar",
"Options": null,
"Scope": "local"
}
]

View File

@ -0,0 +1,10 @@
[
{
"Driver": "local",
"Labels": null,
"Mountpoint": "/data/volume",
"Name": "volume",
"Options": null,
"Scope": "local"
}
]

View File

@ -0,0 +1,3 @@
baz local foo=bar
foo bar
volume local

View File

@ -0,0 +1,3 @@
baz local foo=bar
foo bar
volume local

View File

@ -0,0 +1,4 @@
DRIVER VOLUME NAME
local baz
bar foo
local volume

View File

@ -0,0 +1,2 @@
WARNING! This will remove all volumes not used by at least one container.
Are you sure you want to continue? [y/N] Total reclaimed space: 0B

View File

@ -0,0 +1,7 @@
WARNING! This will remove all volumes not used by at least one container.
Are you sure you want to continue? [y/N] Deleted Volumes:
foo
bar
baz
Total reclaimed space: 2kB

View File

@ -0,0 +1,6 @@
Deleted Volumes:
foo
bar
baz
Total reclaimed space: 2kB

View File

@ -0,0 +1 @@
Total reclaimed space: 0B

View File

@ -0,0 +1,3 @@
// Package builders helps you create struct for your unit test while keeping them expressive.
//
package builders

View File

@ -8,6 +8,9 @@ import (
// Node creates a node with default values. // Node creates a node with default values.
// Any number of node function builder can be pass to augment it. // Any number of node function builder can be pass to augment it.
//
// n1 := Node() // Returns a default node
// n2 := Node(NodeID("foo"), NodeHostname("bar"), Leader())
func Node(builders ...func(*swarm.Node)) *swarm.Node { func Node(builders ...func(*swarm.Node)) *swarm.Node {
t1 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) t1 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
node := &swarm.Node{ node := &swarm.Node{

View File

@ -0,0 +1,43 @@
package builders
import (
"github.com/docker/docker/api/types"
)
// Volume creates a volume with default values.
// Any number of volume function builder can be pass to augment it.
func Volume(builders ...func(volume *types.Volume)) *types.Volume {
volume := &types.Volume{
Name: "volume",
Driver: "local",
Mountpoint: "/data/volume",
Scope: "local",
}
for _, builder := range builders {
builder(volume)
}
return volume
}
// VolumeLabels sets the volume labels
func VolumeLabels(labels map[string]string) func(volume *types.Volume) {
return func(volume *types.Volume) {
volume.Labels = labels
}
}
// VolumeName sets the volume labels
func VolumeName(name string) func(volume *types.Volume) {
return func(volume *types.Volume) {
volume.Name = name
}
}
// VolumeDriver sets the volume driver
func VolumeDriver(name string) func(volume *types.Volume) {
return func(volume *types.Volume) {
volume.Driver = name
}
}

View File

@ -1,21 +1,23 @@
// Package test is a test-only package that can be used by other cli package to write unit test
package test package test
import ( import (
"io" "io"
"io/ioutil" "io/ioutil"
"strings"
"github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/config/configfile"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"strings"
) )
// FakeCli emulates the default DockerCli // FakeCli emulates the default DockerCli
type FakeCli struct { type FakeCli struct {
command.DockerCli command.DockerCli
client client.APIClient client client.APIClient
out io.Writer configfile *configfile.ConfigFile
in io.ReadCloser out io.Writer
err io.Writer
in io.ReadCloser
} }
// NewFakeCli returns a Cli backed by the fakeCli // NewFakeCli returns a Cli backed by the fakeCli
@ -23,6 +25,7 @@ func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
return &FakeCli{ return &FakeCli{
client: client, client: client,
out: out, out: out,
err: ioutil.Discard,
in: ioutil.NopCloser(strings.NewReader("")), in: ioutil.NopCloser(strings.NewReader("")),
} }
} }
@ -32,17 +35,37 @@ func (c *FakeCli) SetIn(in io.ReadCloser) {
c.in = in c.in = in
} }
// SetErr sets the standard error stream th cli should write on
func (c *FakeCli) SetErr(err io.Writer) {
c.err = err
}
// SetConfigfile sets the "fake" config file
func (c *FakeCli) SetConfigfile(configfile *configfile.ConfigFile) {
c.configfile = configfile
}
// Client returns a docker API client // Client returns a docker API client
func (c *FakeCli) Client() client.APIClient { func (c *FakeCli) Client() client.APIClient {
return c.client return c.client
} }
// Out returns the output stream the cli should write on // Out returns the output stream (stdout) the cli should write on
func (c *FakeCli) Out() *command.OutStream { func (c *FakeCli) Out() *command.OutStream {
return command.NewOutStream(c.out) return command.NewOutStream(c.out)
} }
// In returns thi input stream the cli will use // Err returns the output stream (stderr) the cli should write on
func (c *FakeCli) Err() io.Writer {
return c.err
}
// In returns the input stream the cli will use
func (c *FakeCli) In() *command.InStream { func (c *FakeCli) In() *command.InStream {
return command.NewInStream(c.in) return command.NewInStream(c.in)
} }
// ConfigFile returns the cli configfile object (to get client configuration)
func (c *FakeCli) ConfigFile() *configfile.ConfigFile {
return c.configfile
}

5
internal/test/doc.go Normal file
View File

@ -0,0 +1,5 @@
// Package test is a test-only package that can be used by other cli package to write unit test.
//
// It as an internal package and cannot be used outside of github.com/docker/docker/cli package.
//
package test