github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/vfs/vfs_test.go (about) 1 // Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package vfs 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "runtime" 14 "sort" 15 "strings" 16 "testing" 17 18 "github.com/cockroachdb/errors" 19 "github.com/cockroachdb/errors/oserror" 20 "github.com/stretchr/testify/require" 21 "github.com/zuoyebang/bitalostable/internal/datadriven" 22 ) 23 24 func normalizeError(err error) error { 25 // It is OS-specific which errors match IsExist, IsNotExist, and 26 // IsPermission, with OS-specific error messages. We normalize to the 27 // oserror.Err* errors which have standard error messages across 28 // platforms. 29 switch { 30 case oserror.IsExist(err): 31 return oserror.ErrExist 32 case oserror.IsNotExist(err): 33 return oserror.ErrNotExist 34 case oserror.IsPermission(err): 35 return oserror.ErrPermission 36 } 37 return err 38 } 39 40 type loggingFS struct { 41 FS 42 base string 43 w io.Writer 44 linkErr error 45 } 46 47 func (fs loggingFS) stripBase(path string) string { 48 if strings.HasPrefix(path, fs.base+"/") { 49 return path[len(fs.base)+1:] 50 } 51 return path 52 } 53 54 func (fs loggingFS) Create(name string) (File, error) { 55 f, err := fs.FS.Create(name) 56 fmt.Fprintf(fs.w, "create: %s [%v]\n", fs.stripBase(name), normalizeError(err)) 57 return loggingFile{f, fs.PathBase(name), fs.w}, err 58 } 59 60 func (fs loggingFS) Link(oldname, newname string) error { 61 err := fs.linkErr 62 if err == nil { 63 err = fs.FS.Link(oldname, newname) 64 } 65 fmt.Fprintf(fs.w, "link: %s -> %s [%v]\n", 66 fs.stripBase(oldname), fs.stripBase(newname), normalizeError(err)) 67 return err 68 } 69 70 func (fs loggingFS) ReuseForWrite(oldname, newname string) (File, error) { 71 f, err := fs.FS.ReuseForWrite(oldname, newname) 72 if err == nil { 73 f = loggingFile{f, fs.PathBase(newname), fs.w} 74 } 75 fmt.Fprintf(fs.w, "reuseForWrite: %s -> %s [%v]\n", 76 fs.stripBase(oldname), fs.stripBase(newname), normalizeError(err)) 77 return f, err 78 } 79 80 func (fs loggingFS) MkdirAll(dir string, perm os.FileMode) error { 81 err := fs.FS.MkdirAll(dir, perm) 82 fmt.Fprintf(fs.w, "mkdir: %s [%v]\n", fs.stripBase(dir), normalizeError(err)) 83 return err 84 } 85 86 func (fs loggingFS) Open(name string, opts ...OpenOption) (File, error) { 87 f, err := fs.FS.Open(name, opts...) 88 fmt.Fprintf(fs.w, "open: %s [%v]\n", fs.stripBase(name), normalizeError(err)) 89 return loggingFile{f, fs.stripBase(name), fs.w}, err 90 } 91 92 func (fs loggingFS) Remove(name string) error { 93 err := fs.FS.Remove(name) 94 fmt.Fprintf(fs.w, "remove: %s [%v]\n", fs.stripBase(name), normalizeError(err)) 95 return err 96 } 97 98 func (fs loggingFS) RemoveAll(name string) error { 99 err := fs.FS.RemoveAll(name) 100 fmt.Fprintf(fs.w, "remove-all: %s [%v]\n", fs.stripBase(name), normalizeError(err)) 101 return err 102 } 103 104 type loggingFile struct { 105 File 106 name string 107 w io.Writer 108 } 109 110 func (f loggingFile) Close() error { 111 err := f.File.Close() 112 fmt.Fprintf(f.w, "close: %s [%v]\n", f.name, err) 113 return err 114 } 115 116 func (f loggingFile) Sync() error { 117 err := f.File.Sync() 118 fmt.Fprintf(f.w, "sync: %s [%v]\n", f.name, err) 119 return err 120 } 121 122 func runTestVFS(t *testing.T, baseFS FS, dir string) { 123 var buf bytes.Buffer 124 fs := loggingFS{FS: baseFS, base: dir, w: &buf} 125 126 datadriven.RunTest(t, "testdata/vfs", func(td *datadriven.TestData) string { 127 switch td.Cmd { 128 case "define": 129 buf.Reset() 130 131 for _, arg := range td.CmdArgs { 132 switch arg.Key { 133 case "linkErr": 134 if len(arg.Vals) != 1 { 135 return fmt.Sprintf("%s: %s expected 1 value", td.Cmd, arg.Key) 136 } 137 switch arg.Vals[0] { 138 case "ErrExist": 139 fs.linkErr = oserror.ErrExist 140 case "ErrNotExist": 141 fs.linkErr = oserror.ErrNotExist 142 case "ErrPermission": 143 fs.linkErr = oserror.ErrPermission 144 default: 145 fs.linkErr = errors.New(arg.Vals[0]) 146 } 147 default: 148 return fmt.Sprintf("%s: unknown arg: %s", td.Cmd, arg.Key) 149 } 150 } 151 152 for _, line := range strings.Split(td.Input, "\n") { 153 parts := strings.Fields(line) 154 if len(parts) == 0 { 155 return "<op> [<args>]" 156 } 157 158 switch parts[0] { 159 case "clone": 160 if len(parts) != 3 { 161 return "clone <src> <dest>" 162 } 163 _, _ = Clone(fs, fs, fs.PathJoin(dir, parts[1]), fs.PathJoin(dir, parts[2])) 164 165 case "create": 166 if len(parts) != 2 { 167 return "create <name>" 168 } 169 f, _ := fs.Create(fs.PathJoin(dir, parts[1])) 170 f.Close() 171 172 case "link": 173 if len(parts) != 3 { 174 return "link <oldname> <newname>" 175 } 176 _ = fs.Link(fs.PathJoin(dir, parts[1]), fs.PathJoin(dir, parts[2])) 177 178 case "link-or-copy": 179 if len(parts) != 3 { 180 return "link-or-copy <oldname> <newname>" 181 } 182 _ = LinkOrCopy(fs, fs.PathJoin(dir, parts[1]), fs.PathJoin(dir, parts[2])) 183 184 case "reuseForWrite": 185 if len(parts) != 3 { 186 return "reuseForWrite <oldname> <newname>" 187 } 188 _, _ = fs.ReuseForWrite(fs.PathJoin(dir, parts[1]), fs.PathJoin(dir, parts[2])) 189 190 case "list": 191 if len(parts) != 2 { 192 return "list <dir>" 193 } 194 paths, _ := fs.List(fs.PathJoin(dir, parts[1])) 195 sort.Strings(paths) 196 for _, p := range paths { 197 fmt.Fprintln(&buf, p) 198 } 199 200 case "mkdir": 201 if len(parts) != 2 { 202 return "mkdir <dir>" 203 } 204 _ = fs.MkdirAll(fs.PathJoin(dir, parts[1]), 0755) 205 206 case "remove": 207 if len(parts) != 2 { 208 return "remove <name>" 209 } 210 _ = fs.Remove(fs.PathJoin(dir, parts[1])) 211 212 case "remove-all": 213 if len(parts) != 2 { 214 return "remove-all <name>" 215 } 216 _ = fs.RemoveAll(fs.PathJoin(dir, parts[1])) 217 } 218 } 219 220 return buf.String() 221 222 default: 223 return fmt.Sprintf("unknown command: %s", td.Cmd) 224 } 225 }) 226 } 227 228 func TestVFS(t *testing.T) { 229 t.Run("mem", func(t *testing.T) { 230 runTestVFS(t, NewMem(), "") 231 }) 232 if runtime.GOOS != "windows" { 233 t.Run("disk", func(t *testing.T) { 234 dir, err := ioutil.TempDir("", "test-vfs") 235 require.NoError(t, err) 236 defer func() { 237 _ = os.RemoveAll(dir) 238 }() 239 runTestVFS(t, Default, dir) 240 }) 241 } 242 } 243 244 func TestVFSGetDiskUsage(t *testing.T) { 245 dir, err := ioutil.TempDir("", "test-free-space") 246 require.NoError(t, err) 247 defer func() { 248 _ = os.RemoveAll(dir) 249 }() 250 _, err = Default.GetDiskUsage(dir) 251 require.Nil(t, err) 252 } 253 254 func TestVFSCreateLinkSemantics(t *testing.T) { 255 dir, err := ioutil.TempDir("", "test-create-link") 256 require.NoError(t, err) 257 defer func() { _ = os.RemoveAll(dir) }() 258 259 for _, fs := range []FS{Default, NewMem()} { 260 t.Run(fmt.Sprintf("%T", fs), func(t *testing.T) { 261 writeFile := func(path, contents string) { 262 path = fs.PathJoin(dir, path) 263 f, err := fs.Create(path) 264 require.NoError(t, err) 265 _, err = f.Write([]byte(contents)) 266 require.NoError(t, err) 267 require.NoError(t, f.Close()) 268 } 269 readFile := func(path string) string { 270 path = fs.PathJoin(dir, path) 271 f, err := fs.Open(path) 272 require.NoError(t, err) 273 b, err := ioutil.ReadAll(f) 274 require.NoError(t, err) 275 require.NoError(t, f.Close()) 276 return string(b) 277 } 278 require.NoError(t, fs.MkdirAll(dir, 0755)) 279 280 // Write a file 'foo' and create a hardlink at 'bar'. 281 writeFile("foo", "foo") 282 require.NoError(t, fs.Link(fs.PathJoin(dir, "foo"), fs.PathJoin(dir, "bar"))) 283 284 // Both files should contain equal contents, because they're backed by 285 // the same inode. 286 require.Equal(t, "foo", readFile("foo")) 287 require.Equal(t, "foo", readFile("bar")) 288 289 // Calling Create on 'bar' must NOT truncate 'foo'. It should create a 290 // new file at path 'bar' with a new inode. 291 writeFile("bar", "bar") 292 293 require.Equal(t, "foo", readFile("foo")) 294 require.Equal(t, "bar", readFile("bar")) 295 }) 296 } 297 } 298 299 // TestVFSRootDirName ensures that opening the root directory on both the 300 // Default and MemFS works and returns a File which has the name of the 301 // path separator for the FS (always sep for MemFS). 302 func TestVFSRootDirName(t *testing.T) { 303 for _, fs := range []FS{Default, NewMem()} { 304 rootDir, err := fs.Open("/") 305 require.NoError(t, err) 306 fi, err := rootDir.Stat() 307 require.NoError(t, err) 308 309 exp := sep 310 if fs == Default { 311 exp = string(os.PathSeparator) 312 } 313 require.Equal(t, exp, fi.Name()) 314 } 315 }