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  }