github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/vfs/vfstest/fs.go (about) 1 // Test suite for rclonefs 2 3 package vfstest 4 5 import ( 6 "context" 7 "flag" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "log" 12 "os" 13 "os/exec" 14 "path" 15 "path/filepath" 16 "reflect" 17 "runtime" 18 "strings" 19 "testing" 20 "time" 21 22 _ "github.com/rclone/rclone/backend/all" // import all the backends 23 "github.com/rclone/rclone/fs" 24 "github.com/rclone/rclone/fs/walk" 25 "github.com/rclone/rclone/fstest" 26 "github.com/rclone/rclone/vfs" 27 "github.com/rclone/rclone/vfs/vfscommon" 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 ) 31 32 type ( 33 // UnmountFn is called to unmount the file system 34 UnmountFn func() error 35 // MountFn is called to mount the file system 36 MountFn func(f fs.Fs, mountpoint string) (vfs *vfs.VFS, unmountResult <-chan error, unmount func() error, err error) 37 ) 38 39 var ( 40 mountFn MountFn 41 ) 42 43 // RunTests runs all the tests against all the VFS cache modes 44 // 45 // If useVFS is set then it runs the tests against a VFS rather than amount 46 func RunTests(t *testing.T, useVFS bool, fn MountFn) { 47 mountFn = fn 48 flag.Parse() 49 cacheModes := []vfscommon.CacheMode{ 50 vfscommon.CacheModeOff, 51 vfscommon.CacheModeMinimal, 52 vfscommon.CacheModeWrites, 53 vfscommon.CacheModeFull, 54 } 55 run = newRun(useVFS) 56 for _, cacheMode := range cacheModes { 57 run.cacheMode(cacheMode) 58 log.Printf("Starting test run with cache mode %v", cacheMode) 59 ok := t.Run(fmt.Sprintf("CacheMode=%v", cacheMode), func(t *testing.T) { 60 t.Run("TestTouchAndDelete", TestTouchAndDelete) 61 t.Run("TestRenameOpenHandle", TestRenameOpenHandle) 62 t.Run("TestDirLs", TestDirLs) 63 t.Run("TestDirCreateAndRemoveDir", TestDirCreateAndRemoveDir) 64 t.Run("TestDirCreateAndRemoveFile", TestDirCreateAndRemoveFile) 65 t.Run("TestDirRenameFile", TestDirRenameFile) 66 t.Run("TestDirRenameEmptyDir", TestDirRenameEmptyDir) 67 t.Run("TestDirRenameFullDir", TestDirRenameFullDir) 68 t.Run("TestDirModTime", TestDirModTime) 69 t.Run("TestDirCacheFlush", TestDirCacheFlush) 70 t.Run("TestDirCacheFlushOnDirRename", TestDirCacheFlushOnDirRename) 71 t.Run("TestFileModTime", TestFileModTime) 72 t.Run("TestFileModTimeWithOpenWriters", TestFileModTimeWithOpenWriters) 73 t.Run("TestMount", TestMount) 74 t.Run("TestRoot", TestRoot) 75 t.Run("TestReadByByte", TestReadByByte) 76 t.Run("TestReadChecksum", TestReadChecksum) 77 t.Run("TestReadFileDoubleClose", TestReadFileDoubleClose) 78 t.Run("TestReadSeek", TestReadSeek) 79 t.Run("TestWriteFileNoWrite", TestWriteFileNoWrite) 80 t.Run("TestWriteFileWrite", TestWriteFileWrite) 81 t.Run("TestWriteFileOverwrite", TestWriteFileOverwrite) 82 t.Run("TestWriteFileDoubleClose", TestWriteFileDoubleClose) 83 t.Run("TestWriteFileFsync", TestWriteFileFsync) 84 t.Run("TestWriteFileDup", TestWriteFileDup) 85 t.Run("TestWriteFileAppend", TestWriteFileAppend) 86 }) 87 log.Printf("Finished test run with cache mode %v (ok=%v)", cacheMode, ok) 88 if !ok { 89 break 90 } 91 } 92 run.Finalise() 93 } 94 95 // Run holds the remotes for a test run 96 type Run struct { 97 os Oser 98 vfs *vfs.VFS 99 useVFS bool // set if we are testing a VFS not a mount 100 mountPath string 101 fremote fs.Fs 102 fremoteName string 103 cleanRemote func() 104 umountResult <-chan error 105 umountFn UnmountFn 106 skip bool 107 } 108 109 // run holds the master Run data 110 var run *Run 111 112 // newRun initialise the remote mount for testing and returns a run 113 // object. 114 // 115 // r.fremote is an empty remote Fs 116 // 117 // Finalise() will tidy them away when done. 118 func newRun(useVFS bool) *Run { 119 r := &Run{ 120 useVFS: useVFS, 121 umountResult: make(chan error, 1), 122 } 123 fstest.Initialise() 124 125 var err error 126 r.fremote, r.fremoteName, r.cleanRemote, err = fstest.RandomRemote() 127 if err != nil { 128 log.Fatalf("Failed to open remote %q: %v", *fstest.RemoteName, err) 129 } 130 131 err = r.fremote.Mkdir(context.Background(), "") 132 if err != nil { 133 log.Fatalf("Failed to open mkdir %q: %v", *fstest.RemoteName, err) 134 } 135 136 if !r.useVFS { 137 r.mountPath = findMountPath() 138 } 139 // Mount it up 140 r.mount() 141 142 return r 143 } 144 145 func findMountPath() string { 146 if runtime.GOOS != "windows" { 147 mountPath, err := ioutil.TempDir("", "rclonefs-mount") 148 if err != nil { 149 log.Fatalf("Failed to create mount dir: %v", err) 150 } 151 return mountPath 152 } 153 154 // Find a free drive letter 155 drive := "" 156 for letter := 'E'; letter <= 'Z'; letter++ { 157 drive = string(letter) + ":" 158 _, err := os.Stat(drive + "\\") 159 if os.IsNotExist(err) { 160 goto found 161 } 162 } 163 log.Fatalf("Couldn't find free drive letter for test") 164 found: 165 return drive 166 } 167 168 func (r *Run) mount() { 169 log.Printf("mount %q %q", r.fremote, r.mountPath) 170 var err error 171 r.vfs, r.umountResult, r.umountFn, err = mountFn(r.fremote, r.mountPath) 172 if err != nil { 173 log.Printf("mount FAILED: %v", err) 174 r.skip = true 175 } else { 176 log.Printf("mount OK") 177 } 178 if r.useVFS { 179 r.os = vfsOs{r.vfs} 180 } else { 181 r.os = realOs{} 182 } 183 184 } 185 186 func (r *Run) umount() { 187 if r.skip { 188 log.Printf("FUSE not found so skipping umount") 189 return 190 } 191 /* 192 log.Printf("Calling fusermount -u %q", r.mountPath) 193 err := exec.Command("fusermount", "-u", r.mountPath).Run() 194 if err != nil { 195 log.Printf("fusermount failed: %v", err) 196 } 197 */ 198 log.Printf("Unmounting %q", r.mountPath) 199 err := r.umountFn() 200 if err != nil { 201 log.Printf("signal to umount failed - retrying: %v", err) 202 time.Sleep(3 * time.Second) 203 err = r.umountFn() 204 } 205 if err != nil { 206 log.Fatalf("signal to umount failed: %v", err) 207 } 208 log.Printf("Waiting for umount") 209 err = <-r.umountResult 210 if err != nil { 211 log.Fatalf("umount failed: %v", err) 212 } 213 214 // Cleanup the VFS cache - umount has called Shutdown 215 err = r.vfs.CleanUp() 216 if err != nil { 217 log.Printf("Failed to cleanup the VFS cache: %v", err) 218 } 219 } 220 221 // cacheMode flushes the VFS and changes the CacheMode 222 func (r *Run) cacheMode(cacheMode vfscommon.CacheMode) { 223 if r.skip { 224 log.Printf("FUSE not found so skipping cacheMode") 225 return 226 } 227 // Wait for writers to finish 228 r.vfs.WaitForWriters(30 * time.Second) 229 // Empty and remake the remote 230 r.cleanRemote() 231 err := r.fremote.Mkdir(context.Background(), "") 232 if err != nil { 233 log.Fatalf("Failed to open mkdir %q: %v", *fstest.RemoteName, err) 234 } 235 // Empty the cache 236 err = r.vfs.CleanUp() 237 if err != nil { 238 log.Printf("Failed to cleanup the VFS cache: %v", err) 239 } 240 // Reset the cache mode 241 r.vfs.SetCacheMode(cacheMode) 242 // Flush the directory cache 243 r.vfs.FlushDirCache() 244 245 } 246 247 func (r *Run) skipIfNoFUSE(t *testing.T) { 248 if r.skip { 249 t.Skip("FUSE not found so skipping test") 250 } 251 } 252 253 func (r *Run) skipIfVFS(t *testing.T) { 254 if r.useVFS { 255 t.Skip("Not running under VFS") 256 } 257 } 258 259 // Finalise cleans the remote and unmounts 260 func (r *Run) Finalise() { 261 r.umount() 262 r.cleanRemote() 263 if r.useVFS { 264 // FIXME 265 } else { 266 err := os.RemoveAll(r.mountPath) 267 if err != nil { 268 log.Printf("Failed to clean mountPath %q: %v", r.mountPath, err) 269 } 270 } 271 } 272 273 // path returns an OS local path for filepath 274 func (r *Run) path(filePath string) string { 275 if r.useVFS { 276 return filePath 277 } 278 // return windows drive letter root as E:\ 279 if filePath == "" && runtime.GOOS == "windows" { 280 return run.mountPath + `\` 281 } 282 return filepath.Join(run.mountPath, filepath.FromSlash(filePath)) 283 } 284 285 type dirMap map[string]struct{} 286 287 // Create a dirMap from a string 288 func newDirMap(dirString string) (dm dirMap) { 289 dm = make(dirMap) 290 for _, entry := range strings.Split(dirString, "|") { 291 if entry != "" { 292 dm[entry] = struct{}{} 293 } 294 } 295 return dm 296 } 297 298 // Returns a dirmap with only the files in 299 func (dm dirMap) filesOnly() dirMap { 300 newDm := make(dirMap) 301 for name := range dm { 302 if !strings.HasSuffix(name, "/") { 303 newDm[name] = struct{}{} 304 } 305 } 306 return newDm 307 } 308 309 // reads the local tree into dir 310 func (r *Run) readLocal(t *testing.T, dir dirMap, filePath string) { 311 realPath := r.path(filePath) 312 files, err := r.os.ReadDir(realPath) 313 require.NoError(t, err) 314 for _, fi := range files { 315 name := path.Join(filePath, fi.Name()) 316 if fi.IsDir() { 317 dir[name+"/"] = struct{}{} 318 r.readLocal(t, dir, name) 319 assert.Equal(t, run.vfs.Opt.DirPerms&os.ModePerm, fi.Mode().Perm()) 320 } else { 321 dir[fmt.Sprintf("%s %d", name, fi.Size())] = struct{}{} 322 assert.Equal(t, run.vfs.Opt.FilePerms&os.ModePerm, fi.Mode().Perm()) 323 } 324 } 325 } 326 327 // reads the remote tree into dir 328 func (r *Run) readRemote(t *testing.T, dir dirMap, filepath string) { 329 objs, dirs, err := walk.GetAll(context.Background(), r.fremote, filepath, true, 1) 330 if err == fs.ErrorDirNotFound { 331 return 332 } 333 require.NoError(t, err) 334 for _, obj := range objs { 335 dir[fmt.Sprintf("%s %d", obj.Remote(), obj.Size())] = struct{}{} 336 } 337 for _, d := range dirs { 338 name := d.Remote() 339 dir[name+"/"] = struct{}{} 340 r.readRemote(t, dir, name) 341 } 342 } 343 344 // checkDir checks the local and remote against the string passed in 345 func (r *Run) checkDir(t *testing.T, dirString string) { 346 var retries = *fstest.ListRetries 347 sleep := time.Second / 5 348 var remoteOK, fuseOK bool 349 var dm, localDm, remoteDm dirMap 350 for i := 1; i <= retries; i++ { 351 dm = newDirMap(dirString) 352 localDm = make(dirMap) 353 r.readLocal(t, localDm, "") 354 remoteDm = make(dirMap) 355 r.readRemote(t, remoteDm, "") 356 // Ignore directories for remote compare 357 remoteOK = reflect.DeepEqual(dm.filesOnly(), remoteDm.filesOnly()) 358 fuseOK = reflect.DeepEqual(dm, localDm) 359 if remoteOK && fuseOK { 360 return 361 } 362 sleep *= 2 363 t.Logf("Sleeping for %v for list eventual consistency: %d/%d", sleep, i, retries) 364 time.Sleep(sleep) 365 } 366 assert.Equal(t, dm.filesOnly(), remoteDm.filesOnly(), "expected vs remote") 367 assert.Equal(t, dm, localDm, "expected vs fuse mount") 368 } 369 370 // wait for any files being written to be released by fuse 371 func (r *Run) waitForWriters() { 372 run.vfs.WaitForWriters(10 * time.Second) 373 } 374 375 // writeFile writes data to a file named by filename. 376 // If the file does not exist, WriteFile creates it with permissions perm; 377 // otherwise writeFile truncates it before writing. 378 // If there is an error writing then writeFile 379 // deletes it an existing file and tries again. 380 func writeFile(filename string, data []byte, perm os.FileMode) error { 381 f, err := run.os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) 382 if err != nil { 383 err = run.os.Remove(filename) 384 if err != nil { 385 return err 386 } 387 f, err = run.os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, perm) 388 if err != nil { 389 return err 390 } 391 } 392 n, err := f.Write(data) 393 if err == nil && n < len(data) { 394 err = io.ErrShortWrite 395 } 396 if err1 := f.Close(); err == nil { 397 err = err1 398 } 399 return err 400 } 401 402 func (r *Run) createFile(t *testing.T, filepath string, contents string) { 403 filepath = r.path(filepath) 404 err := writeFile(filepath, []byte(contents), 0600) 405 require.NoError(t, err) 406 r.waitForWriters() 407 } 408 409 func (r *Run) readFile(t *testing.T, filepath string) string { 410 filepath = r.path(filepath) 411 result, err := run.os.ReadFile(filepath) 412 require.NoError(t, err) 413 time.Sleep(100 * time.Millisecond) // FIXME wait for Release 414 return string(result) 415 } 416 417 func (r *Run) mkdir(t *testing.T, filepath string) { 418 filepath = r.path(filepath) 419 err := run.os.Mkdir(filepath, 0700) 420 require.NoError(t, err) 421 } 422 423 func (r *Run) rm(t *testing.T, filepath string) { 424 filepath = r.path(filepath) 425 err := run.os.Remove(filepath) 426 require.NoError(t, err) 427 428 // Wait for file to disappear from listing 429 for i := 0; i < 100; i++ { 430 _, err := run.os.Stat(filepath) 431 if os.IsNotExist(err) { 432 return 433 } 434 time.Sleep(100 * time.Millisecond) 435 } 436 assert.Fail(t, "failed to delete file", filepath) 437 } 438 439 func (r *Run) rmdir(t *testing.T, filepath string) { 440 filepath = r.path(filepath) 441 err := run.os.Remove(filepath) 442 require.NoError(t, err) 443 } 444 445 // TestMount checks that the Fs is mounted by seeing if the mountpoint 446 // is in the mount output 447 func TestMount(t *testing.T) { 448 run.skipIfVFS(t) 449 run.skipIfNoFUSE(t) 450 if runtime.GOOS == "windows" { 451 t.Skip("not running on windows") 452 } 453 454 out, err := exec.Command("mount").Output() 455 require.NoError(t, err) 456 assert.Contains(t, string(out), run.mountPath) 457 } 458 459 // TestRoot checks root directory is present and correct 460 func TestRoot(t *testing.T) { 461 run.skipIfVFS(t) 462 run.skipIfNoFUSE(t) 463 464 fi, err := os.Lstat(run.mountPath) 465 require.NoError(t, err) 466 assert.True(t, fi.IsDir()) 467 assert.Equal(t, run.vfs.Opt.DirPerms&os.ModePerm, fi.Mode().Perm()) 468 }