github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/volume/mounts/linux_parser_test.go (about) 1 package mounts // import "github.com/docker/docker/volume/mounts" 2 3 import ( 4 "fmt" 5 "strings" 6 "testing" 7 8 "github.com/docker/docker/api/types/mount" 9 "gotest.tools/v3/assert" 10 ) 11 12 func TestLinuxParseMountRaw(t *testing.T) { 13 valid := []string{ 14 "/home", 15 "/home:/home", 16 "/home:/something/else", 17 "/with space", 18 "/home:/with space", 19 "relative:/absolute-path", 20 "hostPath:/containerPath:ro", 21 "/hostPath:/containerPath:rw", 22 "/rw:/ro", 23 "/hostPath:/containerPath:shared", 24 "/hostPath:/containerPath:rshared", 25 "/hostPath:/containerPath:slave", 26 "/hostPath:/containerPath:rslave", 27 "/hostPath:/containerPath:private", 28 "/hostPath:/containerPath:rprivate", 29 "/hostPath:/containerPath:ro,shared", 30 "/hostPath:/containerPath:ro,slave", 31 "/hostPath:/containerPath:ro,private", 32 "/hostPath:/containerPath:ro,z,shared", 33 "/hostPath:/containerPath:ro,Z,slave", 34 "/hostPath:/containerPath:Z,ro,slave", 35 "/hostPath:/containerPath:slave,Z,ro", 36 "/hostPath:/containerPath:Z,slave,ro", 37 "/hostPath:/containerPath:slave,ro,Z", 38 "/hostPath:/containerPath:rslave,ro,Z", 39 "/hostPath:/containerPath:ro,rshared,Z", 40 "/hostPath:/containerPath:ro,Z,rprivate", 41 } 42 43 invalid := map[string]string{ 44 "": "invalid volume specification", 45 "./": "mount path must be absolute", 46 "../": "mount path must be absolute", 47 "/:../": "mount path must be absolute", 48 "/:path": "mount path must be absolute", 49 ":": "invalid volume specification", 50 "/tmp:": "invalid volume specification", 51 ":test": "invalid volume specification", 52 ":/test": "invalid volume specification", 53 "tmp:": "invalid volume specification", 54 ":test:": "invalid volume specification", 55 "::": "invalid volume specification", 56 ":::": "invalid volume specification", 57 "/tmp:::": "invalid volume specification", 58 ":/tmp::": "invalid volume specification", 59 "/path:rw": "invalid volume specification", 60 "/path:ro": "invalid volume specification", 61 "/rw:rw": "invalid volume specification", 62 "path:ro": "invalid volume specification", 63 "/path:/path:sw": `invalid mode`, 64 "/path:/path:rwz": `invalid mode`, 65 "/path:/path:ro,rshared,rslave": `invalid mode`, 66 "/path:/path:ro,z,rshared,rslave": `invalid mode`, 67 "/path:shared": "invalid volume specification", 68 "/path:slave": "invalid volume specification", 69 "/path:private": "invalid volume specification", 70 "name:/absolute-path:shared": "invalid volume specification", 71 "name:/absolute-path:rshared": "invalid volume specification", 72 "name:/absolute-path:slave": "invalid volume specification", 73 "name:/absolute-path:rslave": "invalid volume specification", 74 "name:/absolute-path:private": "invalid volume specification", 75 "name:/absolute-path:rprivate": "invalid volume specification", 76 } 77 78 parser := NewLinuxParser() 79 if p, ok := parser.(*linuxParser); ok { 80 p.fi = mockFiProvider{} 81 } 82 83 for _, path := range valid { 84 if _, err := parser.ParseMountRaw(path, "local"); err != nil { 85 t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err) 86 } 87 } 88 89 for path, expectedError := range invalid { 90 if mp, err := parser.ParseMountRaw(path, "local"); err == nil { 91 t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp) 92 } else { 93 if !strings.Contains(err.Error(), expectedError) { 94 t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error()) 95 } 96 } 97 } 98 } 99 100 func TestLinuxParseMountRawSplit(t *testing.T) { 101 cases := []struct { 102 bind string 103 driver string 104 expType mount.Type 105 expDest string 106 expSource string 107 expName string 108 expDriver string 109 expRW bool 110 fail bool 111 }{ 112 {"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false}, 113 {"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false}, 114 {"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false}, 115 {"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true}, 116 {"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false}, 117 {"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false}, 118 {"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false}, 119 {"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false}, 120 {"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true}, 121 } 122 123 parser := NewLinuxParser() 124 if p, ok := parser.(*linuxParser); ok { 125 p.fi = mockFiProvider{} 126 } 127 128 for i, c := range cases { 129 c := c 130 t.Run(fmt.Sprintf("%d_%s", i, c.bind), func(t *testing.T) { 131 m, err := parser.ParseMountRaw(c.bind, c.driver) 132 if c.fail { 133 assert.ErrorContains(t, err, "", "expected an error") 134 return 135 } 136 137 assert.NilError(t, err) 138 assert.Equal(t, m.Destination, c.expDest) 139 assert.Equal(t, m.Source, c.expSource) 140 assert.Equal(t, m.Name, c.expName) 141 assert.Equal(t, m.Driver, c.expDriver) 142 assert.Equal(t, m.RW, c.expRW) 143 assert.Equal(t, m.Type, c.expType) 144 }) 145 } 146 } 147 148 // TestLinuxParseMountSpecBindWithFileinfoError makes sure that the parser returns 149 // the error produced by the fileinfo provider. 150 // 151 // Some extra context for the future in case of changes and possible wtf are we 152 // testing this for: 153 // 154 // Currently this "fileInfoProvider" returns (bool, bool, error) 155 // The 1st bool is "does this path exist" 156 // The 2nd bool is "is this path a dir" 157 // Then of course the error is an error. 158 // 159 // The issue is the parser was ignoring the error and only looking at the 160 // "does this path exist" boolean, which is always false if there is an error. 161 // Then the error returned to the caller was a (slightly, maybe) friendlier 162 // error string than what comes from `os.Stat` 163 // So ...the caller was always getting an error saying the path doesn't exist 164 // even if it does exist but got some other error (like a permission error). 165 // This is confusing to users. 166 func TestLinuxParseMountSpecBindWithFileinfoError(t *testing.T) { 167 parser := NewLinuxParser() 168 testErr := fmt.Errorf("some crazy error") 169 if pr, ok := parser.(*linuxParser); ok { 170 pr.fi = &mockFiProviderWithError{err: testErr} 171 } 172 173 _, err := parser.ParseMountSpec(mount.Mount{ 174 Type: mount.TypeBind, 175 Source: `/bananas`, 176 Target: `/bananas`, 177 }) 178 assert.ErrorContains(t, err, testErr.Error()) 179 } 180 181 func TestConvertTmpfsOptions(t *testing.T) { 182 type testCase struct { 183 opt mount.TmpfsOptions 184 readOnly bool 185 expectedSubstrings []string 186 unexpectedSubstrings []string 187 } 188 cases := []testCase{ 189 { 190 opt: mount.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700}, 191 readOnly: false, 192 expectedSubstrings: []string{"size=1m", "mode=700"}, 193 unexpectedSubstrings: []string{"ro"}, 194 }, 195 { 196 opt: mount.TmpfsOptions{}, 197 readOnly: true, 198 expectedSubstrings: []string{"ro"}, 199 unexpectedSubstrings: []string{}, 200 }, 201 } 202 p := NewLinuxParser() 203 for _, c := range cases { 204 data, err := p.ConvertTmpfsOptions(&c.opt, c.readOnly) 205 if err != nil { 206 t.Fatalf("could not convert %+v (readOnly: %v) to string: %v", 207 c.opt, c.readOnly, err) 208 } 209 t.Logf("data=%q", data) 210 for _, s := range c.expectedSubstrings { 211 if !strings.Contains(data, s) { 212 t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c) 213 } 214 } 215 for _, s := range c.unexpectedSubstrings { 216 if strings.Contains(data, s) { 217 t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c) 218 } 219 } 220 } 221 }