Browse code

Add unit tests to cli/command/volume package

Signed-off-by: Vincent Demeester <vincent@sbr.pm>

Vincent Demeester authored on 2017/02/28 02:39:35
Showing 30 changed files
... ...
@@ -38,6 +38,7 @@ type Cli interface {
38 38
 	Out() *OutStream
39 39
 	Err() io.Writer
40 40
 	In() *InStream
41
+	ConfigFile() *configfile.ConfigFile
41 42
 }
42 43
 
43 44
 // DockerCli is an instance the docker command line client.
44 45
new file mode 100644
... ...
@@ -0,0 +1,53 @@
0
+package volume
1
+
2
+import (
3
+	"github.com/docker/docker/api/types"
4
+	"github.com/docker/docker/api/types/filters"
5
+	volumetypes "github.com/docker/docker/api/types/volume"
6
+	"github.com/docker/docker/client"
7
+	"golang.org/x/net/context"
8
+)
9
+
10
+type fakeClient struct {
11
+	client.Client
12
+	volumeCreateFunc  func(volumetypes.VolumesCreateBody) (types.Volume, error)
13
+	volumeInspectFunc func(volumeID string) (types.Volume, error)
14
+	volumeListFunc    func(filter filters.Args) (volumetypes.VolumesListOKBody, error)
15
+	volumeRemoveFunc  func(volumeID string, force bool) error
16
+	volumePruneFunc   func(filter filters.Args) (types.VolumesPruneReport, error)
17
+}
18
+
19
+func (c *fakeClient) VolumeCreate(ctx context.Context, options volumetypes.VolumesCreateBody) (types.Volume, error) {
20
+	if c.volumeCreateFunc != nil {
21
+		return c.volumeCreateFunc(options)
22
+	}
23
+	return types.Volume{}, nil
24
+}
25
+
26
+func (c *fakeClient) VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) {
27
+	if c.volumeInspectFunc != nil {
28
+		return c.volumeInspectFunc(volumeID)
29
+	}
30
+	return types.Volume{}, nil
31
+}
32
+
33
+func (c *fakeClient) VolumeList(ctx context.Context, filter filters.Args) (volumetypes.VolumesListOKBody, error) {
34
+	if c.volumeListFunc != nil {
35
+		return c.volumeListFunc(filter)
36
+	}
37
+	return volumetypes.VolumesListOKBody{}, nil
38
+}
39
+
40
+func (c *fakeClient) VolumesPrune(ctx context.Context, filter filters.Args) (types.VolumesPruneReport, error) {
41
+	if c.volumePruneFunc != nil {
42
+		return c.volumePruneFunc(filter)
43
+	}
44
+	return types.VolumesPruneReport{}, nil
45
+}
46
+
47
+func (c *fakeClient) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
48
+	if c.volumeRemoveFunc != nil {
49
+		return c.volumeRemoveFunc(volumeID, force)
50
+	}
51
+	return nil
52
+}
... ...
@@ -1,10 +1,9 @@
1 1
 package volume
2 2
 
3 3
 import (
4
-	"github.com/spf13/cobra"
5
-
6 4
 	"github.com/docker/docker/cli"
7 5
 	"github.com/docker/docker/cli/command"
6
+	"github.com/spf13/cobra"
8 7
 )
9 8
 
10 9
 // NewVolumeCommand returns a cobra command for `volume` subcommands
... ...
@@ -19,7 +19,7 @@ type createOptions struct {
19 19
 	labels     opts.ListOpts
20 20
 }
21 21
 
22
-func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
22
+func newCreateCommand(dockerCli command.Cli) *cobra.Command {
23 23
 	opts := createOptions{
24 24
 		driverOpts: *opts.NewMapOpts(nil, nil),
25 25
 		labels:     opts.NewListOpts(opts.ValidateEnv),
... ...
@@ -32,8 +32,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
32 32
 		RunE: func(cmd *cobra.Command, args []string) error {
33 33
 			if len(args) == 1 {
34 34
 				if opts.name != "" {
35
-					fmt.Fprint(dockerCli.Err(), "Conflicting options: either specify --name or provide positional arg, not both\n")
36
-					return cli.StatusError{StatusCode: 1}
35
+					return fmt.Errorf("Conflicting options: either specify --name or provide positional arg, not both\n")
37 36
 				}
38 37
 				opts.name = args[0]
39 38
 			}
... ...
@@ -50,7 +49,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
50 50
 	return cmd
51 51
 }
52 52
 
53
-func runCreate(dockerCli *command.DockerCli, opts createOptions) error {
53
+func runCreate(dockerCli command.Cli, opts createOptions) error {
54 54
 	client := dockerCli.Client()
55 55
 
56 56
 	volReq := volumetypes.VolumesCreateBody{
57 57
new file mode 100644
... ...
@@ -0,0 +1,142 @@
0
+package volume
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+	"io/ioutil"
6
+	"strings"
7
+	"testing"
8
+
9
+	"github.com/docker/docker/api/types"
10
+	volumetypes "github.com/docker/docker/api/types/volume"
11
+	"github.com/docker/docker/cli/internal/test"
12
+	"github.com/docker/docker/pkg/testutil/assert"
13
+)
14
+
15
+func TestVolumeCreateErrors(t *testing.T) {
16
+	testCases := []struct {
17
+		args             []string
18
+		flags            map[string]string
19
+		volumeCreateFunc func(volumetypes.VolumesCreateBody) (types.Volume, error)
20
+		expectedError    string
21
+	}{
22
+		{
23
+			args: []string{"volumeName"},
24
+			flags: map[string]string{
25
+				"name": "volumeName",
26
+			},
27
+			expectedError: "Conflicting options: either specify --name or provide positional arg, not both",
28
+		},
29
+		{
30
+			args:          []string{"too", "many"},
31
+			expectedError: "requires at most 1 argument(s)",
32
+		},
33
+		{
34
+			volumeCreateFunc: func(createBody volumetypes.VolumesCreateBody) (types.Volume, error) {
35
+				return types.Volume{}, fmt.Errorf("error creating volume")
36
+			},
37
+			expectedError: "error creating volume",
38
+		},
39
+	}
40
+	for _, tc := range testCases {
41
+		buf := new(bytes.Buffer)
42
+		cmd := newCreateCommand(
43
+			test.NewFakeCli(&fakeClient{
44
+				volumeCreateFunc: tc.volumeCreateFunc,
45
+			}, buf),
46
+		)
47
+		cmd.SetArgs(tc.args)
48
+		for key, value := range tc.flags {
49
+			cmd.Flags().Set(key, value)
50
+		}
51
+		cmd.SetOutput(ioutil.Discard)
52
+		assert.Error(t, cmd.Execute(), tc.expectedError)
53
+	}
54
+}
55
+
56
+func TestVolumeCreateWithName(t *testing.T) {
57
+	name := "foo"
58
+	buf := new(bytes.Buffer)
59
+	cli := test.NewFakeCli(&fakeClient{
60
+		volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
61
+			if body.Name != name {
62
+				return types.Volume{}, fmt.Errorf("expected name %q, got %q", name, body.Name)
63
+			}
64
+			return types.Volume{
65
+				Name: body.Name,
66
+			}, nil
67
+		},
68
+	}, buf)
69
+
70
+	// Test by flags
71
+	cmd := newCreateCommand(cli)
72
+	cmd.Flags().Set("name", name)
73
+	assert.NilError(t, cmd.Execute())
74
+	assert.Equal(t, strings.TrimSpace(buf.String()), name)
75
+
76
+	// Then by args
77
+	buf.Reset()
78
+	cmd = newCreateCommand(cli)
79
+	cmd.SetArgs([]string{name})
80
+	assert.NilError(t, cmd.Execute())
81
+	assert.Equal(t, strings.TrimSpace(buf.String()), name)
82
+}
83
+
84
+func TestVolumeCreateWithFlags(t *testing.T) {
85
+	expectedDriver := "foo"
86
+	expectedOpts := map[string]string{
87
+		"bar": "1",
88
+		"baz": "baz",
89
+	}
90
+	expectedLabels := map[string]string{
91
+		"lbl1": "v1",
92
+		"lbl2": "v2",
93
+	}
94
+	name := "banana"
95
+
96
+	buf := new(bytes.Buffer)
97
+	cli := test.NewFakeCli(&fakeClient{
98
+		volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
99
+			if body.Name != "" {
100
+				return types.Volume{}, fmt.Errorf("expected empty name, got %q", body.Name)
101
+			}
102
+			if body.Driver != expectedDriver {
103
+				return types.Volume{}, fmt.Errorf("expected driver %q, got %q", expectedDriver, body.Driver)
104
+			}
105
+			if !compareMap(body.DriverOpts, expectedOpts) {
106
+				return types.Volume{}, fmt.Errorf("expected drivers opts %v, got %v", expectedOpts, body.DriverOpts)
107
+			}
108
+			if !compareMap(body.Labels, expectedLabels) {
109
+				return types.Volume{}, fmt.Errorf("expected labels %v, got %v", expectedLabels, body.Labels)
110
+			}
111
+			return types.Volume{
112
+				Name: name,
113
+			}, nil
114
+		},
115
+	}, buf)
116
+
117
+	cmd := newCreateCommand(cli)
118
+	cmd.Flags().Set("driver", "foo")
119
+	cmd.Flags().Set("opt", "bar=1")
120
+	cmd.Flags().Set("opt", "baz=baz")
121
+	cmd.Flags().Set("label", "lbl1=v1")
122
+	cmd.Flags().Set("label", "lbl2=v2")
123
+	assert.NilError(t, cmd.Execute())
124
+	assert.Equal(t, strings.TrimSpace(buf.String()), name)
125
+}
126
+
127
+func compareMap(actual map[string]string, expected map[string]string) bool {
128
+	if len(actual) != len(expected) {
129
+		return false
130
+	}
131
+	for key, value := range actual {
132
+		if expectedValue, ok := expected[key]; ok {
133
+			if expectedValue != value {
134
+				return false
135
+			}
136
+		} else {
137
+			return false
138
+		}
139
+	}
140
+	return true
141
+}
... ...
@@ -1,12 +1,11 @@
1 1
 package volume
2 2
 
3 3
 import (
4
-	"golang.org/x/net/context"
5
-
6 4
 	"github.com/docker/docker/cli"
7 5
 	"github.com/docker/docker/cli/command"
8 6
 	"github.com/docker/docker/cli/command/inspect"
9 7
 	"github.com/spf13/cobra"
8
+	"golang.org/x/net/context"
10 9
 )
11 10
 
12 11
 type inspectOptions struct {
... ...
@@ -14,7 +13,7 @@ type inspectOptions struct {
14 14
 	names  []string
15 15
 }
16 16
 
17
-func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
17
+func newInspectCommand(dockerCli command.Cli) *cobra.Command {
18 18
 	var opts inspectOptions
19 19
 
20 20
 	cmd := &cobra.Command{
... ...
@@ -32,7 +31,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
32 32
 	return cmd
33 33
 }
34 34
 
35
-func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
35
+func runInspect(dockerCli command.Cli, opts inspectOptions) error {
36 36
 	client := dockerCli.Client()
37 37
 
38 38
 	ctx := context.Background()
39 39
new file mode 100644
... ...
@@ -0,0 +1,150 @@
0
+package volume
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+	"io/ioutil"
6
+	"testing"
7
+
8
+	"github.com/docker/docker/api/types"
9
+	"github.com/docker/docker/cli/internal/test"
10
+	// Import builders to get the builder function as package function
11
+	. "github.com/docker/docker/cli/internal/test/builders"
12
+	"github.com/docker/docker/pkg/testutil/assert"
13
+	"github.com/docker/docker/pkg/testutil/golden"
14
+)
15
+
16
+func TestVolumeInspectErrors(t *testing.T) {
17
+	testCases := []struct {
18
+		args              []string
19
+		flags             map[string]string
20
+		volumeInspectFunc func(volumeID string) (types.Volume, error)
21
+		expectedError     string
22
+	}{
23
+		{
24
+			expectedError: "requires at least 1 argument",
25
+		},
26
+		{
27
+			args: []string{"foo"},
28
+			volumeInspectFunc: func(volumeID string) (types.Volume, error) {
29
+				return types.Volume{}, fmt.Errorf("error while inspecting the volume")
30
+			},
31
+			expectedError: "error while inspecting the volume",
32
+		},
33
+		{
34
+			args: []string{"foo"},
35
+			flags: map[string]string{
36
+				"format": "{{invalid format}}",
37
+			},
38
+			expectedError: "Template parsing error",
39
+		},
40
+		{
41
+			args: []string{"foo", "bar"},
42
+			volumeInspectFunc: func(volumeID string) (types.Volume, error) {
43
+				if volumeID == "foo" {
44
+					return types.Volume{
45
+						Name: "foo",
46
+					}, nil
47
+				}
48
+				return types.Volume{}, fmt.Errorf("error while inspecting the volume")
49
+			},
50
+			expectedError: "error while inspecting the volume",
51
+		},
52
+	}
53
+	for _, tc := range testCases {
54
+		buf := new(bytes.Buffer)
55
+		cmd := newInspectCommand(
56
+			test.NewFakeCli(&fakeClient{
57
+				volumeInspectFunc: tc.volumeInspectFunc,
58
+			}, buf),
59
+		)
60
+		cmd.SetArgs(tc.args)
61
+		for key, value := range tc.flags {
62
+			cmd.Flags().Set(key, value)
63
+		}
64
+		cmd.SetOutput(ioutil.Discard)
65
+		assert.Error(t, cmd.Execute(), tc.expectedError)
66
+	}
67
+}
68
+
69
+func TestVolumeInspectWithoutFormat(t *testing.T) {
70
+	testCases := []struct {
71
+		name              string
72
+		args              []string
73
+		volumeInspectFunc func(volumeID string) (types.Volume, error)
74
+	}{
75
+		{
76
+			name: "single-volume",
77
+			args: []string{"foo"},
78
+			volumeInspectFunc: func(volumeID string) (types.Volume, error) {
79
+				if volumeID != "foo" {
80
+					return types.Volume{}, fmt.Errorf("Invalid volumeID, expected %s, got %s", "foo", volumeID)
81
+				}
82
+				return *Volume(), nil
83
+			},
84
+		},
85
+		{
86
+			name: "multiple-volume-with-labels",
87
+			args: []string{"foo", "bar"},
88
+			volumeInspectFunc: func(volumeID string) (types.Volume, error) {
89
+				return *Volume(VolumeName(volumeID), VolumeLabels(map[string]string{
90
+					"foo": "bar",
91
+				})), nil
92
+			},
93
+		},
94
+	}
95
+	for _, tc := range testCases {
96
+		buf := new(bytes.Buffer)
97
+		cmd := newInspectCommand(
98
+			test.NewFakeCli(&fakeClient{
99
+				volumeInspectFunc: tc.volumeInspectFunc,
100
+			}, buf),
101
+		)
102
+		cmd.SetArgs(tc.args)
103
+		assert.NilError(t, cmd.Execute())
104
+		actual := buf.String()
105
+		expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-inspect-without-format.%s.golden", tc.name))
106
+		assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
107
+	}
108
+}
109
+
110
+func TestVolumeInspectWithFormat(t *testing.T) {
111
+	volumeInspectFunc := func(volumeID string) (types.Volume, error) {
112
+		return *Volume(VolumeLabels(map[string]string{
113
+			"foo": "bar",
114
+		})), nil
115
+	}
116
+	testCases := []struct {
117
+		name              string
118
+		format            string
119
+		args              []string
120
+		volumeInspectFunc func(volumeID string) (types.Volume, error)
121
+	}{
122
+		{
123
+			name:              "simple-template",
124
+			format:            "{{.Name}}",
125
+			args:              []string{"foo"},
126
+			volumeInspectFunc: volumeInspectFunc,
127
+		},
128
+		{
129
+			name:              "json-template",
130
+			format:            "{{json .Labels}}",
131
+			args:              []string{"foo"},
132
+			volumeInspectFunc: volumeInspectFunc,
133
+		},
134
+	}
135
+	for _, tc := range testCases {
136
+		buf := new(bytes.Buffer)
137
+		cmd := newInspectCommand(
138
+			test.NewFakeCli(&fakeClient{
139
+				volumeInspectFunc: tc.volumeInspectFunc,
140
+			}, buf),
141
+		)
142
+		cmd.SetArgs(tc.args)
143
+		cmd.Flags().Set("format", tc.format)
144
+		assert.NilError(t, cmd.Execute())
145
+		actual := buf.String()
146
+		expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-inspect-with-format.%s.golden", tc.name))
147
+		assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
148
+	}
149
+}
... ...
@@ -3,14 +3,13 @@ package volume
3 3
 import (
4 4
 	"sort"
5 5
 
6
-	"golang.org/x/net/context"
7
-
8 6
 	"github.com/docker/docker/api/types"
9 7
 	"github.com/docker/docker/cli"
10 8
 	"github.com/docker/docker/cli/command"
11 9
 	"github.com/docker/docker/cli/command/formatter"
12 10
 	"github.com/docker/docker/opts"
13 11
 	"github.com/spf13/cobra"
12
+	"golang.org/x/net/context"
14 13
 )
15 14
 
16 15
 type byVolumeName []*types.Volume
... ...
@@ -27,7 +26,7 @@ type listOptions struct {
27 27
 	filter opts.FilterOpt
28 28
 }
29 29
 
30
-func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
30
+func newListCommand(dockerCli command.Cli) *cobra.Command {
31 31
 	opts := listOptions{filter: opts.NewFilterOpt()}
32 32
 
33 33
 	cmd := &cobra.Command{
... ...
@@ -48,7 +47,7 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
48 48
 	return cmd
49 49
 }
50 50
 
51
-func runList(dockerCli *command.DockerCli, opts listOptions) error {
51
+func runList(dockerCli command.Cli, opts listOptions) error {
52 52
 	client := dockerCli.Client()
53 53
 	volumes, err := client.VolumeList(context.Background(), opts.filter.Value())
54 54
 	if err != nil {
55 55
new file mode 100644
... ...
@@ -0,0 +1,124 @@
0
+package volume
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+	"io/ioutil"
6
+	"testing"
7
+
8
+	"github.com/docker/docker/api/types"
9
+	"github.com/docker/docker/api/types/filters"
10
+	volumetypes "github.com/docker/docker/api/types/volume"
11
+	"github.com/docker/docker/cli/config/configfile"
12
+	"github.com/docker/docker/cli/internal/test"
13
+	// Import builders to get the builder function as package function
14
+	. "github.com/docker/docker/cli/internal/test/builders"
15
+	"github.com/docker/docker/pkg/testutil/assert"
16
+	"github.com/docker/docker/pkg/testutil/golden"
17
+)
18
+
19
+func TestVolumeListErrors(t *testing.T) {
20
+	testCases := []struct {
21
+		args           []string
22
+		flags          map[string]string
23
+		volumeListFunc func(filter filters.Args) (volumetypes.VolumesListOKBody, error)
24
+		expectedError  string
25
+	}{
26
+		{
27
+			args:          []string{"foo"},
28
+			expectedError: "accepts no argument",
29
+		},
30
+		{
31
+			volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
32
+				return volumetypes.VolumesListOKBody{}, fmt.Errorf("error listing volumes")
33
+			},
34
+			expectedError: "error listing volumes",
35
+		},
36
+	}
37
+	for _, tc := range testCases {
38
+		buf := new(bytes.Buffer)
39
+		cmd := newListCommand(
40
+			test.NewFakeCli(&fakeClient{
41
+				volumeListFunc: tc.volumeListFunc,
42
+			}, buf),
43
+		)
44
+		cmd.SetArgs(tc.args)
45
+		for key, value := range tc.flags {
46
+			cmd.Flags().Set(key, value)
47
+		}
48
+		cmd.SetOutput(ioutil.Discard)
49
+		assert.Error(t, cmd.Execute(), tc.expectedError)
50
+	}
51
+}
52
+
53
+func TestVolumeListWithoutFormat(t *testing.T) {
54
+	buf := new(bytes.Buffer)
55
+	cli := test.NewFakeCli(&fakeClient{
56
+		volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
57
+			return volumetypes.VolumesListOKBody{
58
+				Volumes: []*types.Volume{
59
+					Volume(),
60
+					Volume(VolumeName("foo"), VolumeDriver("bar")),
61
+					Volume(VolumeName("baz"), VolumeLabels(map[string]string{
62
+						"foo": "bar",
63
+					})),
64
+				},
65
+			}, nil
66
+		},
67
+	}, buf)
68
+	cli.SetConfigfile(&configfile.ConfigFile{})
69
+	cmd := newListCommand(cli)
70
+	assert.NilError(t, cmd.Execute())
71
+	actual := buf.String()
72
+	expected := golden.Get(t, []byte(actual), "volume-list-without-format.golden")
73
+	assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
74
+}
75
+
76
+func TestVolumeListWithConfigFormat(t *testing.T) {
77
+	buf := new(bytes.Buffer)
78
+	cli := test.NewFakeCli(&fakeClient{
79
+		volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
80
+			return volumetypes.VolumesListOKBody{
81
+				Volumes: []*types.Volume{
82
+					Volume(),
83
+					Volume(VolumeName("foo"), VolumeDriver("bar")),
84
+					Volume(VolumeName("baz"), VolumeLabels(map[string]string{
85
+						"foo": "bar",
86
+					})),
87
+				},
88
+			}, nil
89
+		},
90
+	}, buf)
91
+	cli.SetConfigfile(&configfile.ConfigFile{
92
+		VolumesFormat: "{{ .Name }} {{ .Driver }} {{ .Labels }}",
93
+	})
94
+	cmd := newListCommand(cli)
95
+	assert.NilError(t, cmd.Execute())
96
+	actual := buf.String()
97
+	expected := golden.Get(t, []byte(actual), "volume-list-with-config-format.golden")
98
+	assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
99
+}
100
+
101
+func TestVolumeListWithFormat(t *testing.T) {
102
+	buf := new(bytes.Buffer)
103
+	cli := test.NewFakeCli(&fakeClient{
104
+		volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
105
+			return volumetypes.VolumesListOKBody{
106
+				Volumes: []*types.Volume{
107
+					Volume(),
108
+					Volume(VolumeName("foo"), VolumeDriver("bar")),
109
+					Volume(VolumeName("baz"), VolumeLabels(map[string]string{
110
+						"foo": "bar",
111
+					})),
112
+				},
113
+			}, nil
114
+		},
115
+	}, buf)
116
+	cli.SetConfigfile(&configfile.ConfigFile{})
117
+	cmd := newListCommand(cli)
118
+	cmd.Flags().Set("format", "{{ .Name }} {{ .Driver }} {{ .Labels }}")
119
+	assert.NilError(t, cmd.Execute())
120
+	actual := buf.String()
121
+	expected := golden.Get(t, []byte(actual), "volume-list-with-format.golden")
122
+	assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
123
+}
... ...
@@ -3,13 +3,12 @@ package volume
3 3
 import (
4 4
 	"fmt"
5 5
 
6
-	"golang.org/x/net/context"
7
-
8 6
 	"github.com/docker/docker/api/types/filters"
9 7
 	"github.com/docker/docker/cli"
10 8
 	"github.com/docker/docker/cli/command"
11 9
 	units "github.com/docker/go-units"
12 10
 	"github.com/spf13/cobra"
11
+	"golang.org/x/net/context"
13 12
 )
14 13
 
15 14
 type pruneOptions struct {
... ...
@@ -17,7 +16,7 @@ type pruneOptions struct {
17 17
 }
18 18
 
19 19
 // NewPruneCommand returns a new cobra prune command for volumes
20
-func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
20
+func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
21 21
 	var opts pruneOptions
22 22
 
23 23
 	cmd := &cobra.Command{
... ...
@@ -47,7 +46,7 @@ func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
47 47
 const warning = `WARNING! This will remove all volumes not used by at least one container.
48 48
 Are you sure you want to continue?`
49 49
 
50
-func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
50
+func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
51 51
 	if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
52 52
 		return
53 53
 	}
54 54
new file mode 100644
... ...
@@ -0,0 +1,132 @@
0
+package volume
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+	"io/ioutil"
6
+	"runtime"
7
+	"strings"
8
+	"testing"
9
+
10
+	"github.com/docker/docker/api/types"
11
+	"github.com/docker/docker/api/types/filters"
12
+	"github.com/docker/docker/cli/internal/test"
13
+	"github.com/docker/docker/pkg/testutil/assert"
14
+	"github.com/docker/docker/pkg/testutil/golden"
15
+)
16
+
17
+func TestVolumePruneErrors(t *testing.T) {
18
+	testCases := []struct {
19
+		args            []string
20
+		flags           map[string]string
21
+		volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
22
+		expectedError   string
23
+	}{
24
+		{
25
+			args:          []string{"foo"},
26
+			expectedError: "accepts no argument",
27
+		},
28
+		{
29
+			flags: map[string]string{
30
+				"force": "true",
31
+			},
32
+			volumePruneFunc: func(args filters.Args) (types.VolumesPruneReport, error) {
33
+				return types.VolumesPruneReport{}, fmt.Errorf("error pruning volumes")
34
+			},
35
+			expectedError: "error pruning volumes",
36
+		},
37
+	}
38
+	for _, tc := range testCases {
39
+		cmd := NewPruneCommand(
40
+			test.NewFakeCli(&fakeClient{
41
+				volumePruneFunc: tc.volumePruneFunc,
42
+			}, ioutil.Discard),
43
+		)
44
+		cmd.SetArgs(tc.args)
45
+		for key, value := range tc.flags {
46
+			cmd.Flags().Set(key, value)
47
+		}
48
+		cmd.SetOutput(ioutil.Discard)
49
+		assert.Error(t, cmd.Execute(), tc.expectedError)
50
+	}
51
+}
52
+
53
+func TestVolumePruneForce(t *testing.T) {
54
+	testCases := []struct {
55
+		name            string
56
+		volumePruneFunc func(args filters.Args) (types.VolumesPruneReport, error)
57
+	}{
58
+		{
59
+			name: "empty",
60
+		},
61
+		{
62
+			name:            "deletedVolumes",
63
+			volumePruneFunc: simplePruneFunc,
64
+		},
65
+	}
66
+	for _, tc := range testCases {
67
+		buf := new(bytes.Buffer)
68
+		cmd := NewPruneCommand(
69
+			test.NewFakeCli(&fakeClient{
70
+				volumePruneFunc: tc.volumePruneFunc,
71
+			}, buf),
72
+		)
73
+		cmd.Flags().Set("force", "true")
74
+		assert.NilError(t, cmd.Execute())
75
+		actual := buf.String()
76
+		expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-prune.%s.golden", tc.name))
77
+		assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
78
+	}
79
+}
80
+func TestVolumePrunePromptYes(t *testing.T) {
81
+	if runtime.GOOS == "windows" {
82
+		// FIXME(vdemeester) make it work..
83
+		t.Skip("skipping this test on Windows")
84
+	}
85
+	for _, input := range []string{"y", "Y"} {
86
+		buf := new(bytes.Buffer)
87
+		cli := test.NewFakeCli(&fakeClient{
88
+			volumePruneFunc: simplePruneFunc,
89
+		}, buf)
90
+
91
+		cli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
92
+		cmd := NewPruneCommand(
93
+			cli,
94
+		)
95
+		assert.NilError(t, cmd.Execute())
96
+		actual := buf.String()
97
+		expected := golden.Get(t, []byte(actual), "volume-prune-yes.golden")
98
+		assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
99
+	}
100
+}
101
+
102
+func TestVolumePrunePromptNo(t *testing.T) {
103
+	if runtime.GOOS == "windows" {
104
+		// FIXME(vdemeester) make it work..
105
+		t.Skip("skipping this test on Windows")
106
+	}
107
+	for _, input := range []string{"n", "N", "no", "anything", "really"} {
108
+		buf := new(bytes.Buffer)
109
+		cli := test.NewFakeCli(&fakeClient{
110
+			volumePruneFunc: simplePruneFunc,
111
+		}, buf)
112
+
113
+		cli.SetIn(ioutil.NopCloser(strings.NewReader(input)))
114
+		cmd := NewPruneCommand(
115
+			cli,
116
+		)
117
+		assert.NilError(t, cmd.Execute())
118
+		actual := buf.String()
119
+		expected := golden.Get(t, []byte(actual), "volume-prune-no.golden")
120
+		assert.EqualNormalizedString(t, assert.RemoveSpace, actual, string(expected))
121
+	}
122
+}
123
+
124
+func simplePruneFunc(args filters.Args) (types.VolumesPruneReport, error) {
125
+	return types.VolumesPruneReport{
126
+		VolumesDeleted: []string{
127
+			"foo", "bar", "baz",
128
+		},
129
+		SpaceReclaimed: 2000,
130
+	}, nil
131
+}
... ...
@@ -2,12 +2,12 @@ package volume
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-
6
-	"golang.org/x/net/context"
5
+	"strings"
7 6
 
8 7
 	"github.com/docker/docker/cli"
9 8
 	"github.com/docker/docker/cli/command"
10 9
 	"github.com/spf13/cobra"
10
+	"golang.org/x/net/context"
11 11
 )
12 12
 
13 13
 type removeOptions struct {
... ...
@@ -16,7 +16,7 @@ type removeOptions struct {
16 16
 	volumes []string
17 17
 }
18 18
 
19
-func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
19
+func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
20 20
 	var opts removeOptions
21 21
 
22 22
 	cmd := &cobra.Command{
... ...
@@ -38,22 +38,22 @@ func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
38 38
 	return cmd
39 39
 }
40 40
 
41
-func runRemove(dockerCli *command.DockerCli, opts *removeOptions) error {
41
+func runRemove(dockerCli command.Cli, opts *removeOptions) error {
42 42
 	client := dockerCli.Client()
43 43
 	ctx := context.Background()
44
-	status := 0
44
+
45
+	var errs []string
45 46
 
46 47
 	for _, name := range opts.volumes {
47 48
 		if err := client.VolumeRemove(ctx, name, opts.force); err != nil {
48
-			fmt.Fprintf(dockerCli.Err(), "%s\n", err)
49
-			status = 1
49
+			errs = append(errs, err.Error())
50 50
 			continue
51 51
 		}
52 52
 		fmt.Fprintf(dockerCli.Out(), "%s\n", name)
53 53
 	}
54 54
 
55
-	if status != 0 {
56
-		return cli.StatusError{StatusCode: status}
55
+	if len(errs) > 0 {
56
+		return fmt.Errorf("%s", strings.Join(errs, "\n"))
57 57
 	}
58 58
 	return nil
59 59
 }
60 60
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+package volume
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+	"io/ioutil"
6
+	"testing"
7
+
8
+	"github.com/docker/docker/cli/internal/test"
9
+	"github.com/docker/docker/pkg/testutil/assert"
10
+)
11
+
12
+func TestVolumeRemoveErrors(t *testing.T) {
13
+	testCases := []struct {
14
+		args             []string
15
+		volumeRemoveFunc func(volumeID string, force bool) error
16
+		expectedError    string
17
+	}{
18
+		{
19
+			expectedError: "requires at least 1 argument",
20
+		},
21
+		{
22
+			args: []string{"nodeID"},
23
+			volumeRemoveFunc: func(volumeID string, force bool) error {
24
+				return fmt.Errorf("error removing the volume")
25
+			},
26
+			expectedError: "error removing the volume",
27
+		},
28
+	}
29
+	for _, tc := range testCases {
30
+		buf := new(bytes.Buffer)
31
+		cmd := newRemoveCommand(
32
+			test.NewFakeCli(&fakeClient{
33
+				volumeRemoveFunc: tc.volumeRemoveFunc,
34
+			}, buf))
35
+		cmd.SetArgs(tc.args)
36
+		cmd.SetOutput(ioutil.Discard)
37
+		assert.Error(t, cmd.Execute(), tc.expectedError)
38
+	}
39
+}
40
+
41
+func TestNodeRemoveMultiple(t *testing.T) {
42
+	buf := new(bytes.Buffer)
43
+	cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, buf))
44
+	cmd.SetArgs([]string{"volume1", "volume2"})
45
+	assert.NilError(t, cmd.Execute())
46
+}
0 47
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+{"foo":"bar"}
0 1
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+volume
0 1
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+[
1
+    {
2
+        "Driver": "local",
3
+        "Labels": {
4
+            "foo": "bar"
5
+        },
6
+        "Mountpoint": "/data/volume",
7
+        "Name": "foo",
8
+        "Options": null,
9
+        "Scope": "local"
10
+    },
11
+    {
12
+        "Driver": "local",
13
+        "Labels": {
14
+            "foo": "bar"
15
+        },
16
+        "Mountpoint": "/data/volume",
17
+        "Name": "bar",
18
+        "Options": null,
19
+        "Scope": "local"
20
+    }
21
+]
0 22
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+[
1
+    {
2
+        "Driver": "local",
3
+        "Labels": null,
4
+        "Mountpoint": "/data/volume",
5
+        "Name": "volume",
6
+        "Options": null,
7
+        "Scope": "local"
8
+    }
9
+]
0 10
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+baz local foo=bar
1
+foo bar 
2
+volume local 
0 3
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+baz local foo=bar
1
+foo bar 
2
+volume local 
0 3
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+DRIVER              VOLUME NAME
1
+local               baz
2
+bar                 foo
3
+local               volume
0 4
new file mode 100644
... ...
@@ -0,0 +1,2 @@
0
+WARNING! This will remove all volumes not used by at least one container.
1
+Are you sure you want to continue? [y/N] Total reclaimed space: 0B
0 2
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+WARNING! This will remove all volumes not used by at least one container.
1
+Are you sure you want to continue? [y/N] Deleted Volumes:
2
+foo
3
+bar
4
+baz
5
+
6
+Total reclaimed space: 2kB
0 7
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+Deleted Volumes:
1
+foo
2
+bar
3
+baz
4
+
5
+Total reclaimed space: 2kB
0 6
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Total reclaimed space: 0B
0 1
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+// Package builders helps you create struct for your unit test while keeping them expressive.
1
+//
2
+package builders
... ...
@@ -8,6 +8,9 @@ import (
8 8
 
9 9
 // Node creates a node with default values.
10 10
 // Any number of node function builder can be pass to augment it.
11
+//
12
+//	n1 := Node() // Returns a default node
13
+//	n2 := Node(NodeID("foo"), NodeHostname("bar"), Leader())
11 14
 func Node(builders ...func(*swarm.Node)) *swarm.Node {
12 15
 	t1 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
13 16
 	node := &swarm.Node{
14 17
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+package builders
1
+
2
+import (
3
+	"github.com/docker/docker/api/types"
4
+)
5
+
6
+// Volume creates a volume with default values.
7
+// Any number of volume function builder can be pass to augment it.
8
+func Volume(builders ...func(volume *types.Volume)) *types.Volume {
9
+	volume := &types.Volume{
10
+		Name:       "volume",
11
+		Driver:     "local",
12
+		Mountpoint: "/data/volume",
13
+		Scope:      "local",
14
+	}
15
+
16
+	for _, builder := range builders {
17
+		builder(volume)
18
+	}
19
+
20
+	return volume
21
+}
22
+
23
+// VolumeLabels sets the volume labels
24
+func VolumeLabels(labels map[string]string) func(volume *types.Volume) {
25
+	return func(volume *types.Volume) {
26
+		volume.Labels = labels
27
+	}
28
+}
29
+
30
+// VolumeName sets the volume labels
31
+func VolumeName(name string) func(volume *types.Volume) {
32
+	return func(volume *types.Volume) {
33
+		volume.Name = name
34
+	}
35
+}
36
+
37
+// VolumeDriver sets the volume driver
38
+func VolumeDriver(name string) func(volume *types.Volume) {
39
+	return func(volume *types.Volume) {
40
+		volume.Driver = name
41
+	}
42
+}
... ...
@@ -1,21 +1,23 @@
1
-// Package test is a test-only package that can be used by other cli package to write unit test
2 1
 package test
3 2
 
4 3
 import (
5 4
 	"io"
6 5
 	"io/ioutil"
6
+	"strings"
7 7
 
8 8
 	"github.com/docker/docker/cli/command"
9
+	"github.com/docker/docker/cli/config/configfile"
9 10
 	"github.com/docker/docker/client"
10
-	"strings"
11 11
 )
12 12
 
13 13
 // FakeCli emulates the default DockerCli
14 14
 type FakeCli struct {
15 15
 	command.DockerCli
16
-	client client.APIClient
17
-	out    io.Writer
18
-	in     io.ReadCloser
16
+	client     client.APIClient
17
+	configfile *configfile.ConfigFile
18
+	out        io.Writer
19
+	err        io.Writer
20
+	in         io.ReadCloser
19 21
 }
20 22
 
21 23
 // NewFakeCli returns a Cli backed by the fakeCli
... ...
@@ -23,6 +25,7 @@ func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
23 23
 	return &FakeCli{
24 24
 		client: client,
25 25
 		out:    out,
26
+		err:    ioutil.Discard,
26 27
 		in:     ioutil.NopCloser(strings.NewReader("")),
27 28
 	}
28 29
 }
... ...
@@ -32,17 +35,37 @@ func (c *FakeCli) SetIn(in io.ReadCloser) {
32 32
 	c.in = in
33 33
 }
34 34
 
35
+// SetErr sets the standard error stream th cli should write on
36
+func (c *FakeCli) SetErr(err io.Writer) {
37
+	c.err = err
38
+}
39
+
40
+// SetConfigfile sets the "fake" config file
41
+func (c *FakeCli) SetConfigfile(configfile *configfile.ConfigFile) {
42
+	c.configfile = configfile
43
+}
44
+
35 45
 // Client returns a docker API client
36 46
 func (c *FakeCli) Client() client.APIClient {
37 47
 	return c.client
38 48
 }
39 49
 
40
-// Out returns the output stream the cli should write on
50
+// Out returns the output stream (stdout) the cli should write on
41 51
 func (c *FakeCli) Out() *command.OutStream {
42 52
 	return command.NewOutStream(c.out)
43 53
 }
44 54
 
45
-// In returns thi input stream the cli will use
55
+// Err returns the output stream (stderr) the cli should write on
56
+func (c *FakeCli) Err() io.Writer {
57
+	return c.err
58
+}
59
+
60
+// In returns the input stream the cli will use
46 61
 func (c *FakeCli) In() *command.InStream {
47 62
 	return command.NewInStream(c.in)
48 63
 }
64
+
65
+// ConfigFile returns the cli configfile object (to get client configuration)
66
+func (c *FakeCli) ConfigFile() *configfile.ConfigFile {
67
+	return c.configfile
68
+}
49 69
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+// Package test is a test-only package that can be used by other cli package to write unit test.
1
+//
2
+// It as an internal package and cannot be used outside of github.com/docker/docker/cli package.
3
+//
4
+package test
... ...
@@ -292,11 +292,6 @@ func (s *DockerExternalVolumeSuite) TestVolumeCLICreateOptionConflict(c *check.C
292 292
 	out, _ = dockerCmd(c, "volume", "inspect", "--format={{ .Driver }}", "test")
293 293
 	_, _, err = dockerCmdWithError("volume", "create", "test", "--driver", strings.TrimSpace(out))
294 294
 	c.Assert(err, check.IsNil)
295
-
296
-	// make sure hidden --name option conflicts with positional arg name
297
-	out, _, err = dockerCmdWithError("volume", "create", "--name", "test2", "test2")
298
-	c.Assert(err, check.NotNil)
299
-	c.Assert(strings.TrimSpace(out), checker.Equals, "Conflicting options: either specify --name or provide positional arg, not both")
300 295
 }
301 296
 
302 297
 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamed(c *check.C) {