github.com/moby/docker@v26.1.3+incompatible/volume/mounts/windows_parser_test.go (about)

     1  package mounts // import "github.com/docker/docker/volume/mounts"
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/docker/docker/api/types/mount"
     9  	"gotest.tools/v3/assert"
    10  	is "gotest.tools/v3/assert/cmp"
    11  )
    12  
    13  func TestWindowsParseMountRaw(t *testing.T) {
    14  	valid := []string{
    15  		`d:\`,
    16  		`d:`,
    17  		`d:\path`,
    18  		`d:\path with space`,
    19  		`c:\:d:\`,
    20  		`c:\windows\:d:`,
    21  		`c:\windows:d:\s p a c e`,
    22  		`c:\windows:d:\s p a c e:RW`,
    23  		`c:\program files:d:\s p a c e i n h o s t d i r`,
    24  		`0123456789name:d:`,
    25  		`MiXeDcAsEnAmE:d:`,
    26  		`test-aux-volume:d:`, // includes reserved word, but is not one itself
    27  		`name:D:`,
    28  		`name:D::rW`,
    29  		`name:D::RW`,
    30  		`name:D::RO`,
    31  		`c:/:d:/forward/slashes/are/good/too`,
    32  		`c:/:d:/including with/spaces:ro`,
    33  		`c:\Windows`,                // With capital
    34  		`c:\Program Files (x86)`,    // With capitals and brackets
    35  		`\\?\c:\windows\:d:`,        // Long path handling (source)
    36  		`c:\windows\:\\?\d:\`,       // Long path handling (target)
    37  		`\\.\pipe\foo:\\.\pipe\foo`, // named pipe
    38  		`//./pipe/foo://./pipe/foo`, // named pipe forward slashes
    39  	}
    40  
    41  	invalid := map[string]string{
    42  		``:                                 "invalid volume specification: ",
    43  		`.`:                                "invalid volume specification: ",
    44  		`..\`:                              "invalid volume specification: ",
    45  		`c:\:..\`:                          "invalid volume specification: ",
    46  		`c:\:d:\:xyzzy`:                    "invalid volume specification: ",
    47  		`c:`:                               "cannot be `c:`",
    48  		`c:\`:                              "cannot be `c:`",
    49  		`c:\notexist:d:`:                   `source path does not exist: c:\notexist`,
    50  		`c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`,
    51  		`name<:d:`:                         `invalid volume specification`,
    52  		`name>:d:`:                         `invalid volume specification`,
    53  		`name::d:`:                         `invalid volume specification`,
    54  		`name":d:`:                         `invalid volume specification`,
    55  		`name\:d:`:                         `invalid volume specification`,
    56  		`name*:d:`:                         `invalid volume specification`,
    57  		`name|:d:`:                         `invalid volume specification`,
    58  		`name?:d:`:                         `invalid volume specification`,
    59  		`name/:d:`:                         `invalid volume specification`,
    60  		`d:\pathandmode:rw`:                `invalid volume specification`,
    61  		`d:\pathandmode:ro`:                `invalid volume specification`,
    62  		`con:d:`:                           `cannot be a reserved word for Windows filenames`,
    63  		`PRN:d:`:                           `cannot be a reserved word for Windows filenames`,
    64  		`aUx:d:`:                           `cannot be a reserved word for Windows filenames`,
    65  		`nul:d:`:                           `cannot be a reserved word for Windows filenames`,
    66  		`com1:d:`:                          `cannot be a reserved word for Windows filenames`,
    67  		`com2:d:`:                          `cannot be a reserved word for Windows filenames`,
    68  		`com3:d:`:                          `cannot be a reserved word for Windows filenames`,
    69  		`com4:d:`:                          `cannot be a reserved word for Windows filenames`,
    70  		`com5:d:`:                          `cannot be a reserved word for Windows filenames`,
    71  		`com6:d:`:                          `cannot be a reserved word for Windows filenames`,
    72  		`com7:d:`:                          `cannot be a reserved word for Windows filenames`,
    73  		`com8:d:`:                          `cannot be a reserved word for Windows filenames`,
    74  		`com9:d:`:                          `cannot be a reserved word for Windows filenames`,
    75  		`lpt1:d:`:                          `cannot be a reserved word for Windows filenames`,
    76  		`lpt2:d:`:                          `cannot be a reserved word for Windows filenames`,
    77  		`lpt3:d:`:                          `cannot be a reserved word for Windows filenames`,
    78  		`lpt4:d:`:                          `cannot be a reserved word for Windows filenames`,
    79  		`lpt5:d:`:                          `cannot be a reserved word for Windows filenames`,
    80  		`lpt6:d:`:                          `cannot be a reserved word for Windows filenames`,
    81  		`lpt7:d:`:                          `cannot be a reserved word for Windows filenames`,
    82  		`lpt8:d:`:                          `cannot be a reserved word for Windows filenames`,
    83  		`lpt9:d:`:                          `cannot be a reserved word for Windows filenames`,
    84  		`c:\windows\system32\ntdll.dll`:    `Only directories can be mapped on this platform`,
    85  		`\\.\pipe\foo:c:\pipe`:             `'c:\pipe' is not a valid pipe path`,
    86  	}
    87  
    88  	parser := NewWindowsParser()
    89  	if p, ok := parser.(*windowsParser); ok {
    90  		p.fi = mockFiProvider{}
    91  	}
    92  
    93  	for _, path := range valid {
    94  		if _, err := parser.ParseMountRaw(path, "local"); err != nil {
    95  			t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
    96  		}
    97  	}
    98  
    99  	for path, expectedError := range invalid {
   100  		if mp, err := parser.ParseMountRaw(path, "local"); err == nil {
   101  			t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
   102  		} else {
   103  			if !strings.Contains(err.Error(), expectedError) {
   104  				t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
   105  			}
   106  		}
   107  	}
   108  }
   109  
   110  func TestWindowsParseMountRawSplit(t *testing.T) {
   111  	cases := []struct {
   112  		bind      string
   113  		driver    string
   114  		expType   mount.Type
   115  		expDest   string
   116  		expSource string
   117  		expName   string
   118  		expDriver string
   119  		expRW     bool
   120  		fail      bool
   121  	}{
   122  		{
   123  			bind:      `c:\:d:`,
   124  			driver:    "local",
   125  			expType:   mount.TypeBind,
   126  			expDest:   `d:`,
   127  			expSource: `c:\`,
   128  			expRW:     true,
   129  		},
   130  		{
   131  			bind:      `c:\:d:\`,
   132  			driver:    "local",
   133  			expType:   mount.TypeBind,
   134  			expDest:   `d:\`,
   135  			expSource: `c:\`,
   136  			expRW:     true,
   137  		},
   138  		{
   139  			bind:      `c:\:d:\:ro`,
   140  			driver:    "local",
   141  			expType:   mount.TypeBind,
   142  			expDest:   `d:\`,
   143  			expSource: `c:\`,
   144  		},
   145  		{
   146  			bind:      `c:\:d:\:rw`,
   147  			driver:    "local",
   148  			expType:   mount.TypeBind,
   149  			expDest:   `d:\`,
   150  			expSource: `c:\`,
   151  			expRW:     true,
   152  		},
   153  		{
   154  			bind:      `c:\:d:\:foo`,
   155  			driver:    "local",
   156  			expType:   mount.TypeBind,
   157  			expDest:   `d:\`,
   158  			expSource: `c:\`,
   159  			fail:      true,
   160  		},
   161  		{
   162  			bind:      `name:d::rw`,
   163  			driver:    "local",
   164  			expType:   mount.TypeVolume,
   165  			expDest:   `d:`,
   166  			expName:   `name`,
   167  			expDriver: "local",
   168  			expRW:     true,
   169  		},
   170  		{
   171  			bind:      `name:d:`,
   172  			driver:    "local",
   173  			expType:   mount.TypeVolume,
   174  			expDest:   `d:`,
   175  			expName:   `name`,
   176  			expDriver: "local",
   177  			expRW:     true,
   178  		},
   179  		{
   180  			bind:      `name:d::ro`,
   181  			driver:    "local",
   182  			expType:   mount.TypeVolume,
   183  			expDest:   `d:`,
   184  			expName:   `name`,
   185  			expDriver: "local",
   186  		},
   187  		{
   188  			bind:    `name:c:`,
   189  			expType: mount.TypeVolume,
   190  			expRW:   true,
   191  			fail:    true,
   192  		},
   193  		{
   194  			bind:    `driver/name:c:`,
   195  			expType: mount.TypeVolume,
   196  			expRW:   true,
   197  			fail:    true,
   198  		},
   199  		{
   200  			bind:      `\\.\pipe\foo:\\.\pipe\bar`,
   201  			driver:    "local",
   202  			expType:   mount.TypeNamedPipe,
   203  			expDest:   `\\.\pipe\bar`,
   204  			expSource: `\\.\pipe\foo`,
   205  			expRW:     true,
   206  		},
   207  		{
   208  			bind:    `\\.\pipe\foo:c:\foo\bar`,
   209  			driver:  "local",
   210  			expType: mount.TypeNamedPipe,
   211  			expRW:   true,
   212  			fail:    true,
   213  		},
   214  		{
   215  			bind:    `c:\foo\bar:\\.\pipe\foo`,
   216  			driver:  "local",
   217  			expType: mount.TypeNamedPipe,
   218  			expRW:   true,
   219  			fail:    true,
   220  		},
   221  	}
   222  
   223  	parser := NewWindowsParser()
   224  	if p, ok := parser.(*windowsParser); ok {
   225  		p.fi = mockFiProvider{}
   226  	}
   227  
   228  	for _, tc := range cases {
   229  		tc := tc
   230  		t.Run(tc.bind, func(t *testing.T) {
   231  			m, err := parser.ParseMountRaw(tc.bind, tc.driver)
   232  			if tc.fail {
   233  				assert.Check(t, is.ErrorContains(err, ""), "expected an error")
   234  				return
   235  			}
   236  
   237  			assert.NilError(t, err)
   238  			assert.Check(t, is.Equal(m.Destination, tc.expDest))
   239  			assert.Check(t, is.Equal(m.Source, tc.expSource))
   240  			assert.Check(t, is.Equal(m.Name, tc.expName))
   241  			assert.Check(t, is.Equal(m.Driver, tc.expDriver))
   242  			assert.Check(t, is.Equal(m.RW, tc.expRW))
   243  			assert.Check(t, is.Equal(m.Type, tc.expType))
   244  		})
   245  	}
   246  }
   247  
   248  // TestWindowsParseMountSpecBindWithFileinfoError makes sure that the parser returns
   249  // the error produced by the fileinfo provider.
   250  //
   251  // Some extra context for the future in case of changes and possible wtf are we
   252  // testing this for:
   253  //
   254  // Currently this "fileInfoProvider" returns (bool, bool, error)
   255  // The 1st bool is "does this path exist"
   256  // The 2nd bool is "is this path a dir"
   257  // Then of course the error is an error.
   258  //
   259  // The issue is the parser was ignoring the error and only looking at the
   260  // "does this path exist" boolean, which is always false if there is an error.
   261  // Then the error returned to the caller was a (slightly, maybe) friendlier
   262  // error string than what comes from `os.Stat`
   263  // So ...the caller was always getting an error saying the path doesn't exist
   264  // even if it does exist but got some other error (like a permission error).
   265  // This is confusing to users.
   266  func TestWindowsParseMountSpecBindWithFileinfoError(t *testing.T) {
   267  	parser := NewWindowsParser()
   268  	testErr := fmt.Errorf("some crazy error")
   269  	if pr, ok := parser.(*windowsParser); ok {
   270  		pr.fi = &mockFiProviderWithError{err: testErr}
   271  	}
   272  
   273  	_, err := parser.ParseMountSpec(mount.Mount{
   274  		Type:   mount.TypeBind,
   275  		Source: `c:\bananas`,
   276  		Target: `c:\bananas`,
   277  	})
   278  	assert.ErrorContains(t, err, testErr.Error())
   279  }