github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/fstest/run.go (about)

     1  /*
     2  
     3  This provides Run for use in creating test suites
     4  
     5  To use this declare a TestMain
     6  
     7  // TestMain drives the tests
     8  func TestMain(m *testing.M) {
     9  	fstest.TestMain(m)
    10  }
    11  
    12  And then make and destroy a Run in each test
    13  
    14  func TestMkdir(t *testing.T) {
    15  	r := fstest.NewRun(t)
    16  	defer r.Finalise()
    17  	// test stuff
    18  }
    19  
    20  This will make r.Fremote and r.Flocal for a remote remote and a local
    21  remote.  The remote is determined by the -remote flag passed in.
    22  
    23  */
    24  
    25  package fstest
    26  
    27  import (
    28  	"bytes"
    29  	"context"
    30  	"flag"
    31  	"fmt"
    32  	"io/ioutil"
    33  	"log"
    34  	"os"
    35  	"path"
    36  	"path/filepath"
    37  	"sort"
    38  	"testing"
    39  	"time"
    40  
    41  	"github.com/rclone/rclone/fs"
    42  	"github.com/rclone/rclone/fs/cache"
    43  	"github.com/rclone/rclone/fs/fserrors"
    44  	"github.com/rclone/rclone/fs/hash"
    45  	"github.com/rclone/rclone/fs/object"
    46  	"github.com/rclone/rclone/fs/walk"
    47  	"github.com/stretchr/testify/assert"
    48  	"github.com/stretchr/testify/require"
    49  )
    50  
    51  // Run holds the remotes for a test run
    52  type Run struct {
    53  	LocalName    string
    54  	Flocal       fs.Fs
    55  	Fremote      fs.Fs
    56  	FremoteName  string
    57  	cleanRemote  func()
    58  	mkdir        map[string]bool // whether the remote has been made yet for the fs name
    59  	Logf, Fatalf func(text string, args ...interface{})
    60  }
    61  
    62  // TestMain drives the tests
    63  func TestMain(m *testing.M) {
    64  	flag.Parse()
    65  	if !*Individual {
    66  		oneRun = newRun()
    67  	}
    68  	rc := m.Run()
    69  	if !*Individual {
    70  		oneRun.Finalise()
    71  	}
    72  	os.Exit(rc)
    73  }
    74  
    75  // oneRun holds the master run data if individual is not set
    76  var oneRun *Run
    77  
    78  // newRun initialise the remote and local for testing and returns a
    79  // run object.
    80  //
    81  // r.Flocal is an empty local Fs
    82  // r.Fremote is an empty remote Fs
    83  //
    84  // Finalise() will tidy them away when done.
    85  func newRun() *Run {
    86  	r := &Run{
    87  		Logf:   log.Printf,
    88  		Fatalf: log.Fatalf,
    89  		mkdir:  make(map[string]bool),
    90  	}
    91  
    92  	Initialise()
    93  
    94  	var err error
    95  	r.Fremote, r.FremoteName, r.cleanRemote, err = RandomRemote()
    96  	if err != nil {
    97  		r.Fatalf("Failed to open remote %q: %v", *RemoteName, err)
    98  	}
    99  
   100  	r.LocalName, err = ioutil.TempDir("", "rclone")
   101  	if err != nil {
   102  		r.Fatalf("Failed to create temp dir: %v", err)
   103  	}
   104  	r.LocalName = filepath.ToSlash(r.LocalName)
   105  	r.Flocal, err = fs.NewFs(r.LocalName)
   106  	if err != nil {
   107  		r.Fatalf("Failed to make %q: %v", r.LocalName, err)
   108  	}
   109  	return r
   110  }
   111  
   112  // run f(), retrying it until it returns with no error or the limit
   113  // expires and it calls t.Fatalf
   114  func retry(t *testing.T, what string, f func() error) {
   115  	var err error
   116  	for try := 1; try <= *ListRetries; try++ {
   117  		err = f()
   118  		if err == nil {
   119  			return
   120  		}
   121  		t.Logf("%s failed - try %d/%d: %v", what, try, *ListRetries, err)
   122  		time.Sleep(time.Second)
   123  	}
   124  	t.Logf("%s failed: %v", what, err)
   125  }
   126  
   127  // newRunIndividual initialise the remote and local for testing and
   128  // returns a run object.  Pass in true to make individual tests or
   129  // false to use the global one.
   130  //
   131  // r.Flocal is an empty local Fs
   132  // r.Fremote is an empty remote Fs
   133  //
   134  // Finalise() will tidy them away when done.
   135  func newRunIndividual(t *testing.T, individual bool) *Run {
   136  	ctx := context.Background()
   137  	var r *Run
   138  	if individual {
   139  		r = newRun()
   140  	} else {
   141  		// If not individual, use the global one with the clean method overridden
   142  		r = new(Run)
   143  		*r = *oneRun
   144  		r.cleanRemote = func() {
   145  			var toDelete []string
   146  			err := walk.ListR(ctx, r.Fremote, "", true, -1, walk.ListAll, func(entries fs.DirEntries) error {
   147  				for _, entry := range entries {
   148  					switch x := entry.(type) {
   149  					case fs.Object:
   150  						retry(t, fmt.Sprintf("removing file %q", x.Remote()), func() error { return x.Remove(ctx) })
   151  					case fs.Directory:
   152  						toDelete = append(toDelete, x.Remote())
   153  					}
   154  				}
   155  				return nil
   156  			})
   157  			if err == fs.ErrorDirNotFound {
   158  				return
   159  			}
   160  			require.NoError(t, err)
   161  			sort.Strings(toDelete)
   162  			for i := len(toDelete) - 1; i >= 0; i-- {
   163  				dir := toDelete[i]
   164  				retry(t, fmt.Sprintf("removing dir %q", dir), func() error {
   165  					return r.Fremote.Rmdir(ctx, dir)
   166  				})
   167  			}
   168  			// Check remote is empty
   169  			CheckListingWithPrecision(t, r.Fremote, []Item{}, []string{}, r.Fremote.Precision())
   170  			// Clear the remote cache
   171  			cache.Clear()
   172  		}
   173  	}
   174  	r.Logf = t.Logf
   175  	r.Fatalf = t.Fatalf
   176  	r.Logf("Remote %q, Local %q, Modify Window %q", r.Fremote, r.Flocal, fs.GetModifyWindow(r.Fremote))
   177  	return r
   178  }
   179  
   180  // NewRun initialise the remote and local for testing and returns a
   181  // run object.  Call this from the tests.
   182  //
   183  // r.Flocal is an empty local Fs
   184  // r.Fremote is an empty remote Fs
   185  //
   186  // Finalise() will tidy them away when done.
   187  func NewRun(t *testing.T) *Run {
   188  	return newRunIndividual(t, *Individual)
   189  }
   190  
   191  // NewRunIndividual as per NewRun but makes an individual remote for this test
   192  func NewRunIndividual(t *testing.T) *Run {
   193  	return newRunIndividual(t, true)
   194  }
   195  
   196  // RenameFile renames a file in local
   197  func (r *Run) RenameFile(item Item, newpath string) Item {
   198  	oldFilepath := path.Join(r.LocalName, item.Path)
   199  	newFilepath := path.Join(r.LocalName, newpath)
   200  	if err := os.Rename(oldFilepath, newFilepath); err != nil {
   201  		r.Fatalf("Failed to rename file from %q to %q: %v", item.Path, newpath, err)
   202  	}
   203  
   204  	item.Path = newpath
   205  
   206  	return item
   207  }
   208  
   209  // WriteFile writes a file to local
   210  func (r *Run) WriteFile(filePath, content string, t time.Time) Item {
   211  	item := NewItem(filePath, content, t)
   212  	// FIXME make directories?
   213  	filePath = path.Join(r.LocalName, filePath)
   214  	dirPath := path.Dir(filePath)
   215  	err := os.MkdirAll(dirPath, 0770)
   216  	if err != nil {
   217  		r.Fatalf("Failed to make directories %q: %v", dirPath, err)
   218  	}
   219  	err = ioutil.WriteFile(filePath, []byte(content), 0600)
   220  	if err != nil {
   221  		r.Fatalf("Failed to write file %q: %v", filePath, err)
   222  	}
   223  	err = os.Chtimes(filePath, t, t)
   224  	if err != nil {
   225  		r.Fatalf("Failed to chtimes file %q: %v", filePath, err)
   226  	}
   227  	return item
   228  }
   229  
   230  // ForceMkdir creates the remote
   231  func (r *Run) ForceMkdir(ctx context.Context, f fs.Fs) {
   232  	err := f.Mkdir(ctx, "")
   233  	if err != nil {
   234  		r.Fatalf("Failed to mkdir %q: %v", f, err)
   235  	}
   236  	r.mkdir[f.String()] = true
   237  }
   238  
   239  // Mkdir creates the remote if it hasn't been created already
   240  func (r *Run) Mkdir(ctx context.Context, f fs.Fs) {
   241  	if !r.mkdir[f.String()] {
   242  		r.ForceMkdir(ctx, f)
   243  	}
   244  }
   245  
   246  // WriteObjectTo writes an object to the fs, remote passed in
   247  func (r *Run) WriteObjectTo(ctx context.Context, f fs.Fs, remote, content string, modTime time.Time, useUnchecked bool) Item {
   248  	put := f.Put
   249  	if useUnchecked {
   250  		put = f.Features().PutUnchecked
   251  		if put == nil {
   252  			r.Fatalf("Fs doesn't support PutUnchecked")
   253  		}
   254  	}
   255  	r.Mkdir(ctx, f)
   256  
   257  	// caclulate all hashes f supports for content
   258  	hash, err := hash.NewMultiHasherTypes(f.Hashes())
   259  	if err != nil {
   260  		r.Fatalf("Failed to make new multi hasher: %v", err)
   261  	}
   262  	_, err = hash.Write([]byte(content))
   263  	if err != nil {
   264  		r.Fatalf("Failed to make write to hash: %v", err)
   265  	}
   266  	hashSums := hash.Sums()
   267  
   268  	const maxTries = 10
   269  	for tries := 1; ; tries++ {
   270  		in := bytes.NewBufferString(content)
   271  		objinfo := object.NewStaticObjectInfo(remote, modTime, int64(len(content)), true, hashSums, nil)
   272  		_, err := put(ctx, in, objinfo)
   273  		if err == nil {
   274  			break
   275  		}
   276  		// Retry if err returned a retry error
   277  		if fserrors.IsRetryError(err) && tries < maxTries {
   278  			r.Logf("Retry Put of %q to %v: %d/%d (%v)", remote, f, tries, maxTries, err)
   279  			time.Sleep(2 * time.Second)
   280  			continue
   281  		}
   282  		r.Fatalf("Failed to put %q to %q: %v", remote, f, err)
   283  	}
   284  	return NewItem(remote, content, modTime)
   285  }
   286  
   287  // WriteObject writes an object to the remote
   288  func (r *Run) WriteObject(ctx context.Context, remote, content string, modTime time.Time) Item {
   289  	return r.WriteObjectTo(ctx, r.Fremote, remote, content, modTime, false)
   290  }
   291  
   292  // WriteUncheckedObject writes an object to the remote not checking for duplicates
   293  func (r *Run) WriteUncheckedObject(ctx context.Context, remote, content string, modTime time.Time) Item {
   294  	return r.WriteObjectTo(ctx, r.Fremote, remote, content, modTime, true)
   295  }
   296  
   297  // WriteBoth calls WriteObject and WriteFile with the same arguments
   298  func (r *Run) WriteBoth(ctx context.Context, remote, content string, modTime time.Time) Item {
   299  	r.WriteFile(remote, content, modTime)
   300  	return r.WriteObject(ctx, remote, content, modTime)
   301  }
   302  
   303  // CheckWithDuplicates does a test but allows duplicates
   304  func (r *Run) CheckWithDuplicates(t *testing.T, items ...Item) {
   305  	var want, got []string
   306  
   307  	// construct a []string of desired items
   308  	for _, item := range items {
   309  		want = append(want, fmt.Sprintf("%q %d", item.Path, item.Size))
   310  	}
   311  	sort.Strings(want)
   312  
   313  	// do the listing
   314  	objs, _, err := walk.GetAll(context.Background(), r.Fremote, "", true, -1)
   315  	if err != nil && err != fs.ErrorDirNotFound {
   316  		t.Fatalf("Error listing: %v", err)
   317  	}
   318  
   319  	// construct a []string of actual items
   320  	for _, o := range objs {
   321  		got = append(got, fmt.Sprintf("%q %d", o.Remote(), o.Size()))
   322  	}
   323  	sort.Strings(got)
   324  
   325  	assert.Equal(t, want, got)
   326  }
   327  
   328  // Clean the temporary directory
   329  func (r *Run) cleanTempDir() {
   330  	err := os.RemoveAll(r.LocalName)
   331  	if err != nil {
   332  		r.Logf("Failed to clean temporary directory %q: %v", r.LocalName, err)
   333  	}
   334  }
   335  
   336  // Finalise cleans the remote and local
   337  func (r *Run) Finalise() {
   338  	// r.Logf("Cleaning remote %q", r.Fremote)
   339  	r.cleanRemote()
   340  	// r.Logf("Cleaning local %q", r.LocalName)
   341  	r.cleanTempDir()
   342  	// Clear the remote cache
   343  	cache.Clear()
   344  }