github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/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 ) 11 12 func TestWindowsParseMountRaw(t *testing.T) { 13 valid := []string{ 14 `d:\`, 15 `d:`, 16 `d:\path`, 17 `d:\path with space`, 18 `c:\:d:\`, 19 `c:\windows\:d:`, 20 `c:\windows:d:\s p a c e`, 21 `c:\windows:d:\s p a c e:RW`, 22 `c:\program files:d:\s p a c e i n h o s t d i r`, 23 `0123456789name:d:`, 24 `MiXeDcAsEnAmE:d:`, 25 `test-aux-volume:d:`, // includes reserved word, but is not one itself 26 `name:D:`, 27 `name:D::rW`, 28 `name:D::RW`, 29 `name:D::RO`, 30 `c:/:d:/forward/slashes/are/good/too`, 31 `c:/:d:/including with/spaces:ro`, 32 `c:\Windows`, // With capital 33 `c:\Program Files (x86)`, // With capitals and brackets 34 `\\?\c:\windows\:d:`, // Long path handling (source) 35 `c:\windows\:\\?\d:\`, // Long path handling (target) 36 `\\.\pipe\foo:\\.\pipe\foo`, // named pipe 37 `//./pipe/foo://./pipe/foo`, // named pipe forward slashes 38 } 39 40 invalid := map[string]string{ 41 ``: "invalid volume specification: ", 42 `.`: "invalid volume specification: ", 43 `..\`: "invalid volume specification: ", 44 `c:\:..\`: "invalid volume specification: ", 45 `c:\:d:\:xyzzy`: "invalid volume specification: ", 46 `c:`: "cannot be `c:`", 47 `c:\`: "cannot be `c:`", 48 `c:\notexist:d:`: `source path does not exist: c:\notexist`, 49 `c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`, 50 `name<:d:`: `invalid volume specification`, 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 `d:\pathandmode:rw`: `invalid volume specification`, 60 `d:\pathandmode:ro`: `invalid volume specification`, 61 `con:d:`: `cannot be a reserved word for Windows filenames`, 62 `PRN:d:`: `cannot be a reserved word for Windows filenames`, 63 `aUx:d:`: `cannot be a reserved word for Windows filenames`, 64 `nul:d:`: `cannot be a reserved word for Windows filenames`, 65 `com1:d:`: `cannot be a reserved word for Windows filenames`, 66 `com2:d:`: `cannot be a reserved word for Windows filenames`, 67 `com3:d:`: `cannot be a reserved word for Windows filenames`, 68 `com4:d:`: `cannot be a reserved word for Windows filenames`, 69 `com5:d:`: `cannot be a reserved word for Windows filenames`, 70 `com6:d:`: `cannot be a reserved word for Windows filenames`, 71 `com7:d:`: `cannot be a reserved word for Windows filenames`, 72 `com8:d:`: `cannot be a reserved word for Windows filenames`, 73 `com9:d:`: `cannot be a reserved word for Windows filenames`, 74 `lpt1:d:`: `cannot be a reserved word for Windows filenames`, 75 `lpt2:d:`: `cannot be a reserved word for Windows filenames`, 76 `lpt3:d:`: `cannot be a reserved word for Windows filenames`, 77 `lpt4:d:`: `cannot be a reserved word for Windows filenames`, 78 `lpt5:d:`: `cannot be a reserved word for Windows filenames`, 79 `lpt6:d:`: `cannot be a reserved word for Windows filenames`, 80 `lpt7:d:`: `cannot be a reserved word for Windows filenames`, 81 `lpt8:d:`: `cannot be a reserved word for Windows filenames`, 82 `lpt9:d:`: `cannot be a reserved word for Windows filenames`, 83 `c:\windows\system32\ntdll.dll`: `Only directories can be mapped on this platform`, 84 `\\.\pipe\foo:c:\pipe`: `'c:\pipe' is not a valid pipe path`, 85 } 86 87 parser := NewWindowsParser() 88 if p, ok := parser.(*windowsParser); ok { 89 p.fi = mockFiProvider{} 90 } 91 92 for _, path := range valid { 93 if _, err := parser.ParseMountRaw(path, "local"); err != nil { 94 t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err) 95 } 96 } 97 98 for path, expectedError := range invalid { 99 if mp, err := parser.ParseMountRaw(path, "local"); err == nil { 100 t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp) 101 } else { 102 if !strings.Contains(err.Error(), expectedError) { 103 t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error()) 104 } 105 } 106 } 107 } 108 109 func TestWindowsParseMountRawSplit(t *testing.T) { 110 cases := []struct { 111 bind string 112 driver string 113 expType mount.Type 114 expDest string 115 expSource string 116 expName string 117 expDriver string 118 expRW bool 119 fail bool 120 }{ 121 {`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false}, 122 {`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false}, 123 {`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false}, 124 {`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false}, 125 {`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true}, 126 {`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false}, 127 {`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false}, 128 {`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false}, 129 {`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, 130 {`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, 131 {`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false}, 132 {`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, 133 {`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, 134 } 135 136 parser := NewWindowsParser() 137 if p, ok := parser.(*windowsParser); ok { 138 p.fi = mockFiProvider{} 139 } 140 141 for i, c := range cases { 142 c := c 143 t.Run(fmt.Sprintf("%d_%s", i, c.bind), func(t *testing.T) { 144 m, err := parser.ParseMountRaw(c.bind, c.driver) 145 if c.fail { 146 assert.ErrorContains(t, err, "", "expected an error") 147 return 148 } 149 150 assert.NilError(t, err) 151 assert.Equal(t, m.Destination, c.expDest) 152 assert.Equal(t, m.Source, c.expSource) 153 assert.Equal(t, m.Name, c.expName) 154 assert.Equal(t, m.Driver, c.expDriver) 155 assert.Equal(t, m.RW, c.expRW) 156 assert.Equal(t, m.Type, c.expType) 157 }) 158 } 159 } 160 161 // TestWindowsParseMountSpecBindWithFileinfoError makes sure that the parser returns 162 // the error produced by the fileinfo provider. 163 // 164 // Some extra context for the future in case of changes and possible wtf are we 165 // testing this for: 166 // 167 // Currently this "fileInfoProvider" returns (bool, bool, error) 168 // The 1st bool is "does this path exist" 169 // The 2nd bool is "is this path a dir" 170 // Then of course the error is an error. 171 // 172 // The issue is the parser was ignoring the error and only looking at the 173 // "does this path exist" boolean, which is always false if there is an error. 174 // Then the error returned to the caller was a (slightly, maybe) friendlier 175 // error string than what comes from `os.Stat` 176 // So ...the caller was always getting an error saying the path doesn't exist 177 // even if it does exist but got some other error (like a permission error). 178 // This is confusing to users. 179 func TestWindowsParseMountSpecBindWithFileinfoError(t *testing.T) { 180 parser := NewWindowsParser() 181 testErr := fmt.Errorf("some crazy error") 182 if pr, ok := parser.(*windowsParser); ok { 183 pr.fi = &mockFiProviderWithError{err: testErr} 184 } 185 186 _, err := parser.ParseMountSpec(mount.Mount{ 187 Type: mount.TypeBind, 188 Source: `c:\bananas`, 189 Target: `c:\bananas`, 190 }) 191 assert.ErrorContains(t, err, testErr.Error()) 192 }