volume/volume_test.go
a7e686a7
 package volume
 
 import (
fc7b904d
 	"io/ioutil"
 	"os"
a7e686a7
 	"runtime"
 	"strings"
 	"testing"
fc7b904d
 
 	"github.com/docker/docker/api/types/mount"
a7e686a7
 )
 
fc7b904d
 func TestParseMountRaw(t *testing.T) {
a7e686a7
 	var (
 		valid   []string
 		invalid map[string]string
 	)
 
 	if runtime.GOOS == "windows" {
 		valid = []string{
 			`d:\`,
 			`d:`,
 			`d:\path`,
 			`d:\path with space`,
b0e24c73
 			// TODO Windows post TP5 - readonly support `d:\pathandmode:ro`,
a7e686a7
 			`c:\:d:\`,
 			`c:\windows\:d:`,
 			`c:\windows:d:\s p a c e`,
 			`c:\windows:d:\s p a c e:RW`,
 			`c:\program files:d:\s p a c e i n h o s t d i r`,
 			`0123456789name:d:`,
 			`MiXeDcAsEnAmE:d:`,
 			`name:D:`,
 			`name:D::rW`,
 			`name:D::RW`,
b0e24c73
 			// TODO Windows post TP5 - readonly support `name:D::RO`,
a7e686a7
 			`c:/:d:/forward/slashes/are/good/too`,
b0e24c73
 			// TODO Windows post TP5 - readonly support `c:/:d:/including with/spaces:ro`,
a7e686a7
 			`c:\Windows`,             // With capital
 			`c:\Program Files (x86)`, // With capitals and brackets
 		}
 		invalid = map[string]string{
fc7b904d
 			``:                                 "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`,
a7e686a7
 			`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`,
 			`nul:d:`:                           `cannot be a reserved word for Windows filenames`,
 			`com1:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`com2:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`com3:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`com4:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`com5:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`com6:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`com7:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`com8:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`com9:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`lpt1:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`lpt2:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`lpt3:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`lpt4:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`lpt5:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`lpt6:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`lpt7:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`lpt8:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`lpt9:d:`:                          `cannot be a reserved word for Windows filenames`,
1b62b8c0
 			`c:\windows\system32\ntdll.dll`:    `Only directories can be mapped on this platform`,
a7e686a7
 		}
 
 	} else {
 		valid = []string{
 			"/home",
 			"/home:/home",
 			"/home:/something/else",
 			"/with space",
 			"/home:/with space",
 			"relative:/absolute-path",
 			"hostPath:/containerPath:ro",
 			"/hostPath:/containerPath:rw",
 			"/rw:/ro",
 		}
 		invalid = map[string]string{
fc7b904d
 			"":                "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`,
a7e686a7
 		}
 	}
 
 	for _, path := range valid {
fc7b904d
 		if _, err := ParseMountRaw(path, "local"); err != nil {
 			t.Fatalf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
a7e686a7
 		}
 	}
 
 	for path, expectedError := range invalid {
fc7b904d
 		if mp, err := ParseMountRaw(path, "local"); err == nil {
 			t.Fatalf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
a7e686a7
 		} else {
 			if !strings.Contains(err.Error(), expectedError) {
fc7b904d
 				t.Fatalf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
a7e686a7
 			}
 		}
 	}
 }
 
fc7b904d
 // testParseMountRaw is a structure used by TestParseMountRawSplit for
 // specifying test cases for the ParseMountRaw() function.
 type testParseMountRaw struct {
a7e686a7
 	bind      string
 	driver    string
 	expDest   string
 	expSource string
 	expName   string
 	expDriver string
 	expRW     bool
 	fail      bool
 }
 
fc7b904d
 func TestParseMountRawSplit(t *testing.T) {
 	var cases []testParseMountRaw
a7e686a7
 	if runtime.GOOS == "windows" {
fc7b904d
 		cases = []testParseMountRaw{
a7e686a7
 			{`c:\:d:`, "local", `d:`, `c:\`, ``, "", true, false},
 			{`c:\:d:\`, "local", `d:\`, `c:\`, ``, "", true, false},
b0e24c73
 			// TODO Windows post TP5 - Add readonly support {`c:\:d:\:ro`, "local", `d:\`, `c:\`, ``, "", false, false},
a7e686a7
 			{`c:\:d:\:rw`, "local", `d:\`, `c:\`, ``, "", true, false},
 			{`c:\:d:\:foo`, "local", `d:\`, `c:\`, ``, "", false, true},
 			{`name:d::rw`, "local", `d:`, ``, `name`, "local", true, false},
 			{`name:d:`, "local", `d:`, ``, `name`, "local", true, false},
b0e24c73
 			// TODO Windows post TP5 - Add readonly support {`name:d::ro`, "local", `d:`, ``, `name`, "local", false, false},
6a0bdffc
 			{`name:c:`, "", ``, ``, ``, "", true, true},
 			{`driver/name:c:`, "", ``, ``, ``, "", true, true},
a7e686a7
 		}
 	} else {
fc7b904d
 		cases = []testParseMountRaw{
a7e686a7
 			{"/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},
6a0bdffc
 			{"name:/named1", "", "/named1", "", "name", "", true, false},
a7e686a7
 			{"name:/named2", "external", "/named2", "", "name", "external", true, false},
 			{"name:/named3:ro", "local", "/named3", "", "name", "local", false, false},
6a0bdffc
 			{"local/name:/tmp:rw", "", "/tmp", "", "local/name", "", true, false},
a7e686a7
 			{"/tmp:tmp", "", "", "", "", "", true, true},
 		}
 	}
 
fc7b904d
 	for i, c := range cases {
 		t.Logf("case %d", i)
 		m, err := ParseMountRaw(c.bind, c.driver)
a7e686a7
 		if c.fail {
 			if err == nil {
 				t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
 			}
 			continue
 		}
 
 		if m == nil || err != nil {
fc7b904d
 			t.Fatalf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
a7e686a7
 			continue
 		}
 
 		if m.Destination != c.expDest {
fc7b904d
 			t.Fatalf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
a7e686a7
 		}
 
 		if m.Source != c.expSource {
fc7b904d
 			t.Fatalf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
a7e686a7
 		}
 
 		if m.Name != c.expName {
fc7b904d
 			t.Fatalf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
a7e686a7
 		}
 
6a0bdffc
 		if m.Driver != c.expDriver {
fc7b904d
 			t.Fatalf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
a7e686a7
 		}
 
 		if m.RW != c.expRW {
fc7b904d
 			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}},
6a0bdffc
 		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
 		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
fc7b904d
 	}
 
 	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 {
e3c24caf
 			t.Fatalf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
fc7b904d
 		}
 		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)
a7e686a7
 		}
 	}
 }