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