github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/vfstest/fs.go (about)

     1  // Test suite for rclonefs
     2  
     3  package vfstest
     4  
     5  import (
     6  	"bufio"
     7  	"context"
     8  	"flag"
     9  	"fmt"
    10  	"io"
    11  	"log"
    12  	"os"
    13  	"os/exec"
    14  	"path"
    15  	"path/filepath"
    16  	"reflect"
    17  	"runtime"
    18  	"strings"
    19  	"sync"
    20  	"testing"
    21  	"time"
    22  
    23  	_ "github.com/rclone/rclone/backend/all" // import all the backends
    24  	"github.com/rclone/rclone/cmd/mountlib"
    25  	"github.com/rclone/rclone/fs"
    26  	"github.com/rclone/rclone/fs/walk"
    27  	"github.com/rclone/rclone/fstest"
    28  	"github.com/rclone/rclone/vfs/vfscommon"
    29  	"github.com/rclone/rclone/vfs/vfsflags"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  const (
    35  	waitForWritersDelay = 30 * time.Second // time to wait for existing writers
    36  )
    37  
    38  // RunTests runs all the tests against all the VFS cache modes
    39  //
    40  // If useVFS is set then it runs the tests against a VFS rather than a
    41  // mount
    42  //
    43  // If useVFS is not set then it runs the mount in a subprocess in
    44  // order to avoid kernel deadlocks.
    45  func RunTests(t *testing.T, useVFS bool, minimumRequiredCacheMode vfscommon.CacheMode, enableCacheTests bool, mountFn mountlib.MountFn) {
    46  	flag.Parse()
    47  	if isSubProcess() {
    48  		startMount(mountFn, useVFS, *runMount)
    49  		return
    50  	}
    51  	tests := []struct {
    52  		cacheMode vfscommon.CacheMode
    53  		writeBack time.Duration
    54  	}{
    55  		{cacheMode: vfscommon.CacheModeOff},
    56  		{cacheMode: vfscommon.CacheModeMinimal},
    57  		{cacheMode: vfscommon.CacheModeWrites},
    58  		{cacheMode: vfscommon.CacheModeFull},
    59  		{cacheMode: vfscommon.CacheModeFull, writeBack: 100 * time.Millisecond},
    60  	}
    61  	for _, test := range tests {
    62  		if test.cacheMode < minimumRequiredCacheMode {
    63  			continue
    64  		}
    65  		vfsOpt := vfsflags.Opt
    66  		vfsOpt.CacheMode = test.cacheMode
    67  		vfsOpt.WriteBack = test.writeBack
    68  		run = newRun(useVFS, &vfsOpt, mountFn)
    69  		what := fmt.Sprintf("CacheMode=%v", test.cacheMode)
    70  		if test.writeBack > 0 {
    71  			what += fmt.Sprintf(",WriteBack=%v", test.writeBack)
    72  		}
    73  		log.Printf("Starting test run with %s", what)
    74  		ok := t.Run(what, func(t *testing.T) {
    75  			t.Run("TestTouchAndDelete", TestTouchAndDelete)
    76  			t.Run("TestRenameOpenHandle", TestRenameOpenHandle)
    77  			t.Run("TestDirLs", TestDirLs)
    78  			t.Run("TestDirCreateAndRemoveDir", TestDirCreateAndRemoveDir)
    79  			t.Run("TestDirCreateAndRemoveFile", TestDirCreateAndRemoveFile)
    80  			t.Run("TestDirRenameFile", TestDirRenameFile)
    81  			t.Run("TestDirRenameEmptyDir", TestDirRenameEmptyDir)
    82  			t.Run("TestDirRenameFullDir", TestDirRenameFullDir)
    83  			t.Run("TestDirModTime", TestDirModTime)
    84  			if enableCacheTests {
    85  				t.Run("TestDirCacheFlush", TestDirCacheFlush)
    86  			}
    87  			t.Run("TestDirCacheFlushOnDirRename", TestDirCacheFlushOnDirRename)
    88  			t.Run("TestFileModTime", TestFileModTime)
    89  			t.Run("TestFileModTimeWithOpenWriters", TestFileModTimeWithOpenWriters)
    90  			t.Run("TestMount", TestMount)
    91  			t.Run("TestRoot", TestRoot)
    92  			t.Run("TestReadByByte", TestReadByByte)
    93  			t.Run("TestReadChecksum", TestReadChecksum)
    94  			t.Run("TestReadFileDoubleClose", TestReadFileDoubleClose)
    95  			t.Run("TestReadSeek", TestReadSeek)
    96  			t.Run("TestWriteFileNoWrite", TestWriteFileNoWrite)
    97  			t.Run("TestWriteFileWrite", TestWriteFileWrite)
    98  			t.Run("TestWriteFileOverwrite", TestWriteFileOverwrite)
    99  			t.Run("TestWriteFileDoubleClose", TestWriteFileDoubleClose)
   100  			t.Run("TestWriteFileFsync", TestWriteFileFsync)
   101  			t.Run("TestWriteFileDup", TestWriteFileDup)
   102  			t.Run("TestWriteFileAppend", TestWriteFileAppend)
   103  		})
   104  		log.Printf("Finished test run with %s (ok=%v)", what, ok)
   105  		run.Finalise()
   106  		if !ok {
   107  			break
   108  		}
   109  	}
   110  }
   111  
   112  // Run holds the remotes for a test run
   113  type Run struct {
   114  	os          Oser
   115  	vfsOpt      *vfscommon.Options
   116  	useVFS      bool // set if we are testing a VFS not a mount
   117  	mountPath   string
   118  	fremote     fs.Fs
   119  	fremoteName string
   120  	cleanRemote func()
   121  	skip        bool
   122  	// For controlling the subprocess running the mount
   123  	cmdMu   sync.Mutex
   124  	cmd     *exec.Cmd
   125  	in      io.ReadCloser
   126  	out     io.WriteCloser
   127  	scanner *bufio.Scanner
   128  }
   129  
   130  // run holds the master Run data
   131  var run *Run
   132  
   133  // newRun initialise the remote mount for testing and returns a run
   134  // object.
   135  //
   136  // r.fremote is an empty remote Fs
   137  //
   138  // Finalise() will tidy them away when done.
   139  func newRun(useVFS bool, vfsOpt *vfscommon.Options, mountFn mountlib.MountFn) *Run {
   140  	r := &Run{
   141  		useVFS: useVFS,
   142  		vfsOpt: vfsOpt,
   143  	}
   144  	r.vfsOpt.Init()
   145  	fstest.Initialise()
   146  
   147  	var err error
   148  	r.fremote, r.fremoteName, r.cleanRemote, err = fstest.RandomRemote()
   149  	if err != nil {
   150  		log.Fatalf("Failed to open remote %q: %v", *fstest.RemoteName, err)
   151  	}
   152  
   153  	err = r.fremote.Mkdir(context.Background(), "")
   154  	if err != nil {
   155  		log.Fatalf("Failed to open mkdir %q: %v", *fstest.RemoteName, err)
   156  	}
   157  
   158  	r.startMountSubProcess()
   159  	return r
   160  }
   161  
   162  func (r *Run) skipIfNoFUSE(t *testing.T) {
   163  	if r.skip {
   164  		t.Skip("FUSE not found so skipping test")
   165  	}
   166  }
   167  
   168  func (r *Run) skipIfVFS(t *testing.T) {
   169  	if r.useVFS {
   170  		t.Skip("Not running under VFS")
   171  	}
   172  }
   173  
   174  // Finalise cleans the remote and unmounts
   175  func (r *Run) Finalise() {
   176  	if !r.useVFS {
   177  		r.sendMountCommand("exit")
   178  		_, err := r.cmd.Process.Wait()
   179  		if err != nil {
   180  			log.Fatalf("mount sub process failed: %v", err)
   181  		}
   182  	}
   183  	r.cleanRemote()
   184  	if !r.useVFS {
   185  		err := os.RemoveAll(r.mountPath)
   186  		if err != nil {
   187  			log.Printf("Failed to clean mountPath %q: %v", r.mountPath, err)
   188  		}
   189  	}
   190  }
   191  
   192  // path returns an OS local path for filepath
   193  func (r *Run) path(filePath string) string {
   194  	if r.useVFS {
   195  		return filePath
   196  	}
   197  	// return windows drive letter root as E:\
   198  	if filePath == "" && runtime.GOOS == "windows" {
   199  		return r.mountPath + `\`
   200  	}
   201  	return filepath.Join(r.mountPath, filepath.FromSlash(filePath))
   202  }
   203  
   204  type dirMap map[string]struct{}
   205  
   206  // Create a dirMap from a string
   207  func newDirMap(dirString string) (dm dirMap) {
   208  	dm = make(dirMap)
   209  	for _, entry := range strings.Split(dirString, "|") {
   210  		if entry != "" {
   211  			dm[entry] = struct{}{}
   212  		}
   213  	}
   214  	return dm
   215  }
   216  
   217  // Returns a dirmap with only the files in
   218  func (dm dirMap) filesOnly() dirMap {
   219  	newDm := make(dirMap)
   220  	for name := range dm {
   221  		if !strings.HasSuffix(name, "/") {
   222  			newDm[name] = struct{}{}
   223  		}
   224  	}
   225  	return newDm
   226  }
   227  
   228  // reads the local tree into dir
   229  func (r *Run) readLocal(t *testing.T, dir dirMap, filePath string) {
   230  	realPath := r.path(filePath)
   231  	files, err := r.os.ReadDir(realPath)
   232  	require.NoError(t, err)
   233  	for _, fi := range files {
   234  		name := path.Join(filePath, fi.Name())
   235  		if fi.IsDir() {
   236  			dir[name+"/"] = struct{}{}
   237  			r.readLocal(t, dir, name)
   238  			assert.Equal(t, r.vfsOpt.DirPerms&os.ModePerm, fi.Mode().Perm())
   239  		} else {
   240  			dir[fmt.Sprintf("%s %d", name, fi.Size())] = struct{}{}
   241  			assert.Equal(t, r.vfsOpt.FilePerms&os.ModePerm, fi.Mode().Perm())
   242  		}
   243  	}
   244  }
   245  
   246  // reads the remote tree into dir
   247  func (r *Run) readRemote(t *testing.T, dir dirMap, filepath string) {
   248  	objs, dirs, err := walk.GetAll(context.Background(), r.fremote, filepath, true, 1)
   249  	if err == fs.ErrorDirNotFound {
   250  		return
   251  	}
   252  	require.NoError(t, err)
   253  	for _, obj := range objs {
   254  		dir[fmt.Sprintf("%s %d", obj.Remote(), obj.Size())] = struct{}{}
   255  	}
   256  	for _, d := range dirs {
   257  		name := d.Remote()
   258  		dir[name+"/"] = struct{}{}
   259  		r.readRemote(t, dir, name)
   260  	}
   261  }
   262  
   263  // checkDir checks the local and remote against the string passed in
   264  func (r *Run) checkDir(t *testing.T, dirString string) {
   265  	var retries = *fstest.ListRetries
   266  	sleep := time.Second / 5
   267  	var remoteOK, fuseOK bool
   268  	var dm, localDm, remoteDm dirMap
   269  	for i := 1; i <= retries; i++ {
   270  		dm = newDirMap(dirString)
   271  		localDm = make(dirMap)
   272  		r.readLocal(t, localDm, "")
   273  		remoteDm = make(dirMap)
   274  		r.readRemote(t, remoteDm, "")
   275  		// Ignore directories for remote compare
   276  		remoteOK = reflect.DeepEqual(dm.filesOnly(), remoteDm.filesOnly())
   277  		fuseOK = reflect.DeepEqual(dm, localDm)
   278  		if remoteOK && fuseOK {
   279  			return
   280  		}
   281  		sleep *= 2
   282  		t.Logf("Sleeping for %v for list eventual consistency: %d/%d", sleep, i, retries)
   283  		time.Sleep(sleep)
   284  	}
   285  	assert.Equal(t, dm.filesOnly(), remoteDm.filesOnly(), "expected vs remote")
   286  	assert.Equal(t, dm, localDm, "expected vs fuse mount")
   287  }
   288  
   289  // writeFile writes data to a file named by filename.
   290  // If the file does not exist, WriteFile creates it with permissions perm;
   291  // otherwise writeFile truncates it before writing.
   292  // If there is an error writing then writeFile
   293  // deletes it an existing file and tries again.
   294  func writeFile(filename string, data []byte, perm os.FileMode) error {
   295  	f, err := run.os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
   296  	if err != nil {
   297  		err = run.os.Remove(filename)
   298  		if err != nil {
   299  			return err
   300  		}
   301  		f, err = run.os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, perm)
   302  		if err != nil {
   303  			return err
   304  		}
   305  	}
   306  	n, err := f.Write(data)
   307  	if err == nil && n < len(data) {
   308  		err = io.ErrShortWrite
   309  	}
   310  	if err1 := f.Close(); err == nil {
   311  		err = err1
   312  	}
   313  	return err
   314  }
   315  
   316  func (r *Run) createFile(t *testing.T, filepath string, contents string) {
   317  	filepath = r.path(filepath)
   318  	err := writeFile(filepath, []byte(contents), 0644)
   319  	require.NoError(t, err)
   320  	r.waitForWriters()
   321  }
   322  
   323  func (r *Run) readFile(t *testing.T, filepath string) string {
   324  	filepath = r.path(filepath)
   325  	result, err := r.os.ReadFile(filepath)
   326  	require.NoError(t, err)
   327  	return string(result)
   328  }
   329  
   330  func (r *Run) mkdir(t *testing.T, filepath string) {
   331  	filepath = r.path(filepath)
   332  	err := r.os.Mkdir(filepath, 0755)
   333  	require.NoError(t, err)
   334  }
   335  
   336  func (r *Run) rm(t *testing.T, filepath string) {
   337  	filepath = r.path(filepath)
   338  	err := r.os.Remove(filepath)
   339  	require.NoError(t, err)
   340  
   341  	// Wait for file to disappear from listing
   342  	for i := 0; i < 100; i++ {
   343  		_, err := r.os.Stat(filepath)
   344  		if os.IsNotExist(err) {
   345  			return
   346  		}
   347  		time.Sleep(100 * time.Millisecond)
   348  	}
   349  	assert.Fail(t, "failed to delete file", filepath)
   350  }
   351  
   352  func (r *Run) rmdir(t *testing.T, filepath string) {
   353  	filepath = r.path(filepath)
   354  	err := r.os.Remove(filepath)
   355  	require.NoError(t, err)
   356  }
   357  
   358  // TestMount checks that the Fs is mounted by seeing if the mountpoint
   359  // is in the mount output
   360  func TestMount(t *testing.T) {
   361  	run.skipIfVFS(t)
   362  	run.skipIfNoFUSE(t)
   363  	if runtime.GOOS == "windows" {
   364  		t.Skip("not running on windows")
   365  	}
   366  
   367  	out, err := exec.Command("mount").Output()
   368  	require.NoError(t, err)
   369  	assert.Contains(t, string(out), run.mountPath)
   370  }
   371  
   372  // TestRoot checks root directory is present and correct
   373  func TestRoot(t *testing.T) {
   374  	run.skipIfVFS(t)
   375  	run.skipIfNoFUSE(t)
   376  
   377  	fi, err := os.Lstat(run.mountPath)
   378  	require.NoError(t, err)
   379  	assert.True(t, fi.IsDir())
   380  	assert.Equal(t, run.vfsOpt.DirPerms&os.ModePerm, fi.Mode().Perm())
   381  }