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  }