Browse code

Add decodeContainerConfig test removed from docker/cli

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2017/08/30 03:32:59
Showing 3 changed files
... ...
@@ -7,7 +7,7 @@ import (
7 7
 
8 8
 // ErrorContains checks that the error is not nil, and contains the expected
9 9
 // substring.
10
-func ErrorContains(t require.TestingT, err error, expectedError string) {
11
-	require.Error(t, err)
12
-	assert.Contains(t, err.Error(), expectedError)
10
+func ErrorContains(t require.TestingT, err error, expectedError string, msgAndArgs ...interface{}) {
11
+	require.Error(t, err, msgAndArgs...)
12
+	assert.Contains(t, err.Error(), expectedError, msgAndArgs...)
13 13
 }
... ...
@@ -17,20 +17,12 @@ type ContainerDecoder struct{}
17 17
 
18 18
 // DecodeConfig makes ContainerDecoder to implement httputils.ContainerDecoder
19 19
 func (r ContainerDecoder) DecodeConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
20
-	c, hc, nc, err := decodeContainerConfig(src)
21
-	if err != nil {
22
-		return nil, nil, nil, err
23
-	}
24
-	return c, hc, nc, nil
20
+	return decodeContainerConfig(src)
25 21
 }
26 22
 
27 23
 // DecodeHostConfig makes ContainerDecoder to implement httputils.ContainerDecoder
28 24
 func (r ContainerDecoder) DecodeHostConfig(src io.Reader) (*container.HostConfig, error) {
29
-	hc, err := decodeHostConfig(src)
30
-	if err != nil {
31
-		return nil, err
32
-	}
33
-	return hc, nil
25
+	return decodeHostConfig(src)
34 26
 }
35 27
 
36 28
 // decodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
... ...
@@ -9,9 +9,14 @@ import (
9 9
 	"strings"
10 10
 	"testing"
11 11
 
12
+	"os"
13
+
12 14
 	"github.com/docker/docker/api/types/container"
13 15
 	networktypes "github.com/docker/docker/api/types/network"
14 16
 	"github.com/docker/docker/api/types/strslice"
17
+	"github.com/gotestyourself/gotestyourself/skip"
18
+	"github.com/stretchr/testify/assert"
19
+	"github.com/stretchr/testify/require"
15 20
 )
16 21
 
17 22
 type f struct {
... ...
@@ -137,3 +142,237 @@ func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *c
137 137
 	}
138 138
 	return decodeContainerConfig(bytes.NewReader(b))
139 139
 }
140
+
141
+func TestDecodeContainerConfigWithVolumes(t *testing.T) {
142
+	var testcases = []decodeConfigTestcase{
143
+		{
144
+			doc:         "no paths volume",
145
+			wrapper:     containerWrapperWithVolume(":"),
146
+			expectedErr: `invalid volume specification: ':'`,
147
+		},
148
+		{
149
+			doc:         "no paths bind",
150
+			wrapper:     containerWrapperWithBind(":"),
151
+			expectedErr: `invalid volume specification: ':'`,
152
+		},
153
+		{
154
+			doc:         "no paths or mode volume",
155
+			wrapper:     containerWrapperWithVolume("::"),
156
+			expectedErr: `invalid volume specification: '::'`,
157
+		},
158
+		{
159
+			doc:         "no paths or mode bind",
160
+			wrapper:     containerWrapperWithBind("::"),
161
+			expectedErr: `invalid volume specification: '::'`,
162
+		},
163
+	}
164
+	for _, testcase := range testcases {
165
+		t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase))
166
+	}
167
+}
168
+
169
+func TestDecodeContainerConfigWithVolumesUnix(t *testing.T) {
170
+	skip.IfCondition(t, runtime.GOOS == "windows")
171
+
172
+	baseErr := `invalid mount config for type "volume": invalid specification: `
173
+	var testcases = []decodeConfigTestcase{
174
+		{
175
+			doc:         "root to root volume",
176
+			wrapper:     containerWrapperWithVolume("/:/"),
177
+			expectedErr: `invalid volume specification: '/:/'`,
178
+		},
179
+		{
180
+			doc:         "root to root bind",
181
+			wrapper:     containerWrapperWithBind("/:/"),
182
+			expectedErr: `invalid volume specification: '/:/'`,
183
+		},
184
+		{
185
+			doc:         "no destination path volume",
186
+			wrapper:     containerWrapperWithVolume(`/tmp:`),
187
+			expectedErr: ` invalid volume specification: '/tmp:'`,
188
+		},
189
+		{
190
+			doc:         "no destination path bind",
191
+			wrapper:     containerWrapperWithBind(`/tmp:`),
192
+			expectedErr: ` invalid volume specification: '/tmp:'`,
193
+		},
194
+		{
195
+			doc:         "no destination path or mode volume",
196
+			wrapper:     containerWrapperWithVolume(`/tmp::`),
197
+			expectedErr: `invalid mount config for type "bind": field Target must not be empty`,
198
+		},
199
+		{
200
+			doc:         "no destination path or mode bind",
201
+			wrapper:     containerWrapperWithBind(`/tmp::`),
202
+			expectedErr: `invalid mount config for type "bind": field Target must not be empty`,
203
+		},
204
+		{
205
+			doc:         "too many sections volume",
206
+			wrapper:     containerWrapperWithVolume(`/tmp:/tmp:/tmp:/tmp`),
207
+			expectedErr: `invalid volume specification: '/tmp:/tmp:/tmp:/tmp'`,
208
+		},
209
+		{
210
+			doc:         "too many sections bind",
211
+			wrapper:     containerWrapperWithBind(`/tmp:/tmp:/tmp:/tmp`),
212
+			expectedErr: `invalid volume specification: '/tmp:/tmp:/tmp:/tmp'`,
213
+		},
214
+		{
215
+			doc:         "just root volume",
216
+			wrapper:     containerWrapperWithVolume("/"),
217
+			expectedErr: baseErr + `destination can't be '/'`,
218
+		},
219
+		{
220
+			doc:         "just root bind",
221
+			wrapper:     containerWrapperWithBind("/"),
222
+			expectedErr: baseErr + `destination can't be '/'`,
223
+		},
224
+		{
225
+			doc:     "bind mount passed as a volume",
226
+			wrapper: containerWrapperWithVolume(`/foo:/bar`),
227
+			expectedConfig: &container.Config{
228
+				Volumes: map[string]struct{}{`/foo:/bar`: {}},
229
+			},
230
+			expectedHostConfig: &container.HostConfig{NetworkMode: "default"},
231
+		},
232
+	}
233
+	for _, testcase := range testcases {
234
+		t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase))
235
+	}
236
+}
237
+
238
+type decodeConfigTestcase struct {
239
+	doc                string
240
+	wrapper            ContainerConfigWrapper
241
+	expectedErr        string
242
+	expectedConfig     *container.Config
243
+	expectedHostConfig *container.HostConfig
244
+	goos               string
245
+}
246
+
247
+func runDecodeContainerConfigTestCase(testcase decodeConfigTestcase) func(t *testing.T) {
248
+	return func(t *testing.T) {
249
+		raw := marshal(t, testcase.wrapper, testcase.doc)
250
+		config, hostConfig, _, err := decodeContainerConfig(bytes.NewReader(raw))
251
+		if testcase.expectedErr != "" {
252
+			if !assert.Error(t, err) {
253
+				return
254
+			}
255
+			assert.Contains(t, err.Error(), testcase.expectedErr)
256
+			return
257
+		}
258
+		assert.NoError(t, err)
259
+		assert.Equal(t, testcase.expectedConfig, config)
260
+		assert.Equal(t, testcase.expectedHostConfig, hostConfig)
261
+	}
262
+}
263
+
264
+func TestDecodeContainerConfigWithVolumesWindows(t *testing.T) {
265
+	skip.IfCondition(t, runtime.GOOS != "windows")
266
+
267
+	tmpDir := os.Getenv("TEMP")
268
+	systemDrive := os.Getenv("SystemDrive")
269
+	var testcases = []decodeConfigTestcase{
270
+		{
271
+			doc:         "root to root volume",
272
+			wrapper:     containerWrapperWithVolume(systemDrive + `\:c:\`),
273
+			expectedErr: `invalid volume specification: `,
274
+		},
275
+		{
276
+			doc:         "root to root bind",
277
+			wrapper:     containerWrapperWithBind(systemDrive + `\:c:\`),
278
+			expectedErr: `invalid volume specification: `,
279
+		},
280
+		{
281
+			doc:         "no destination path volume",
282
+			wrapper:     containerWrapperWithVolume(tmpDir + `\:`),
283
+			expectedErr: `invalid volume specification: `,
284
+		},
285
+		{
286
+			doc:         "no destination path bind",
287
+			wrapper:     containerWrapperWithBind(tmpDir + `\:`),
288
+			expectedErr: `invalid volume specification: `,
289
+		},
290
+		{
291
+			doc:         "no destination path or mode volume",
292
+			wrapper:     containerWrapperWithVolume(tmpDir + `\::`),
293
+			expectedErr: `invalid volume specification: `,
294
+		},
295
+		{
296
+			doc:         "no destination path or mode bind",
297
+			wrapper:     containerWrapperWithBind(tmpDir + `\::`),
298
+			expectedErr: `invalid volume specification: `,
299
+		},
300
+		{
301
+			doc:         "too many sections volume",
302
+			wrapper:     containerWrapperWithVolume(tmpDir + ":" + tmpDir + ":" + tmpDir + ":" + tmpDir),
303
+			expectedErr: `invalid volume specification: `,
304
+		},
305
+		{
306
+			doc:         "too many sections bind",
307
+			wrapper:     containerWrapperWithBind(tmpDir + ":" + tmpDir + ":" + tmpDir + ":" + tmpDir),
308
+			expectedErr: `invalid volume specification: `,
309
+		},
310
+		{
311
+			doc:         "no drive letter volume",
312
+			wrapper:     containerWrapperWithVolume(`\tmp`),
313
+			expectedErr: `invalid volume specification: `,
314
+		},
315
+		{
316
+			doc:         "no drive letter bind",
317
+			wrapper:     containerWrapperWithBind(`\tmp`),
318
+			expectedErr: `invalid volume specification: `,
319
+		},
320
+		{
321
+			doc:         "root to c-drive volume",
322
+			wrapper:     containerWrapperWithVolume(systemDrive + `\:c:`),
323
+			expectedErr: `invalid volume specification: `,
324
+		},
325
+		{
326
+			doc:         "root to c-drive bind",
327
+			wrapper:     containerWrapperWithBind(systemDrive + `\:c:`),
328
+			expectedErr: `invalid volume specification: `,
329
+		},
330
+		{
331
+			doc:         "container path without driver letter volume",
332
+			wrapper:     containerWrapperWithVolume(`c:\windows:\somewhere`),
333
+			expectedErr: `invalid volume specification: `,
334
+		},
335
+		{
336
+			doc:         "container path without driver letter bind",
337
+			wrapper:     containerWrapperWithBind(`c:\windows:\somewhere`),
338
+			expectedErr: `invalid volume specification: `,
339
+		},
340
+	}
341
+
342
+	for _, testcase := range testcases {
343
+		t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase))
344
+	}
345
+}
346
+
347
+func marshal(t *testing.T, w ContainerConfigWrapper, doc string) []byte {
348
+	b, err := json.Marshal(w)
349
+	require.NoError(t, err, "%s: failed to encode config wrapper", doc)
350
+	return b
351
+}
352
+
353
+func containerWrapperWithVolume(volume string) ContainerConfigWrapper {
354
+	return ContainerConfigWrapper{
355
+		Config: &container.Config{
356
+			Volumes: map[string]struct{}{
357
+				volume: {},
358
+			},
359
+		},
360
+		HostConfig: &container.HostConfig{},
361
+	}
362
+}
363
+
364
+func containerWrapperWithBind(bind string) ContainerConfigWrapper {
365
+	return ContainerConfigWrapper{
366
+		Config: &container.Config{
367
+			Volumes: map[string]struct{}{},
368
+		},
369
+		HostConfig: &container.HostConfig{
370
+			Binds: []string{bind},
371
+		},
372
+	}
373
+}