github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/vfstest/fs.go (about) 1 // Test suite for rclonefs 2 3 package vfstest 4 5 import ( 6 "bufio" 7 "context" 8 "flag" 9 "fmt" 10 "io" 11 "log" 12 "os" 13 "os/exec" 14 "path" 15 "path/filepath" 16 "reflect" 17 "runtime" 18 "strings" 19 "sync" 20 "testing" 21 "time" 22 23 _ "github.com/rclone/rclone/backend/all" // import all the backends 24 "github.com/rclone/rclone/cmd/mountlib" 25 "github.com/rclone/rclone/fs" 26 "github.com/rclone/rclone/fs/walk" 27 "github.com/rclone/rclone/fstest" 28 "github.com/rclone/rclone/vfs/vfscommon" 29 "github.com/rclone/rclone/vfs/vfsflags" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 ) 33 34 const ( 35 waitForWritersDelay = 30 * time.Second // time to wait for existing writers 36 ) 37 38 // RunTests runs all the tests against all the VFS cache modes 39 // 40 // If useVFS is set then it runs the tests against a VFS rather than a 41 // mount 42 // 43 // If useVFS is not set then it runs the mount in a subprocess in 44 // order to avoid kernel deadlocks. 45 func RunTests(t *testing.T, useVFS bool, minimumRequiredCacheMode vfscommon.CacheMode, enableCacheTests bool, mountFn mountlib.MountFn) { 46 flag.Parse() 47 if isSubProcess() { 48 startMount(mountFn, useVFS, *runMount) 49 return 50 } 51 tests := []struct { 52 cacheMode vfscommon.CacheMode 53 writeBack time.Duration 54 }{ 55 {cacheMode: vfscommon.CacheModeOff}, 56 {cacheMode: vfscommon.CacheModeMinimal}, 57 {cacheMode: vfscommon.CacheModeWrites}, 58 {cacheMode: vfscommon.CacheModeFull}, 59 {cacheMode: vfscommon.CacheModeFull, writeBack: 100 * time.Millisecond}, 60 } 61 for _, test := range tests { 62 if test.cacheMode < minimumRequiredCacheMode { 63 continue 64 } 65 vfsOpt := vfsflags.Opt 66 vfsOpt.CacheMode = test.cacheMode 67 vfsOpt.WriteBack = test.writeBack 68 run = newRun(useVFS, &vfsOpt, mountFn) 69 what := fmt.Sprintf("CacheMode=%v", test.cacheMode) 70 if test.writeBack > 0 { 71 what += fmt.Sprintf(",WriteBack=%v", test.writeBack) 72 } 73 log.Printf("Starting test run with %s", what) 74 ok := t.Run(what, func(t *testing.T) { 75 t.Run("TestTouchAndDelete", TestTouchAndDelete) 76 t.Run("TestRenameOpenHandle", TestRenameOpenHandle) 77 t.Run("TestDirLs", TestDirLs) 78 t.Run("TestDirCreateAndRemoveDir", TestDirCreateAndRemoveDir) 79 t.Run("TestDirCreateAndRemoveFile", TestDirCreateAndRemoveFile) 80 t.Run("TestDirRenameFile", TestDirRenameFile) 81 t.Run("TestDirRenameEmptyDir", TestDirRenameEmptyDir) 82 t.Run("TestDirRenameFullDir", TestDirRenameFullDir) 83 t.Run("TestDirModTime", TestDirModTime) 84 if enableCacheTests { 85 t.Run("TestDirCacheFlush", TestDirCacheFlush) 86 } 87 t.Run("TestDirCacheFlushOnDirRename", TestDirCacheFlushOnDirRename) 88 t.Run("TestFileModTime", TestFileModTime) 89 t.Run("TestFileModTimeWithOpenWriters", TestFileModTimeWithOpenWriters) 90 t.Run("TestMount", TestMount) 91 t.Run("TestRoot", TestRoot) 92 t.Run("TestReadByByte", TestReadByByte) 93 t.Run("TestReadChecksum", TestReadChecksum) 94 t.Run("TestReadFileDoubleClose", TestReadFileDoubleClose) 95 t.Run("TestReadSeek", TestReadSeek) 96 t.Run("TestWriteFileNoWrite", TestWriteFileNoWrite) 97 t.Run("TestWriteFileWrite", TestWriteFileWrite) 98 t.Run("TestWriteFileOverwrite", TestWriteFileOverwrite) 99 t.Run("TestWriteFileDoubleClose", TestWriteFileDoubleClose) 100 t.Run("TestWriteFileFsync", TestWriteFileFsync) 101 t.Run("TestWriteFileDup", TestWriteFileDup) 102 t.Run("TestWriteFileAppend", TestWriteFileAppend) 103 }) 104 log.Printf("Finished test run with %s (ok=%v)", what, ok) 105 run.Finalise() 106 if !ok { 107 break 108 } 109 } 110 } 111 112 // Run holds the remotes for a test run 113 type Run struct { 114 os Oser 115 vfsOpt *vfscommon.Options 116 useVFS bool // set if we are testing a VFS not a mount 117 mountPath string 118 fremote fs.Fs 119 fremoteName string 120 cleanRemote func() 121 skip bool 122 // For controlling the subprocess running the mount 123 cmdMu sync.Mutex 124 cmd *exec.Cmd 125 in io.ReadCloser 126 out io.WriteCloser 127 scanner *bufio.Scanner 128 } 129 130 // run holds the master Run data 131 var run *Run 132 133 // newRun initialise the remote mount for testing and returns a run 134 // object. 135 // 136 // r.fremote is an empty remote Fs 137 // 138 // Finalise() will tidy them away when done. 139 func newRun(useVFS bool, vfsOpt *vfscommon.Options, mountFn mountlib.MountFn) *Run { 140 r := &Run{ 141 useVFS: useVFS, 142 vfsOpt: vfsOpt, 143 } 144 r.vfsOpt.Init() 145 fstest.Initialise() 146 147 var err error 148 r.fremote, r.fremoteName, r.cleanRemote, err = fstest.RandomRemote() 149 if err != nil { 150 log.Fatalf("Failed to open remote %q: %v", *fstest.RemoteName, err) 151 } 152 153 err = r.fremote.Mkdir(context.Background(), "") 154 if err != nil { 155 log.Fatalf("Failed to open mkdir %q: %v", *fstest.RemoteName, err) 156 } 157 158 r.startMountSubProcess() 159 return r 160 } 161 162 func (r *Run) skipIfNoFUSE(t *testing.T) { 163 if r.skip { 164 t.Skip("FUSE not found so skipping test") 165 } 166 } 167 168 func (r *Run) skipIfVFS(t *testing.T) { 169 if r.useVFS { 170 t.Skip("Not running under VFS") 171 } 172 } 173 174 // Finalise cleans the remote and unmounts 175 func (r *Run) Finalise() { 176 if !r.useVFS { 177 r.sendMountCommand("exit") 178 _, err := r.cmd.Process.Wait() 179 if err != nil { 180 log.Fatalf("mount sub process failed: %v", err) 181 } 182 } 183 r.cleanRemote() 184 if !r.useVFS { 185 err := os.RemoveAll(r.mountPath) 186 if err != nil { 187 log.Printf("Failed to clean mountPath %q: %v", r.mountPath, err) 188 } 189 } 190 } 191 192 // path returns an OS local path for filepath 193 func (r *Run) path(filePath string) string { 194 if r.useVFS { 195 return filePath 196 } 197 // return windows drive letter root as E:\ 198 if filePath == "" && runtime.GOOS == "windows" { 199 return r.mountPath + `\` 200 } 201 return filepath.Join(r.mountPath, filepath.FromSlash(filePath)) 202 } 203 204 type dirMap map[string]struct{} 205 206 // Create a dirMap from a string 207 func newDirMap(dirString string) (dm dirMap) { 208 dm = make(dirMap) 209 for _, entry := range strings.Split(dirString, "|") { 210 if entry != "" { 211 dm[entry] = struct{}{} 212 } 213 } 214 return dm 215 } 216 217 // Returns a dirmap with only the files in 218 func (dm dirMap) filesOnly() dirMap { 219 newDm := make(dirMap) 220 for name := range dm { 221 if !strings.HasSuffix(name, "/") { 222 newDm[name] = struct{}{} 223 } 224 } 225 return newDm 226 } 227 228 // reads the local tree into dir 229 func (r *Run) readLocal(t *testing.T, dir dirMap, filePath string) { 230 realPath := r.path(filePath) 231 files, err := r.os.ReadDir(realPath) 232 require.NoError(t, err) 233 for _, fi := range files { 234 name := path.Join(filePath, fi.Name()) 235 if fi.IsDir() { 236 dir[name+"/"] = struct{}{} 237 r.readLocal(t, dir, name) 238 assert.Equal(t, r.vfsOpt.DirPerms&os.ModePerm, fi.Mode().Perm()) 239 } else { 240 dir[fmt.Sprintf("%s %d", name, fi.Size())] = struct{}{} 241 assert.Equal(t, r.vfsOpt.FilePerms&os.ModePerm, fi.Mode().Perm()) 242 } 243 } 244 } 245 246 // reads the remote tree into dir 247 func (r *Run) readRemote(t *testing.T, dir dirMap, filepath string) { 248 objs, dirs, err := walk.GetAll(context.Background(), r.fremote, filepath, true, 1) 249 if err == fs.ErrorDirNotFound { 250 return 251 } 252 require.NoError(t, err) 253 for _, obj := range objs { 254 dir[fmt.Sprintf("%s %d", obj.Remote(), obj.Size())] = struct{}{} 255 } 256 for _, d := range dirs { 257 name := d.Remote() 258 dir[name+"/"] = struct{}{} 259 r.readRemote(t, dir, name) 260 } 261 } 262 263 // checkDir checks the local and remote against the string passed in 264 func (r *Run) checkDir(t *testing.T, dirString string) { 265 var retries = *fstest.ListRetries 266 sleep := time.Second / 5 267 var remoteOK, fuseOK bool 268 var dm, localDm, remoteDm dirMap 269 for i := 1; i <= retries; i++ { 270 dm = newDirMap(dirString) 271 localDm = make(dirMap) 272 r.readLocal(t, localDm, "") 273 remoteDm = make(dirMap) 274 r.readRemote(t, remoteDm, "") 275 // Ignore directories for remote compare 276 remoteOK = reflect.DeepEqual(dm.filesOnly(), remoteDm.filesOnly()) 277 fuseOK = reflect.DeepEqual(dm, localDm) 278 if remoteOK && fuseOK { 279 return 280 } 281 sleep *= 2 282 t.Logf("Sleeping for %v for list eventual consistency: %d/%d", sleep, i, retries) 283 time.Sleep(sleep) 284 } 285 assert.Equal(t, dm.filesOnly(), remoteDm.filesOnly(), "expected vs remote") 286 assert.Equal(t, dm, localDm, "expected vs fuse mount") 287 } 288 289 // writeFile writes data to a file named by filename. 290 // If the file does not exist, WriteFile creates it with permissions perm; 291 // otherwise writeFile truncates it before writing. 292 // If there is an error writing then writeFile 293 // deletes it an existing file and tries again. 294 func writeFile(filename string, data []byte, perm os.FileMode) error { 295 f, err := run.os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) 296 if err != nil { 297 err = run.os.Remove(filename) 298 if err != nil { 299 return err 300 } 301 f, err = run.os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, perm) 302 if err != nil { 303 return err 304 } 305 } 306 n, err := f.Write(data) 307 if err == nil && n < len(data) { 308 err = io.ErrShortWrite 309 } 310 if err1 := f.Close(); err == nil { 311 err = err1 312 } 313 return err 314 } 315 316 func (r *Run) createFile(t *testing.T, filepath string, contents string) { 317 filepath = r.path(filepath) 318 err := writeFile(filepath, []byte(contents), 0644) 319 require.NoError(t, err) 320 r.waitForWriters() 321 } 322 323 func (r *Run) readFile(t *testing.T, filepath string) string { 324 filepath = r.path(filepath) 325 result, err := r.os.ReadFile(filepath) 326 require.NoError(t, err) 327 return string(result) 328 } 329 330 func (r *Run) mkdir(t *testing.T, filepath string) { 331 filepath = r.path(filepath) 332 err := r.os.Mkdir(filepath, 0755) 333 require.NoError(t, err) 334 } 335 336 func (r *Run) rm(t *testing.T, filepath string) { 337 filepath = r.path(filepath) 338 err := r.os.Remove(filepath) 339 require.NoError(t, err) 340 341 // Wait for file to disappear from listing 342 for i := 0; i < 100; i++ { 343 _, err := r.os.Stat(filepath) 344 if os.IsNotExist(err) { 345 return 346 } 347 time.Sleep(100 * time.Millisecond) 348 } 349 assert.Fail(t, "failed to delete file", filepath) 350 } 351 352 func (r *Run) rmdir(t *testing.T, filepath string) { 353 filepath = r.path(filepath) 354 err := r.os.Remove(filepath) 355 require.NoError(t, err) 356 } 357 358 // TestMount checks that the Fs is mounted by seeing if the mountpoint 359 // is in the mount output 360 func TestMount(t *testing.T) { 361 run.skipIfVFS(t) 362 run.skipIfNoFUSE(t) 363 if runtime.GOOS == "windows" { 364 t.Skip("not running on windows") 365 } 366 367 out, err := exec.Command("mount").Output() 368 require.NoError(t, err) 369 assert.Contains(t, string(out), run.mountPath) 370 } 371 372 // TestRoot checks root directory is present and correct 373 func TestRoot(t *testing.T) { 374 run.skipIfVFS(t) 375 run.skipIfNoFUSE(t) 376 377 fi, err := os.Lstat(run.mountPath) 378 require.NoError(t, err) 379 assert.True(t, fi.IsDir()) 380 assert.Equal(t, run.vfsOpt.DirPerms&os.ModePerm, fi.Mode().Perm()) 381 }