windows_parser_test.go 9.2 KB

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