volume_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package volume
  2. import (
  3. "io/ioutil"
  4. "os"
  5. "runtime"
  6. "strings"
  7. "testing"
  8. "github.com/docker/docker/api/types/mount"
  9. )
  10. func TestParseMountRaw(t *testing.T) {
  11. var (
  12. valid []string
  13. invalid map[string]string
  14. )
  15. if runtime.GOOS == "windows" {
  16. valid = []string{
  17. `d:\`,
  18. `d:`,
  19. `d:\path`,
  20. `d:\path with space`,
  21. `c:\:d:\`,
  22. `c:\windows\:d:`,
  23. `c:\windows:d:\s p a c e`,
  24. `c:\windows:d:\s p a c e:RW`,
  25. `c:\program files:d:\s p a c e i n h o s t d i r`,
  26. `0123456789name:d:`,
  27. `MiXeDcAsEnAmE:d:`,
  28. `name:D:`,
  29. `name:D::rW`,
  30. `name:D::RW`,
  31. `name:D::RO`,
  32. `c:/:d:/forward/slashes/are/good/too`,
  33. `c:/:d:/including with/spaces:ro`,
  34. `c:\Windows`, // With capital
  35. `c:\Program Files (x86)`, // With capitals and brackets
  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`,
  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. }
  82. } else {
  83. valid = []string{
  84. "/home",
  85. "/home:/home",
  86. "/home:/something/else",
  87. "/with space",
  88. "/home:/with space",
  89. "relative:/absolute-path",
  90. "hostPath:/containerPath:ro",
  91. "/hostPath:/containerPath:rw",
  92. "/rw:/ro",
  93. }
  94. invalid = map[string]string{
  95. "": "invalid volume specification",
  96. "./": "mount path must be absolute",
  97. "../": "mount path must be absolute",
  98. "/:../": "mount path must be absolute",
  99. "/:path": "mount path must be absolute",
  100. ":": "invalid volume specification",
  101. "/tmp:": "invalid volume specification",
  102. ":test": "invalid volume specification",
  103. ":/test": "invalid volume specification",
  104. "tmp:": "invalid volume specification",
  105. ":test:": "invalid volume specification",
  106. "::": "invalid volume specification",
  107. ":::": "invalid volume specification",
  108. "/tmp:::": "invalid volume specification",
  109. ":/tmp::": "invalid volume specification",
  110. "/path:rw": "invalid volume specification",
  111. "/path:ro": "invalid volume specification",
  112. "/rw:rw": "invalid volume specification",
  113. "path:ro": "invalid volume specification",
  114. "/path:/path:sw": `invalid mode`,
  115. "/path:/path:rwz": `invalid mode`,
  116. }
  117. }
  118. for _, path := range valid {
  119. if _, err := ParseMountRaw(path, "local"); err != nil {
  120. t.Fatalf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
  121. }
  122. }
  123. for path, expectedError := range invalid {
  124. if mp, err := ParseMountRaw(path, "local"); err == nil {
  125. t.Fatalf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
  126. } else {
  127. if !strings.Contains(err.Error(), expectedError) {
  128. t.Fatalf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
  129. }
  130. }
  131. }
  132. }
  133. // testParseMountRaw is a structure used by TestParseMountRawSplit for
  134. // specifying test cases for the ParseMountRaw() function.
  135. type testParseMountRaw struct {
  136. bind string
  137. driver string
  138. expDest string
  139. expSource string
  140. expName string
  141. expDriver string
  142. expRW bool
  143. fail bool
  144. }
  145. func TestParseMountRawSplit(t *testing.T) {
  146. var cases []testParseMountRaw
  147. if runtime.GOOS == "windows" {
  148. cases = []testParseMountRaw{
  149. {`c:\:d:`, "local", `d:`, `c:\`, ``, "", true, false},
  150. {`c:\:d:\`, "local", `d:\`, `c:\`, ``, "", true, false},
  151. {`c:\:d:\:ro`, "local", `d:\`, `c:\`, ``, "", false, false},
  152. {`c:\:d:\:rw`, "local", `d:\`, `c:\`, ``, "", true, false},
  153. {`c:\:d:\:foo`, "local", `d:\`, `c:\`, ``, "", false, true},
  154. {`name:d::rw`, "local", `d:`, ``, `name`, "local", true, false},
  155. {`name:d:`, "local", `d:`, ``, `name`, "local", true, false},
  156. {`name:d::ro`, "local", `d:`, ``, `name`, "local", false, false},
  157. {`name:c:`, "", ``, ``, ``, "", true, true},
  158. {`driver/name:c:`, "", ``, ``, ``, "", true, true},
  159. }
  160. } else {
  161. cases = []testParseMountRaw{
  162. {"/tmp:/tmp1", "", "/tmp1", "/tmp", "", "", true, false},
  163. {"/tmp:/tmp2:ro", "", "/tmp2", "/tmp", "", "", false, false},
  164. {"/tmp:/tmp3:rw", "", "/tmp3", "/tmp", "", "", true, false},
  165. {"/tmp:/tmp4:foo", "", "", "", "", "", false, true},
  166. {"name:/named1", "", "/named1", "", "name", "", true, false},
  167. {"name:/named2", "external", "/named2", "", "name", "external", true, false},
  168. {"name:/named3:ro", "local", "/named3", "", "name", "local", false, false},
  169. {"local/name:/tmp:rw", "", "/tmp", "", "local/name", "", true, false},
  170. {"/tmp:tmp", "", "", "", "", "", true, true},
  171. }
  172. }
  173. for i, c := range cases {
  174. t.Logf("case %d", i)
  175. m, err := ParseMountRaw(c.bind, c.driver)
  176. if c.fail {
  177. if err == nil {
  178. t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
  179. }
  180. continue
  181. }
  182. if m == nil || err != nil {
  183. t.Fatalf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
  184. continue
  185. }
  186. if m.Destination != c.expDest {
  187. t.Fatalf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
  188. }
  189. if m.Source != c.expSource {
  190. t.Fatalf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
  191. }
  192. if m.Name != c.expName {
  193. t.Fatalf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
  194. }
  195. if m.Driver != c.expDriver {
  196. t.Fatalf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
  197. }
  198. if m.RW != c.expRW {
  199. t.Fatalf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
  200. }
  201. }
  202. }
  203. func TestParseMountSpec(t *testing.T) {
  204. type c struct {
  205. input mount.Mount
  206. expected MountPoint
  207. }
  208. testDir, err := ioutil.TempDir("", "test-mount-config")
  209. if err != nil {
  210. t.Fatal(err)
  211. }
  212. defer os.RemoveAll(testDir)
  213. cases := []c{
  214. {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
  215. {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: DefaultPropagationMode}},
  216. {mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
  217. {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
  218. {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
  219. {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
  220. }
  221. for i, c := range cases {
  222. t.Logf("case %d", i)
  223. mp, err := ParseMountSpec(c.input)
  224. if err != nil {
  225. t.Fatal(err)
  226. }
  227. if c.expected.Type != mp.Type {
  228. t.Fatalf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
  229. }
  230. if c.expected.Destination != mp.Destination {
  231. t.Fatalf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
  232. }
  233. if c.expected.Source != mp.Source {
  234. t.Fatalf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
  235. }
  236. if c.expected.RW != mp.RW {
  237. t.Fatalf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
  238. }
  239. if c.expected.Propagation != mp.Propagation {
  240. t.Fatalf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
  241. }
  242. if c.expected.Driver != mp.Driver {
  243. t.Fatalf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
  244. }
  245. if c.expected.CopyData != mp.CopyData {
  246. t.Fatalf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
  247. }
  248. }
  249. }