github.com/olljanat/moby@v1.13.1/volume/volume_test.go (about)

     1  package volume
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"runtime"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/docker/docker/api/types/mount"
    11  )
    12  
    13  func TestParseMountRaw(t *testing.T) {
    14  	var (
    15  		valid   []string
    16  		invalid map[string]string
    17  	)
    18  
    19  	if runtime.GOOS == "windows" {
    20  		valid = []string{
    21  			`d:\`,
    22  			`d:`,
    23  			`d:\path`,
    24  			`d:\path with space`,
    25  			// TODO Windows post TP5 - readonly support `d:\pathandmode:ro`,
    26  			`c:\:d:\`,
    27  			`c:\windows\:d:`,
    28  			`c:\windows:d:\s p a c e`,
    29  			`c:\windows:d:\s p a c e:RW`,
    30  			`c:\program files:d:\s p a c e i n h o s t d i r`,
    31  			`0123456789name:d:`,
    32  			`MiXeDcAsEnAmE:d:`,
    33  			`name:D:`,
    34  			`name:D::rW`,
    35  			`name:D::RW`,
    36  			// TODO Windows post TP5 - readonly support `name:D::RO`,
    37  			`c:/:d:/forward/slashes/are/good/too`,
    38  			// TODO Windows post TP5 - readonly support `c:/:d:/including with/spaces:ro`,
    39  			`c:\Windows`,             // With capital
    40  			`c:\Program Files (x86)`, // With capitals and brackets
    41  		}
    42  		invalid = map[string]string{
    43  			``:                                 "invalid volume specification: ",
    44  			`.`:                                "invalid volume specification: ",
    45  			`..\`:                              "invalid volume specification: ",
    46  			`c:\:..\`:                          "invalid volume specification: ",
    47  			`c:\:d:\:xyzzy`:                    "invalid volume specification: ",
    48  			`c:`:                               "cannot be `c:`",
    49  			`c:\`:                              "cannot be `c:`",
    50  			`c:\notexist:d:`:                   `source path does not exist`,
    51  			`c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`,
    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  			`name?:d:`:                         `invalid volume specification`,
    60  			`name/:d:`:                         `invalid volume specification`,
    61  			`d:\pathandmode:rw`:                `invalid volume specification`,
    62  			`con:d:`:                           `cannot be a reserved word for Windows filenames`,
    63  			`PRN:d:`:                           `cannot be a reserved word for Windows filenames`,
    64  			`aUx:d:`:                           `cannot be a reserved word for Windows filenames`,
    65  			`nul:d:`:                           `cannot be a reserved word for Windows filenames`,
    66  			`com1:d:`:                          `cannot be a reserved word for Windows filenames`,
    67  			`com2:d:`:                          `cannot be a reserved word for Windows filenames`,
    68  			`com3:d:`:                          `cannot be a reserved word for Windows filenames`,
    69  			`com4:d:`:                          `cannot be a reserved word for Windows filenames`,
    70  			`com5:d:`:                          `cannot be a reserved word for Windows filenames`,
    71  			`com6:d:`:                          `cannot be a reserved word for Windows filenames`,
    72  			`com7:d:`:                          `cannot be a reserved word for Windows filenames`,
    73  			`com8:d:`:                          `cannot be a reserved word for Windows filenames`,
    74  			`com9:d:`:                          `cannot be a reserved word for Windows filenames`,
    75  			`lpt1:d:`:                          `cannot be a reserved word for Windows filenames`,
    76  			`lpt2:d:`:                          `cannot be a reserved word for Windows filenames`,
    77  			`lpt3:d:`:                          `cannot be a reserved word for Windows filenames`,
    78  			`lpt4:d:`:                          `cannot be a reserved word for Windows filenames`,
    79  			`lpt5:d:`:                          `cannot be a reserved word for Windows filenames`,
    80  			`lpt6:d:`:                          `cannot be a reserved word for Windows filenames`,
    81  			`lpt7:d:`:                          `cannot be a reserved word for Windows filenames`,
    82  			`lpt8:d:`:                          `cannot be a reserved word for Windows filenames`,
    83  			`lpt9:d:`:                          `cannot be a reserved word for Windows filenames`,
    84  			`c:\windows\system32\ntdll.dll`:    `Only directories can be mapped on this platform`,
    85  		}
    86  
    87  	} else {
    88  		valid = []string{
    89  			"/home",
    90  			"/home:/home",
    91  			"/home:/something/else",
    92  			"/with space",
    93  			"/home:/with space",
    94  			"relative:/absolute-path",
    95  			"hostPath:/containerPath:ro",
    96  			"/hostPath:/containerPath:rw",
    97  			"/rw:/ro",
    98  		}
    99  		invalid = map[string]string{
   100  			"":                "invalid volume specification",
   101  			"./":              "mount path must be absolute",
   102  			"../":             "mount path must be absolute",
   103  			"/:../":           "mount path must be absolute",
   104  			"/:path":          "mount path must be absolute",
   105  			":":               "invalid volume specification",
   106  			"/tmp:":           "invalid volume specification",
   107  			":test":           "invalid volume specification",
   108  			":/test":          "invalid volume specification",
   109  			"tmp:":            "invalid volume specification",
   110  			":test:":          "invalid volume specification",
   111  			"::":              "invalid volume specification",
   112  			":::":             "invalid volume specification",
   113  			"/tmp:::":         "invalid volume specification",
   114  			":/tmp::":         "invalid volume specification",
   115  			"/path:rw":        "invalid volume specification",
   116  			"/path:ro":        "invalid volume specification",
   117  			"/rw:rw":          "invalid volume specification",
   118  			"path:ro":         "invalid volume specification",
   119  			"/path:/path:sw":  `invalid mode`,
   120  			"/path:/path:rwz": `invalid mode`,
   121  		}
   122  	}
   123  
   124  	for _, path := range valid {
   125  		if _, err := ParseMountRaw(path, "local"); err != nil {
   126  			t.Fatalf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
   127  		}
   128  	}
   129  
   130  	for path, expectedError := range invalid {
   131  		if mp, err := ParseMountRaw(path, "local"); err == nil {
   132  			t.Fatalf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
   133  		} else {
   134  			if !strings.Contains(err.Error(), expectedError) {
   135  				t.Fatalf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
   136  			}
   137  		}
   138  	}
   139  }
   140  
   141  // testParseMountRaw is a structure used by TestParseMountRawSplit for
   142  // specifying test cases for the ParseMountRaw() function.
   143  type testParseMountRaw struct {
   144  	bind      string
   145  	driver    string
   146  	expDest   string
   147  	expSource string
   148  	expName   string
   149  	expDriver string
   150  	expRW     bool
   151  	fail      bool
   152  }
   153  
   154  func TestParseMountRawSplit(t *testing.T) {
   155  	var cases []testParseMountRaw
   156  	if runtime.GOOS == "windows" {
   157  		cases = []testParseMountRaw{
   158  			{`c:\:d:`, "local", `d:`, `c:\`, ``, "", true, false},
   159  			{`c:\:d:\`, "local", `d:\`, `c:\`, ``, "", true, false},
   160  			// TODO Windows post TP5 - Add readonly support {`c:\:d:\:ro`, "local", `d:\`, `c:\`, ``, "", false, false},
   161  			{`c:\:d:\:rw`, "local", `d:\`, `c:\`, ``, "", true, false},
   162  			{`c:\:d:\:foo`, "local", `d:\`, `c:\`, ``, "", false, true},
   163  			{`name:d::rw`, "local", `d:`, ``, `name`, "local", true, false},
   164  			{`name:d:`, "local", `d:`, ``, `name`, "local", true, false},
   165  			// TODO Windows post TP5 - Add readonly support {`name:d::ro`, "local", `d:`, ``, `name`, "local", false, false},
   166  			{`name:c:`, "", ``, ``, ``, "", true, true},
   167  			{`driver/name:c:`, "", ``, ``, ``, "", true, true},
   168  		}
   169  	} else {
   170  		cases = []testParseMountRaw{
   171  			{"/tmp:/tmp1", "", "/tmp1", "/tmp", "", "", true, false},
   172  			{"/tmp:/tmp2:ro", "", "/tmp2", "/tmp", "", "", false, false},
   173  			{"/tmp:/tmp3:rw", "", "/tmp3", "/tmp", "", "", true, false},
   174  			{"/tmp:/tmp4:foo", "", "", "", "", "", false, true},
   175  			{"name:/named1", "", "/named1", "", "name", "", true, false},
   176  			{"name:/named2", "external", "/named2", "", "name", "external", true, false},
   177  			{"name:/named3:ro", "local", "/named3", "", "name", "local", false, false},
   178  			{"local/name:/tmp:rw", "", "/tmp", "", "local/name", "", true, false},
   179  			{"/tmp:tmp", "", "", "", "", "", true, true},
   180  		}
   181  	}
   182  
   183  	for i, c := range cases {
   184  		t.Logf("case %d", i)
   185  		m, err := ParseMountRaw(c.bind, c.driver)
   186  		if c.fail {
   187  			if err == nil {
   188  				t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
   189  			}
   190  			continue
   191  		}
   192  
   193  		if m == nil || err != nil {
   194  			t.Fatalf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
   195  			continue
   196  		}
   197  
   198  		if m.Destination != c.expDest {
   199  			t.Fatalf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
   200  		}
   201  
   202  		if m.Source != c.expSource {
   203  			t.Fatalf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
   204  		}
   205  
   206  		if m.Name != c.expName {
   207  			t.Fatalf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
   208  		}
   209  
   210  		if m.Driver != c.expDriver {
   211  			t.Fatalf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
   212  		}
   213  
   214  		if m.RW != c.expRW {
   215  			t.Fatalf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
   216  		}
   217  	}
   218  }
   219  
   220  func TestParseMountSpec(t *testing.T) {
   221  	type c struct {
   222  		input    mount.Mount
   223  		expected MountPoint
   224  	}
   225  	testDir, err := ioutil.TempDir("", "test-mount-config")
   226  	if err != nil {
   227  		t.Fatal(err)
   228  	}
   229  	defer os.RemoveAll(testDir)
   230  
   231  	cases := []c{
   232  		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath}},
   233  		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true}},
   234  		{mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath}},
   235  		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath}},
   236  		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
   237  		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
   238  	}
   239  
   240  	for i, c := range cases {
   241  		t.Logf("case %d", i)
   242  		mp, err := ParseMountSpec(c.input)
   243  		if err != nil {
   244  			t.Fatal(err)
   245  		}
   246  
   247  		if c.expected.Type != mp.Type {
   248  			t.Fatalf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
   249  		}
   250  		if c.expected.Destination != mp.Destination {
   251  			t.Fatalf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
   252  		}
   253  		if c.expected.Source != mp.Source {
   254  			t.Fatalf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
   255  		}
   256  		if c.expected.RW != mp.RW {
   257  			t.Fatalf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
   258  		}
   259  		if c.expected.Propagation != mp.Propagation {
   260  			t.Fatalf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
   261  		}
   262  		if c.expected.Driver != mp.Driver {
   263  			t.Fatalf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
   264  		}
   265  		if c.expected.CopyData != mp.CopyData {
   266  			t.Fatalf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
   267  		}
   268  	}
   269  }