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