github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/volume/mounts/parser_test.go (about) 1 package mounts // import "github.com/docker/docker/volume/mounts" 2 3 import ( 4 "errors" 5 "os" 6 "runtime" 7 "strings" 8 "testing" 9 10 "github.com/docker/docker/api/types/mount" 11 "gotest.tools/v3/assert" 12 "gotest.tools/v3/assert/cmp" 13 ) 14 15 type parseMountRawTestSet struct { 16 valid []string 17 invalid map[string]string 18 } 19 20 func TestConvertTmpfsOptions(t *testing.T) { 21 type testCase struct { 22 opt mount.TmpfsOptions 23 readOnly bool 24 expectedSubstrings []string 25 unexpectedSubstrings []string 26 } 27 cases := []testCase{ 28 { 29 opt: mount.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700}, 30 readOnly: false, 31 expectedSubstrings: []string{"size=1m", "mode=700"}, 32 unexpectedSubstrings: []string{"ro"}, 33 }, 34 { 35 opt: mount.TmpfsOptions{}, 36 readOnly: true, 37 expectedSubstrings: []string{"ro"}, 38 unexpectedSubstrings: []string{}, 39 }, 40 } 41 p := &linuxParser{} 42 for _, c := range cases { 43 data, err := p.ConvertTmpfsOptions(&c.opt, c.readOnly) 44 if err != nil { 45 t.Fatalf("could not convert %+v (readOnly: %v) to string: %v", 46 c.opt, c.readOnly, err) 47 } 48 t.Logf("data=%q", data) 49 for _, s := range c.expectedSubstrings { 50 if !strings.Contains(data, s) { 51 t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c) 52 } 53 } 54 for _, s := range c.unexpectedSubstrings { 55 if strings.Contains(data, s) { 56 t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c) 57 } 58 } 59 } 60 } 61 62 type mockFiProvider struct{} 63 64 func (mockFiProvider) fileInfo(path string) (exists, isDir bool, err error) { 65 dirs := map[string]struct{}{ 66 `c:\`: {}, 67 `c:\windows\`: {}, 68 `c:\windows`: {}, 69 `c:\program files`: {}, 70 `c:\Windows`: {}, 71 `c:\Program Files (x86)`: {}, 72 `\\?\c:\windows\`: {}, 73 } 74 files := map[string]struct{}{ 75 `c:\windows\system32\ntdll.dll`: {}, 76 } 77 if _, ok := dirs[path]; ok { 78 return true, true, nil 79 } 80 if _, ok := files[path]; ok { 81 return true, false, nil 82 } 83 return false, false, nil 84 } 85 86 func TestParseMountRaw(t *testing.T) { 87 88 previousProvider := currentFileInfoProvider 89 defer func() { currentFileInfoProvider = previousProvider }() 90 currentFileInfoProvider = mockFiProvider{} 91 windowsSet := parseMountRawTestSet{ 92 valid: []string{ 93 `d:\`, 94 `d:`, 95 `d:\path`, 96 `d:\path with space`, 97 `c:\:d:\`, 98 `c:\windows\:d:`, 99 `c:\windows:d:\s p a c e`, 100 `c:\windows:d:\s p a c e:RW`, 101 `c:\program files:d:\s p a c e i n h o s t d i r`, 102 `0123456789name:d:`, 103 `MiXeDcAsEnAmE:d:`, 104 `name:D:`, 105 `name:D::rW`, 106 `name:D::RW`, 107 `name:D::RO`, 108 `c:/:d:/forward/slashes/are/good/too`, 109 `c:/:d:/including with/spaces:ro`, 110 `c:\Windows`, // With capital 111 `c:\Program Files (x86)`, // With capitals and brackets 112 `\\?\c:\windows\:d:`, // Long path handling (source) 113 `c:\windows\:\\?\d:\`, // Long path handling (target) 114 `\\.\pipe\foo:\\.\pipe\foo`, // named pipe 115 `//./pipe/foo://./pipe/foo`, // named pipe forward slashes 116 }, 117 invalid: map[string]string{ 118 ``: "invalid volume specification: ", 119 `.`: "invalid volume specification: ", 120 `..\`: "invalid volume specification: ", 121 `c:\:..\`: "invalid volume specification: ", 122 `c:\:d:\:xyzzy`: "invalid volume specification: ", 123 `c:`: "cannot be `c:`", 124 `c:\`: "cannot be `c:`", 125 `c:\notexist:d:`: `source path does not exist: c:\notexist`, 126 `c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`, 127 `name<:d:`: `invalid volume specification`, 128 `name>:d:`: `invalid volume specification`, 129 `name::d:`: `invalid volume specification`, 130 `name":d:`: `invalid volume specification`, 131 `name\:d:`: `invalid volume specification`, 132 `name*:d:`: `invalid volume specification`, 133 `name|:d:`: `invalid volume specification`, 134 `name?:d:`: `invalid volume specification`, 135 `name/:d:`: `invalid volume specification`, 136 `d:\pathandmode:rw`: `invalid volume specification`, 137 `d:\pathandmode:ro`: `invalid volume specification`, 138 `con:d:`: `cannot be a reserved word for Windows filenames`, 139 `PRN:d:`: `cannot be a reserved word for Windows filenames`, 140 `aUx:d:`: `cannot be a reserved word for Windows filenames`, 141 `nul:d:`: `cannot be a reserved word for Windows filenames`, 142 `com1:d:`: `cannot be a reserved word for Windows filenames`, 143 `com2:d:`: `cannot be a reserved word for Windows filenames`, 144 `com3:d:`: `cannot be a reserved word for Windows filenames`, 145 `com4:d:`: `cannot be a reserved word for Windows filenames`, 146 `com5:d:`: `cannot be a reserved word for Windows filenames`, 147 `com6:d:`: `cannot be a reserved word for Windows filenames`, 148 `com7:d:`: `cannot be a reserved word for Windows filenames`, 149 `com8:d:`: `cannot be a reserved word for Windows filenames`, 150 `com9:d:`: `cannot be a reserved word for Windows filenames`, 151 `lpt1:d:`: `cannot be a reserved word for Windows filenames`, 152 `lpt2:d:`: `cannot be a reserved word for Windows filenames`, 153 `lpt3:d:`: `cannot be a reserved word for Windows filenames`, 154 `lpt4:d:`: `cannot be a reserved word for Windows filenames`, 155 `lpt5:d:`: `cannot be a reserved word for Windows filenames`, 156 `lpt6:d:`: `cannot be a reserved word for Windows filenames`, 157 `lpt7:d:`: `cannot be a reserved word for Windows filenames`, 158 `lpt8:d:`: `cannot be a reserved word for Windows filenames`, 159 `lpt9:d:`: `cannot be a reserved word for Windows filenames`, 160 `c:\windows\system32\ntdll.dll`: `Only directories can be mapped on this platform`, 161 `\\.\pipe\foo:c:\pipe`: `'c:\pipe' is not a valid pipe path`, 162 }, 163 } 164 lcowSet := parseMountRawTestSet{ 165 valid: []string{ 166 `/foo`, 167 `/foo/`, 168 `/foo bar`, 169 `c:\:/foo`, 170 `c:\windows\:/foo`, 171 `c:\windows:/s p a c e`, 172 `c:\windows:/s p a c e:RW`, 173 `c:\program files:/s p a c e i n h o s t d i r`, 174 `0123456789name:/foo`, 175 `MiXeDcAsEnAmE:/foo`, 176 `name:/foo`, 177 `name:/foo:rW`, 178 `name:/foo:RW`, 179 `name:/foo:RO`, 180 `c:/:/forward/slashes/are/good/too`, 181 `c:/:/including with/spaces:ro`, 182 `/Program Files (x86)`, // With capitals and brackets 183 }, 184 invalid: map[string]string{ 185 ``: "invalid volume specification: ", 186 `.`: "invalid volume specification: ", 187 `c:`: "invalid volume specification: ", 188 `c:\`: "invalid volume specification: ", 189 `../`: "invalid volume specification: ", 190 `c:\:../`: "invalid volume specification: ", 191 `c:\:/foo:xyzzy`: "invalid volume specification: ", 192 `/`: "destination can't be '/'", 193 `/..`: "destination can't be '/'", 194 `c:\notexist:/foo`: `source path does not exist: c:\notexist`, 195 `c:\windows\system32\ntdll.dll:/foo`: `source path must be a directory`, 196 `name<:/foo`: `invalid volume specification`, 197 `name>:/foo`: `invalid volume specification`, 198 `name::/foo`: `invalid volume specification`, 199 `name":/foo`: `invalid volume specification`, 200 `name\:/foo`: `invalid volume specification`, 201 `name*:/foo`: `invalid volume specification`, 202 `name|:/foo`: `invalid volume specification`, 203 `name?:/foo`: `invalid volume specification`, 204 `name/:/foo`: `invalid volume specification`, 205 `/foo:rw`: `invalid volume specification`, 206 `/foo:ro`: `invalid volume specification`, 207 `con:/foo`: `cannot be a reserved word for Windows filenames`, 208 `PRN:/foo`: `cannot be a reserved word for Windows filenames`, 209 `aUx:/foo`: `cannot be a reserved word for Windows filenames`, 210 `nul:/foo`: `cannot be a reserved word for Windows filenames`, 211 `com1:/foo`: `cannot be a reserved word for Windows filenames`, 212 `com2:/foo`: `cannot be a reserved word for Windows filenames`, 213 `com3:/foo`: `cannot be a reserved word for Windows filenames`, 214 `com4:/foo`: `cannot be a reserved word for Windows filenames`, 215 `com5:/foo`: `cannot be a reserved word for Windows filenames`, 216 `com6:/foo`: `cannot be a reserved word for Windows filenames`, 217 `com7:/foo`: `cannot be a reserved word for Windows filenames`, 218 `com8:/foo`: `cannot be a reserved word for Windows filenames`, 219 `com9:/foo`: `cannot be a reserved word for Windows filenames`, 220 `lpt1:/foo`: `cannot be a reserved word for Windows filenames`, 221 `lpt2:/foo`: `cannot be a reserved word for Windows filenames`, 222 `lpt3:/foo`: `cannot be a reserved word for Windows filenames`, 223 `lpt4:/foo`: `cannot be a reserved word for Windows filenames`, 224 `lpt5:/foo`: `cannot be a reserved word for Windows filenames`, 225 `lpt6:/foo`: `cannot be a reserved word for Windows filenames`, 226 `lpt7:/foo`: `cannot be a reserved word for Windows filenames`, 227 `lpt8:/foo`: `cannot be a reserved word for Windows filenames`, 228 `lpt9:/foo`: `cannot be a reserved word for Windows filenames`, 229 `\\.\pipe\foo:/foo`: `Linux containers on Windows do not support named pipe mounts`, 230 }, 231 } 232 linuxSet := parseMountRawTestSet{ 233 valid: []string{ 234 "/home", 235 "/home:/home", 236 "/home:/something/else", 237 "/with space", 238 "/home:/with space", 239 "relative:/absolute-path", 240 "hostPath:/containerPath:ro", 241 "/hostPath:/containerPath:rw", 242 "/rw:/ro", 243 "/hostPath:/containerPath:shared", 244 "/hostPath:/containerPath:rshared", 245 "/hostPath:/containerPath:slave", 246 "/hostPath:/containerPath:rslave", 247 "/hostPath:/containerPath:private", 248 "/hostPath:/containerPath:rprivate", 249 "/hostPath:/containerPath:ro,shared", 250 "/hostPath:/containerPath:ro,slave", 251 "/hostPath:/containerPath:ro,private", 252 "/hostPath:/containerPath:ro,z,shared", 253 "/hostPath:/containerPath:ro,Z,slave", 254 "/hostPath:/containerPath:Z,ro,slave", 255 "/hostPath:/containerPath:slave,Z,ro", 256 "/hostPath:/containerPath:Z,slave,ro", 257 "/hostPath:/containerPath:slave,ro,Z", 258 "/hostPath:/containerPath:rslave,ro,Z", 259 "/hostPath:/containerPath:ro,rshared,Z", 260 "/hostPath:/containerPath:ro,Z,rprivate", 261 }, 262 invalid: map[string]string{ 263 "": "invalid volume specification", 264 "./": "mount path must be absolute", 265 "../": "mount path must be absolute", 266 "/:../": "mount path must be absolute", 267 "/:path": "mount path must be absolute", 268 ":": "invalid volume specification", 269 "/tmp:": "invalid volume specification", 270 ":test": "invalid volume specification", 271 ":/test": "invalid volume specification", 272 "tmp:": "invalid volume specification", 273 ":test:": "invalid volume specification", 274 "::": "invalid volume specification", 275 ":::": "invalid volume specification", 276 "/tmp:::": "invalid volume specification", 277 ":/tmp::": "invalid volume specification", 278 "/path:rw": "invalid volume specification", 279 "/path:ro": "invalid volume specification", 280 "/rw:rw": "invalid volume specification", 281 "path:ro": "invalid volume specification", 282 "/path:/path:sw": `invalid mode`, 283 "/path:/path:rwz": `invalid mode`, 284 "/path:/path:ro,rshared,rslave": `invalid mode`, 285 "/path:/path:ro,z,rshared,rslave": `invalid mode`, 286 "/path:shared": "invalid volume specification", 287 "/path:slave": "invalid volume specification", 288 "/path:private": "invalid volume specification", 289 "name:/absolute-path:shared": "invalid volume specification", 290 "name:/absolute-path:rshared": "invalid volume specification", 291 "name:/absolute-path:slave": "invalid volume specification", 292 "name:/absolute-path:rslave": "invalid volume specification", 293 "name:/absolute-path:private": "invalid volume specification", 294 "name:/absolute-path:rprivate": "invalid volume specification", 295 }, 296 } 297 298 linParser := &linuxParser{} 299 winParser := &windowsParser{} 300 lcowParser := &lcowParser{} 301 tester := func(parser Parser, set parseMountRawTestSet) { 302 303 for _, path := range set.valid { 304 305 if _, err := parser.ParseMountRaw(path, "local"); err != nil { 306 t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err) 307 } 308 } 309 310 for path, expectedError := range set.invalid { 311 if mp, err := parser.ParseMountRaw(path, "local"); err == nil { 312 t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp) 313 } else { 314 if !strings.Contains(err.Error(), expectedError) { 315 t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error()) 316 } 317 } 318 } 319 } 320 tester(linParser, linuxSet) 321 tester(winParser, windowsSet) 322 tester(lcowParser, lcowSet) 323 324 } 325 326 // testParseMountRaw is a structure used by TestParseMountRawSplit for 327 // specifying test cases for the ParseMountRaw() function. 328 type testParseMountRaw struct { 329 bind string 330 driver string 331 expType mount.Type 332 expDest string 333 expSource string 334 expName string 335 expDriver string 336 expRW bool 337 fail bool 338 } 339 340 func TestParseMountRawSplit(t *testing.T) { 341 previousProvider := currentFileInfoProvider 342 defer func() { currentFileInfoProvider = previousProvider }() 343 currentFileInfoProvider = mockFiProvider{} 344 windowsCases := []testParseMountRaw{ 345 {`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false}, 346 {`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false}, 347 {`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false}, 348 {`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false}, 349 {`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true}, 350 {`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false}, 351 {`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false}, 352 {`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false}, 353 {`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, 354 {`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, 355 {`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false}, 356 {`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, 357 {`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, 358 } 359 lcowCases := []testParseMountRaw{ 360 {`c:\:/foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false}, 361 {`c:\:/foo:ro`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, false}, 362 {`c:\:/foo:rw`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false}, 363 {`c:\:/foo:foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, true}, 364 {`name:/foo:rw`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false}, 365 {`name:/foo`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false}, 366 {`name:/foo:ro`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", false, false}, 367 {`name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, 368 {`driver/name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, 369 {`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, true}, 370 {`\\.\pipe\foo:/data`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, 371 {`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, 372 } 373 linuxCases := []testParseMountRaw{ 374 {"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false}, 375 {"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false}, 376 {"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false}, 377 {"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true}, 378 {"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false}, 379 {"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false}, 380 {"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false}, 381 {"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false}, 382 {"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true}, 383 } 384 linParser := &linuxParser{} 385 winParser := &windowsParser{} 386 lcowParser := &lcowParser{} 387 tester := func(parser Parser, cases []testParseMountRaw) { 388 for i, c := range cases { 389 t.Logf("case %d", i) 390 m, err := parser.ParseMountRaw(c.bind, c.driver) 391 if c.fail { 392 if err == nil { 393 t.Errorf("Expected error, was nil, for spec %s\n", c.bind) 394 } 395 continue 396 } 397 398 if m == nil || err != nil { 399 t.Errorf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error()) 400 continue 401 } 402 403 if m.Destination != c.expDest { 404 t.Errorf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind) 405 } 406 407 if m.Source != c.expSource { 408 t.Errorf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind) 409 } 410 411 if m.Name != c.expName { 412 t.Errorf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind) 413 } 414 415 if m.Driver != c.expDriver { 416 t.Errorf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind) 417 } 418 419 if m.RW != c.expRW { 420 t.Errorf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind) 421 } 422 if m.Type != c.expType { 423 t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind) 424 } 425 } 426 } 427 428 tester(linParser, linuxCases) 429 tester(winParser, windowsCases) 430 tester(lcowParser, lcowCases) 431 } 432 433 func TestParseMountSpec(t *testing.T) { 434 type c struct { 435 input mount.Mount 436 expected MountPoint 437 } 438 testDir, err := os.MkdirTemp("", "test-mount-config") 439 if err != nil { 440 t.Fatal(err) 441 } 442 defer os.RemoveAll(testDir) 443 parser := NewParser(runtime.GOOS) 444 cases := []c{ 445 {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}}, 446 {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: parser.DefaultPropagationMode()}}, 447 {mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}}, 448 {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}}, 449 {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}}, 450 {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}}, 451 } 452 453 for i, c := range cases { 454 t.Logf("case %d", i) 455 mp, err := parser.ParseMountSpec(c.input) 456 if err != nil { 457 t.Error(err) 458 } 459 460 if c.expected.Type != mp.Type { 461 t.Errorf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type) 462 } 463 if c.expected.Destination != mp.Destination { 464 t.Errorf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination) 465 } 466 if c.expected.Source != mp.Source { 467 t.Errorf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source) 468 } 469 if c.expected.RW != mp.RW { 470 t.Errorf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW) 471 } 472 if c.expected.Propagation != mp.Propagation { 473 t.Errorf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation) 474 } 475 if c.expected.Driver != mp.Driver { 476 t.Errorf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver) 477 } 478 if c.expected.CopyData != mp.CopyData { 479 t.Errorf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData) 480 } 481 } 482 483 } 484 485 // always returns the configured error 486 // this is used to test error handling 487 type mockFiProviderWithError struct{ err error } 488 489 func (m mockFiProviderWithError) fileInfo(path string) (bool, bool, error) { 490 return false, false, m.err 491 } 492 493 // TestParseMountSpecBindWithFileinfoError makes sure that the parser returns 494 // the error produced by the fileinfo provider. 495 // 496 // Some extra context for the future in case of changes and possible wtf are we 497 // testing this for: 498 // 499 // Currently this "fileInfoProvider" returns (bool, bool, error) 500 // The 1st bool is "does this path exist" 501 // The 2nd bool is "is this path a dir" 502 // Then of course the error is an error. 503 // 504 // The issue is the parser was ignoring the error and only looking at the 505 // "does this path exist" boolean, which is always false if there is an error. 506 // Then the error returned to the caller was a (slightly, maybe) friendlier 507 // error string than what comes from `os.Stat` 508 // So ...the caller was always getting an error saying the path doesn't exist 509 // even if it does exist but got some other error (like a permission error). 510 // This is confusing to users. 511 func TestParseMountSpecBindWithFileinfoError(t *testing.T) { 512 previousProvider := currentFileInfoProvider 513 defer func() { currentFileInfoProvider = previousProvider }() 514 515 testErr := errors.New("some crazy error") 516 currentFileInfoProvider = &mockFiProviderWithError{err: testErr} 517 518 p := "/bananas" 519 if runtime.GOOS == "windows" { 520 p = `c:\bananas` 521 } 522 m := mount.Mount{Type: mount.TypeBind, Source: p, Target: p} 523 524 parser := NewParser(runtime.GOOS) 525 526 _, err := parser.ParseMountSpec(m) 527 assert.Assert(t, err != nil) 528 assert.Assert(t, cmp.Contains(err.Error(), "some crazy error")) 529 }