github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/internal/vfs/vfs_test.go (about) 1 // Copyright 2021 The Bitalosdb author(hustxrb@163.com) and other contributors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package vfs 16 17 import ( 18 "fmt" 19 "io" 20 "io/ioutil" 21 "os" 22 "strings" 23 "testing" 24 25 "github.com/cockroachdb/errors/oserror" 26 "github.com/stretchr/testify/require" 27 ) 28 29 func normalizeError(err error) error { 30 // It is OS-specific which errors match IsExist, IsNotExist, and 31 // IsPermission, with OS-specific error messages. We normalize to the 32 // oserror.Err* errors which have standard error messages across 33 // platforms. 34 switch { 35 case oserror.IsExist(err): 36 return oserror.ErrExist 37 case oserror.IsNotExist(err): 38 return oserror.ErrNotExist 39 case oserror.IsPermission(err): 40 return oserror.ErrPermission 41 } 42 return err 43 } 44 45 type loggingFS struct { 46 FS 47 base string 48 w io.Writer 49 linkErr error 50 } 51 52 func (fs loggingFS) stripBase(path string) string { 53 if strings.HasPrefix(path, fs.base+"/") { 54 return path[len(fs.base)+1:] 55 } 56 return path 57 } 58 59 func (fs loggingFS) Create(name string) (File, error) { 60 f, err := fs.FS.Create(name) 61 fmt.Fprintf(fs.w, "create: %s [%v]\n", fs.stripBase(name), normalizeError(err)) 62 return loggingFile{f, fs.PathBase(name), fs.w}, err 63 } 64 65 func (fs loggingFS) Link(oldname, newname string) error { 66 err := fs.linkErr 67 if err == nil { 68 err = fs.FS.Link(oldname, newname) 69 } 70 fmt.Fprintf(fs.w, "link: %s -> %s [%v]\n", 71 fs.stripBase(oldname), fs.stripBase(newname), normalizeError(err)) 72 return err 73 } 74 75 func (fs loggingFS) ReuseForWrite(oldname, newname string) (File, error) { 76 f, err := fs.FS.ReuseForWrite(oldname, newname) 77 if err == nil { 78 f = loggingFile{f, fs.PathBase(newname), fs.w} 79 } 80 fmt.Fprintf(fs.w, "reuseForWrite: %s -> %s [%v]\n", 81 fs.stripBase(oldname), fs.stripBase(newname), normalizeError(err)) 82 return f, err 83 } 84 85 func (fs loggingFS) MkdirAll(dir string, perm os.FileMode) error { 86 err := fs.FS.MkdirAll(dir, perm) 87 fmt.Fprintf(fs.w, "mkdir: %s [%v]\n", fs.stripBase(dir), normalizeError(err)) 88 return err 89 } 90 91 func (fs loggingFS) Open(name string, opts ...OpenOption) (File, error) { 92 f, err := fs.FS.Open(name, opts...) 93 fmt.Fprintf(fs.w, "open: %s [%v]\n", fs.stripBase(name), normalizeError(err)) 94 return loggingFile{f, fs.stripBase(name), fs.w}, err 95 } 96 97 func (fs loggingFS) Remove(name string) error { 98 err := fs.FS.Remove(name) 99 fmt.Fprintf(fs.w, "remove: %s [%v]\n", fs.stripBase(name), normalizeError(err)) 100 return err 101 } 102 103 func (fs loggingFS) RemoveAll(name string) error { 104 err := fs.FS.RemoveAll(name) 105 fmt.Fprintf(fs.w, "remove-all: %s [%v]\n", fs.stripBase(name), normalizeError(err)) 106 return err 107 } 108 109 type loggingFile struct { 110 File 111 name string 112 w io.Writer 113 } 114 115 func (f loggingFile) Close() error { 116 err := f.File.Close() 117 fmt.Fprintf(f.w, "close: %s [%v]\n", f.name, err) 118 return err 119 } 120 121 func (f loggingFile) Sync() error { 122 err := f.File.Sync() 123 fmt.Fprintf(f.w, "sync: %s [%v]\n", f.name, err) 124 return err 125 } 126 127 func TestVFSGetDiskUsage(t *testing.T) { 128 dir, err := os.MkdirTemp("", "test-free-space") 129 require.NoError(t, err) 130 defer func() { 131 _ = os.RemoveAll(dir) 132 }() 133 _, err = Default.GetDiskUsage(dir) 134 require.Nil(t, err) 135 } 136 137 func TestVFSCreateLinkSemantics(t *testing.T) { 138 dir, err := ioutil.TempDir("", "test-create-link") 139 require.NoError(t, err) 140 defer func() { _ = os.RemoveAll(dir) }() 141 142 for _, fs := range []FS{Default, NewMem()} { 143 t.Run(fmt.Sprintf("%T", fs), func(t *testing.T) { 144 writeFile := func(path, contents string) { 145 path = fs.PathJoin(dir, path) 146 f, err := fs.Create(path) 147 require.NoError(t, err) 148 _, err = f.Write([]byte(contents)) 149 require.NoError(t, err) 150 require.NoError(t, f.Close()) 151 } 152 readFile := func(path string) string { 153 path = fs.PathJoin(dir, path) 154 f, err := fs.Open(path) 155 require.NoError(t, err) 156 b, err := ioutil.ReadAll(f) 157 require.NoError(t, err) 158 require.NoError(t, f.Close()) 159 return string(b) 160 } 161 require.NoError(t, fs.MkdirAll(dir, 0755)) 162 163 // Write a file 'foo' and create a hardlink at 'bar'. 164 writeFile("foo", "foo") 165 require.NoError(t, fs.Link(fs.PathJoin(dir, "foo"), fs.PathJoin(dir, "bar"))) 166 167 // Both files should contain equal contents, because they're backed by 168 // the same inode. 169 require.Equal(t, "foo", readFile("foo")) 170 require.Equal(t, "foo", readFile("bar")) 171 172 // Calling Create on 'bar' must NOT truncate 'foo'. It should create a 173 // new file at path 'bar' with a new inode. 174 writeFile("bar", "bar") 175 176 require.Equal(t, "foo", readFile("foo")) 177 require.Equal(t, "bar", readFile("bar")) 178 }) 179 } 180 }