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