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  }