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  }