|
@@ -1,12 +1,16 @@
|
|
|
package volume
|
|
|
|
|
|
import (
|
|
|
+ "io/ioutil"
|
|
|
+ "os"
|
|
|
"runtime"
|
|
|
"strings"
|
|
|
"testing"
|
|
|
+
|
|
|
+ "github.com/docker/docker/api/types/mount"
|
|
|
)
|
|
|
|
|
|
-func TestParseMountSpec(t *testing.T) {
|
|
|
+func TestParseMountRaw(t *testing.T) {
|
|
|
var (
|
|
|
valid []string
|
|
|
invalid map[string]string
|
|
@@ -36,25 +40,25 @@ func TestParseMountSpec(t *testing.T) {
|
|
|
`c:\Program Files (x86)`, // With capitals and brackets
|
|
|
}
|
|
|
invalid = map[string]string{
|
|
|
- ``: "Invalid volume specification: ",
|
|
|
- `.`: "Invalid volume specification: ",
|
|
|
- `..\`: "Invalid volume specification: ",
|
|
|
- `c:\:..\`: "Invalid volume specification: ",
|
|
|
- `c:\:d:\:xyzzy`: "Invalid volume specification: ",
|
|
|
- `c:`: "cannot be c:",
|
|
|
- `c:\`: `cannot be c:\`,
|
|
|
- `c:\notexist:d:`: `The system cannot find the file specified`,
|
|
|
- `c:\windows\system32\ntdll.dll:d:`: `Source 'c:\windows\system32\ntdll.dll' is not a directory`,
|
|
|
- `name<:d:`: `Invalid volume specification`,
|
|
|
- `name>:d:`: `Invalid volume specification`,
|
|
|
- `name::d:`: `Invalid volume specification`,
|
|
|
- `name":d:`: `Invalid volume specification`,
|
|
|
- `name\:d:`: `Invalid volume specification`,
|
|
|
- `name*:d:`: `Invalid volume specification`,
|
|
|
- `name|:d:`: `Invalid volume specification`,
|
|
|
- `name?:d:`: `Invalid volume specification`,
|
|
|
- `name/:d:`: `Invalid volume specification`,
|
|
|
- `d:\pathandmode:rw`: `Invalid volume specification`,
|
|
|
+ ``: "invalid volume specification: ",
|
|
|
+ `.`: "invalid volume specification: ",
|
|
|
+ `..\`: "invalid volume specification: ",
|
|
|
+ `c:\:..\`: "invalid volume specification: ",
|
|
|
+ `c:\:d:\:xyzzy`: "invalid volume specification: ",
|
|
|
+ `c:`: "cannot be `c:`",
|
|
|
+ `c:\`: "cannot be `c:`",
|
|
|
+ `c:\notexist:d:`: `source path does not exist`,
|
|
|
+ `c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`,
|
|
|
+ `name<:d:`: `invalid volume specification`,
|
|
|
+ `name>:d:`: `invalid volume specification`,
|
|
|
+ `name::d:`: `invalid volume specification`,
|
|
|
+ `name":d:`: `invalid volume specification`,
|
|
|
+ `name\:d:`: `invalid volume specification`,
|
|
|
+ `name*:d:`: `invalid volume specification`,
|
|
|
+ `name|:d:`: `invalid volume specification`,
|
|
|
+ `name?:d:`: `invalid volume specification`,
|
|
|
+ `name/:d:`: `invalid volume specification`,
|
|
|
+ `d:\pathandmode:rw`: `invalid volume specification`,
|
|
|
`con:d:`: `cannot be a reserved word for Windows filenames`,
|
|
|
`PRN:d:`: `cannot be a reserved word for Windows filenames`,
|
|
|
`aUx:d:`: `cannot be a reserved word for Windows filenames`,
|
|
@@ -93,50 +97,50 @@ func TestParseMountSpec(t *testing.T) {
|
|
|
"/rw:/ro",
|
|
|
}
|
|
|
invalid = map[string]string{
|
|
|
- "": "Invalid volume specification",
|
|
|
- "./": "Invalid volume destination",
|
|
|
- "../": "Invalid volume destination",
|
|
|
- "/:../": "Invalid volume destination",
|
|
|
- "/:path": "Invalid volume destination",
|
|
|
- ":": "Invalid volume specification",
|
|
|
- "/tmp:": "Invalid volume destination",
|
|
|
- ":test": "Invalid volume specification",
|
|
|
- ":/test": "Invalid volume specification",
|
|
|
- "tmp:": "Invalid volume destination",
|
|
|
- ":test:": "Invalid volume specification",
|
|
|
- "::": "Invalid volume specification",
|
|
|
- ":::": "Invalid volume specification",
|
|
|
- "/tmp:::": "Invalid volume specification",
|
|
|
- ":/tmp::": "Invalid volume specification",
|
|
|
- "/path:rw": "Invalid volume specification",
|
|
|
- "/path:ro": "Invalid volume specification",
|
|
|
- "/rw:rw": "Invalid volume specification",
|
|
|
- "path:ro": "Invalid volume specification",
|
|
|
- "/path:/path:sw": `invalid mode: sw`,
|
|
|
- "/path:/path:rwz": `invalid mode: rwz`,
|
|
|
+ "": "invalid volume specification",
|
|
|
+ "./": "mount path must be absolute",
|
|
|
+ "../": "mount path must be absolute",
|
|
|
+ "/:../": "mount path must be absolute",
|
|
|
+ "/:path": "mount path must be absolute",
|
|
|
+ ":": "invalid volume specification",
|
|
|
+ "/tmp:": "invalid volume specification",
|
|
|
+ ":test": "invalid volume specification",
|
|
|
+ ":/test": "invalid volume specification",
|
|
|
+ "tmp:": "invalid volume specification",
|
|
|
+ ":test:": "invalid volume specification",
|
|
|
+ "::": "invalid volume specification",
|
|
|
+ ":::": "invalid volume specification",
|
|
|
+ "/tmp:::": "invalid volume specification",
|
|
|
+ ":/tmp::": "invalid volume specification",
|
|
|
+ "/path:rw": "invalid volume specification",
|
|
|
+ "/path:ro": "invalid volume specification",
|
|
|
+ "/rw:rw": "invalid volume specification",
|
|
|
+ "path:ro": "invalid volume specification",
|
|
|
+ "/path:/path:sw": `invalid mode`,
|
|
|
+ "/path:/path:rwz": `invalid mode`,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for _, path := range valid {
|
|
|
- if _, err := ParseMountSpec(path, "local"); err != nil {
|
|
|
- t.Fatalf("ParseMountSpec(`%q`) should succeed: error %q", path, err)
|
|
|
+ if _, err := ParseMountRaw(path, "local"); err != nil {
|
|
|
+ t.Fatalf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for path, expectedError := range invalid {
|
|
|
- if _, err := ParseMountSpec(path, "local"); err == nil {
|
|
|
- t.Fatalf("ParseMountSpec(`%q`) should have failed validation. Err %v", path, err)
|
|
|
+ if mp, err := ParseMountRaw(path, "local"); err == nil {
|
|
|
+ t.Fatalf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
|
|
|
} else {
|
|
|
if !strings.Contains(err.Error(), expectedError) {
|
|
|
- t.Fatalf("ParseMountSpec(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
|
|
|
+ t.Fatalf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// testParseMountSpec is a structure used by TestParseMountSpecSplit for
|
|
|
-// specifying test cases for the ParseMountSpec() function.
|
|
|
-type testParseMountSpec struct {
|
|
|
+// testParseMountRaw is a structure used by TestParseMountRawSplit for
|
|
|
+// specifying test cases for the ParseMountRaw() function.
|
|
|
+type testParseMountRaw struct {
|
|
|
bind string
|
|
|
driver string
|
|
|
expDest string
|
|
@@ -147,10 +151,10 @@ type testParseMountSpec struct {
|
|
|
fail bool
|
|
|
}
|
|
|
|
|
|
-func TestParseMountSpecSplit(t *testing.T) {
|
|
|
- var cases []testParseMountSpec
|
|
|
+func TestParseMountRawSplit(t *testing.T) {
|
|
|
+ var cases []testParseMountRaw
|
|
|
if runtime.GOOS == "windows" {
|
|
|
- cases = []testParseMountSpec{
|
|
|
+ cases = []testParseMountRaw{
|
|
|
{`c:\:d:`, "local", `d:`, `c:\`, ``, "", true, false},
|
|
|
{`c:\:d:\`, "local", `d:\`, `c:\`, ``, "", true, false},
|
|
|
// TODO Windows post TP5 - Add readonly support {`c:\:d:\:ro`, "local", `d:\`, `c:\`, ``, "", false, false},
|
|
@@ -159,25 +163,26 @@ func TestParseMountSpecSplit(t *testing.T) {
|
|
|
{`name:d::rw`, "local", `d:`, ``, `name`, "local", true, false},
|
|
|
{`name:d:`, "local", `d:`, ``, `name`, "local", true, false},
|
|
|
// TODO Windows post TP5 - Add readonly support {`name:d::ro`, "local", `d:`, ``, `name`, "local", false, false},
|
|
|
- {`name:c:`, "", ``, ``, ``, "", true, true},
|
|
|
- {`driver/name:c:`, "", ``, ``, ``, "", true, true},
|
|
|
+ {`name:c:`, "", ``, ``, ``, DefaultDriverName, true, true},
|
|
|
+ {`driver/name:c:`, "", ``, ``, ``, DefaultDriverName, true, true},
|
|
|
}
|
|
|
} else {
|
|
|
- cases = []testParseMountSpec{
|
|
|
+ cases = []testParseMountRaw{
|
|
|
{"/tmp:/tmp1", "", "/tmp1", "/tmp", "", "", true, false},
|
|
|
{"/tmp:/tmp2:ro", "", "/tmp2", "/tmp", "", "", false, false},
|
|
|
{"/tmp:/tmp3:rw", "", "/tmp3", "/tmp", "", "", true, false},
|
|
|
{"/tmp:/tmp4:foo", "", "", "", "", "", false, true},
|
|
|
- {"name:/named1", "", "/named1", "", "name", "", true, false},
|
|
|
+ {"name:/named1", "", "/named1", "", "name", DefaultDriverName, true, false},
|
|
|
{"name:/named2", "external", "/named2", "", "name", "external", true, false},
|
|
|
{"name:/named3:ro", "local", "/named3", "", "name", "local", false, false},
|
|
|
- {"local/name:/tmp:rw", "", "/tmp", "", "local/name", "", true, false},
|
|
|
+ {"local/name:/tmp:rw", "", "/tmp", "", "local/name", DefaultDriverName, true, false},
|
|
|
{"/tmp:tmp", "", "", "", "", "", true, true},
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- for _, c := range cases {
|
|
|
- m, err := ParseMountSpec(c.bind, c.driver)
|
|
|
+ for i, c := range cases {
|
|
|
+ t.Logf("case %d", i)
|
|
|
+ m, err := ParseMountRaw(c.bind, c.driver)
|
|
|
if c.fail {
|
|
|
if err == nil {
|
|
|
t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
|
|
@@ -186,28 +191,79 @@ func TestParseMountSpecSplit(t *testing.T) {
|
|
|
}
|
|
|
|
|
|
if m == nil || err != nil {
|
|
|
- t.Fatalf("ParseMountSpec failed for spec %s driver %s error %v\n", c.bind, c.driver, err.Error())
|
|
|
+ t.Fatalf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
|
|
|
continue
|
|
|
}
|
|
|
|
|
|
if m.Destination != c.expDest {
|
|
|
- t.Fatalf("Expected destination %s, was %s, for spec %s\n", c.expDest, m.Destination, c.bind)
|
|
|
+ t.Fatalf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
|
|
|
}
|
|
|
|
|
|
if m.Source != c.expSource {
|
|
|
- t.Fatalf("Expected source %s, was %s, for spec %s\n", c.expSource, m.Source, c.bind)
|
|
|
+ t.Fatalf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
|
|
|
}
|
|
|
|
|
|
if m.Name != c.expName {
|
|
|
- t.Fatalf("Expected name %s, was %s for spec %s\n", c.expName, m.Name, c.bind)
|
|
|
+ t.Fatalf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
|
|
|
}
|
|
|
|
|
|
- if m.Driver != c.expDriver {
|
|
|
- t.Fatalf("Expected driver %s, was %s, for spec %s\n", c.expDriver, m.Driver, c.bind)
|
|
|
+ if (m.Driver != c.expDriver) || (m.Driver == DefaultDriverName && c.expDriver == "") {
|
|
|
+ t.Fatalf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
|
|
|
}
|
|
|
|
|
|
if m.RW != c.expRW {
|
|
|
- t.Fatalf("Expected RW %v, was %v for spec %s\n", c.expRW, m.RW, c.bind)
|
|
|
+ t.Fatalf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestParseMountSpec(t *testing.T) {
|
|
|
+ type c struct {
|
|
|
+ input mount.Mount
|
|
|
+ expected MountPoint
|
|
|
+ }
|
|
|
+ testDir, err := ioutil.TempDir("", "test-mount-config")
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
+
|
|
|
+ cases := []c{
|
|
|
+ {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath}},
|
|
|
+ {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true}},
|
|
|
+ {mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath}},
|
|
|
+ {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath}},
|
|
|
+ {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, Driver: DefaultDriverName, CopyData: DefaultCopyMode}},
|
|
|
+ {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, Driver: DefaultDriverName, CopyData: DefaultCopyMode}},
|
|
|
+ }
|
|
|
+
|
|
|
+ for i, c := range cases {
|
|
|
+ t.Logf("case %d", i)
|
|
|
+ mp, err := ParseMountSpec(c.input)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if c.expected.Type != mp.Type {
|
|
|
+ t.Fatalf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
|
|
|
+ }
|
|
|
+ if c.expected.Destination != mp.Destination {
|
|
|
+ t.Fatalf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
|
|
|
+ }
|
|
|
+ if c.expected.Source != mp.Source {
|
|
|
+ t.Fatalf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
|
|
|
+ }
|
|
|
+ if c.expected.RW != mp.RW {
|
|
|
+ t.Fatalf("Expected mount writable to match. Expected: '%v', Actual: '%s'", c.expected.RW, mp.RW)
|
|
|
+ }
|
|
|
+ if c.expected.Propagation != mp.Propagation {
|
|
|
+ t.Fatalf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
|
|
|
+ }
|
|
|
+ if c.expected.Driver != mp.Driver {
|
|
|
+ t.Fatalf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
|
|
|
+ }
|
|
|
+ if c.expected.CopyData != mp.CopyData {
|
|
|
+ t.Fatalf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
|
|
|
}
|
|
|
}
|
|
|
}
|