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