github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/container/list_test.go (about) 1 package container 2 3 import ( 4 "fmt" 5 "io" 6 "testing" 7 8 "github.com/docker/cli/cli/config/configfile" 9 "github.com/docker/cli/internal/test" 10 "github.com/docker/cli/internal/test/builders" 11 "github.com/docker/cli/opts" 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/container" 14 "gotest.tools/v3/assert" 15 is "gotest.tools/v3/assert/cmp" 16 "gotest.tools/v3/golden" 17 ) 18 19 func TestContainerListBuildContainerListOptions(t *testing.T) { 20 filters := opts.NewFilterOpt() 21 assert.NilError(t, filters.Set("foo=bar")) 22 assert.NilError(t, filters.Set("baz=foo")) 23 24 contexts := []struct { 25 psOpts *psOptions 26 expectedAll bool 27 expectedSize bool 28 expectedLimit int 29 expectedFilters map[string]string 30 }{ 31 { 32 psOpts: &psOptions{ 33 all: true, 34 size: true, 35 last: 5, 36 filter: filters, 37 }, 38 expectedAll: true, 39 expectedSize: true, 40 expectedLimit: 5, 41 expectedFilters: map[string]string{ 42 "foo": "bar", 43 "baz": "foo", 44 }, 45 }, 46 { 47 psOpts: &psOptions{ 48 all: true, 49 size: true, 50 last: -1, 51 nLatest: true, 52 }, 53 expectedAll: true, 54 expectedSize: true, 55 expectedLimit: 1, 56 expectedFilters: make(map[string]string), 57 }, 58 { 59 psOpts: &psOptions{ 60 all: true, 61 size: false, 62 last: 5, 63 filter: filters, 64 // With .Size, size should be true 65 format: "{{.Size}}", 66 }, 67 expectedAll: true, 68 expectedSize: true, 69 expectedLimit: 5, 70 expectedFilters: map[string]string{ 71 "foo": "bar", 72 "baz": "foo", 73 }, 74 }, 75 { 76 psOpts: &psOptions{ 77 all: true, 78 size: false, 79 last: 5, 80 filter: filters, 81 // With .Size, size should be true 82 format: "{{.Size}} {{.CreatedAt}} {{upper .Networks}}", 83 }, 84 expectedAll: true, 85 expectedSize: true, 86 expectedLimit: 5, 87 expectedFilters: map[string]string{ 88 "foo": "bar", 89 "baz": "foo", 90 }, 91 }, 92 { 93 psOpts: &psOptions{ 94 all: true, 95 size: false, 96 last: 5, 97 filter: filters, 98 // Without .Size, size should be false 99 format: "{{.CreatedAt}} {{.Networks}}", 100 }, 101 expectedAll: true, 102 expectedSize: false, 103 expectedLimit: 5, 104 expectedFilters: map[string]string{ 105 "foo": "bar", 106 "baz": "foo", 107 }, 108 }, 109 } 110 111 for _, c := range contexts { 112 options, err := buildContainerListOptions(c.psOpts) 113 assert.NilError(t, err) 114 115 assert.Check(t, is.Equal(c.expectedAll, options.All)) 116 assert.Check(t, is.Equal(c.expectedSize, options.Size)) 117 assert.Check(t, is.Equal(c.expectedLimit, options.Limit)) 118 assert.Check(t, is.Equal(len(c.expectedFilters), options.Filters.Len())) 119 120 for k, v := range c.expectedFilters { 121 f := options.Filters 122 if !f.ExactMatch(k, v) { 123 t.Fatalf("Expected filter with key %s to be %s but got %s", k, v, f.Get(k)) 124 } 125 } 126 } 127 } 128 129 func TestContainerListErrors(t *testing.T) { 130 testCases := []struct { 131 args []string 132 flags map[string]string 133 containerListFunc func(container.ListOptions) ([]types.Container, error) 134 expectedError string 135 }{ 136 { 137 flags: map[string]string{ 138 "format": "{{invalid}}", 139 }, 140 expectedError: `function "invalid" not defined`, 141 }, 142 { 143 flags: map[string]string{ 144 "format": "{{join}}", 145 }, 146 expectedError: `wrong number of args for join`, 147 }, 148 { 149 containerListFunc: func(_ container.ListOptions) ([]types.Container, error) { 150 return nil, fmt.Errorf("error listing containers") 151 }, 152 expectedError: "error listing containers", 153 }, 154 } 155 for _, tc := range testCases { 156 cmd := newListCommand( 157 test.NewFakeCli(&fakeClient{ 158 containerListFunc: tc.containerListFunc, 159 }), 160 ) 161 cmd.SetArgs(tc.args) 162 for key, value := range tc.flags { 163 assert.Check(t, cmd.Flags().Set(key, value)) 164 } 165 cmd.SetOut(io.Discard) 166 assert.ErrorContains(t, cmd.Execute(), tc.expectedError) 167 } 168 } 169 170 func TestContainerListWithoutFormat(t *testing.T) { 171 cli := test.NewFakeCli(&fakeClient{ 172 containerListFunc: func(_ container.ListOptions) ([]types.Container, error) { 173 return []types.Container{ 174 *builders.Container("c1"), 175 *builders.Container("c2", builders.WithName("foo")), 176 *builders.Container("c3", builders.WithPort(80, 80, builders.TCP), builders.WithPort(81, 81, builders.TCP), builders.WithPort(82, 82, builders.TCP)), 177 *builders.Container("c4", builders.WithPort(81, 81, builders.UDP)), 178 *builders.Container("c5", builders.WithPort(82, 82, builders.IP("8.8.8.8"), builders.TCP)), 179 }, nil 180 }, 181 }) 182 cmd := newListCommand(cli) 183 assert.NilError(t, cmd.Execute()) 184 golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format.golden") 185 } 186 187 func TestContainerListNoTrunc(t *testing.T) { 188 cli := test.NewFakeCli(&fakeClient{ 189 containerListFunc: func(_ container.ListOptions) ([]types.Container, error) { 190 return []types.Container{ 191 *builders.Container("c1"), 192 *builders.Container("c2", builders.WithName("foo/bar")), 193 }, nil 194 }, 195 }) 196 cmd := newListCommand(cli) 197 assert.Check(t, cmd.Flags().Set("no-trunc", "true")) 198 assert.NilError(t, cmd.Execute()) 199 golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format-no-trunc.golden") 200 } 201 202 // Test for GitHub issue docker/docker#21772 203 func TestContainerListNamesMultipleTime(t *testing.T) { 204 cli := test.NewFakeCli(&fakeClient{ 205 containerListFunc: func(_ container.ListOptions) ([]types.Container, error) { 206 return []types.Container{ 207 *builders.Container("c1"), 208 *builders.Container("c2", builders.WithName("foo/bar")), 209 }, nil 210 }, 211 }) 212 cmd := newListCommand(cli) 213 assert.Check(t, cmd.Flags().Set("format", "{{.Names}} {{.Names}}")) 214 assert.NilError(t, cmd.Execute()) 215 golden.Assert(t, cli.OutBuffer().String(), "container-list-format-name-name.golden") 216 } 217 218 // Test for GitHub issue docker/docker#30291 219 func TestContainerListFormatTemplateWithArg(t *testing.T) { 220 cli := test.NewFakeCli(&fakeClient{ 221 containerListFunc: func(_ container.ListOptions) ([]types.Container, error) { 222 return []types.Container{ 223 *builders.Container("c1", builders.WithLabel("some.label", "value")), 224 *builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")), 225 }, nil 226 }, 227 }) 228 cmd := newListCommand(cli) 229 assert.Check(t, cmd.Flags().Set("format", `{{.Names}} {{.Label "some.label"}}`)) 230 assert.NilError(t, cmd.Execute()) 231 golden.Assert(t, cli.OutBuffer().String(), "container-list-format-with-arg.golden") 232 } 233 234 func TestContainerListFormatSizeSetsOption(t *testing.T) { 235 tests := []struct { 236 doc, format, sizeFlag string 237 sizeExpected bool 238 }{ 239 { 240 doc: "detect with all fields", 241 format: `{{json .}}`, 242 sizeExpected: true, 243 }, 244 { 245 doc: "detect with explicit field", 246 format: `{{.Size}}`, 247 sizeExpected: true, 248 }, 249 { 250 doc: "detect no size", 251 format: `{{.Names}}`, 252 sizeExpected: false, 253 }, 254 { 255 doc: "override enable", 256 format: `{{.Names}}`, 257 sizeFlag: "true", 258 sizeExpected: true, 259 }, 260 { 261 doc: "override disable", 262 format: `{{.Size}}`, 263 sizeFlag: "false", 264 sizeExpected: false, 265 }, 266 } 267 268 for _, tc := range tests { 269 tc := tc 270 t.Run(tc.doc, func(t *testing.T) { 271 cli := test.NewFakeCli(&fakeClient{ 272 containerListFunc: func(options container.ListOptions) ([]types.Container, error) { 273 assert.Check(t, is.Equal(options.Size, tc.sizeExpected)) 274 return []types.Container{}, nil 275 }, 276 }) 277 cmd := newListCommand(cli) 278 assert.Check(t, cmd.Flags().Set("format", tc.format)) 279 if tc.sizeFlag != "" { 280 assert.Check(t, cmd.Flags().Set("size", tc.sizeFlag)) 281 } 282 assert.NilError(t, cmd.Execute()) 283 }) 284 } 285 } 286 287 func TestContainerListWithConfigFormat(t *testing.T) { 288 cli := test.NewFakeCli(&fakeClient{ 289 containerListFunc: func(_ container.ListOptions) ([]types.Container, error) { 290 return []types.Container{ 291 *builders.Container("c1", builders.WithLabel("some.label", "value"), builders.WithSize(10700000)), 292 *builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar"), builders.WithSize(3200000)), 293 }, nil 294 }, 295 }) 296 cli.SetConfigFile(&configfile.ConfigFile{ 297 PsFormat: "{{ .Names }} {{ .Image }} {{ .Labels }} {{ .Size}}", 298 }) 299 cmd := newListCommand(cli) 300 assert.NilError(t, cmd.Execute()) 301 golden.Assert(t, cli.OutBuffer().String(), "container-list-with-config-format.golden") 302 } 303 304 func TestContainerListWithFormat(t *testing.T) { 305 cli := test.NewFakeCli(&fakeClient{ 306 containerListFunc: func(_ container.ListOptions) ([]types.Container, error) { 307 return []types.Container{ 308 *builders.Container("c1", builders.WithLabel("some.label", "value")), 309 *builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")), 310 }, nil 311 }, 312 }) 313 314 t.Run("with format", func(t *testing.T) { 315 cli.OutBuffer().Reset() 316 cmd := newListCommand(cli) 317 assert.Check(t, cmd.Flags().Set("format", "{{ .Names }} {{ .Image }} {{ .Labels }}")) 318 assert.NilError(t, cmd.Execute()) 319 golden.Assert(t, cli.OutBuffer().String(), "container-list-with-format.golden") 320 }) 321 322 t.Run("with format and quiet", func(t *testing.T) { 323 cli.OutBuffer().Reset() 324 cmd := newListCommand(cli) 325 assert.Check(t, cmd.Flags().Set("format", "{{ .Names }} {{ .Image }} {{ .Labels }}")) 326 assert.Check(t, cmd.Flags().Set("quiet", "true")) 327 assert.NilError(t, cmd.Execute()) 328 assert.Equal(t, cli.ErrBuffer().String(), "WARNING: Ignoring custom format, because both --format and --quiet are set.\n") 329 golden.Assert(t, cli.OutBuffer().String(), "container-list-quiet.golden") 330 }) 331 }