github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/fs/walk/walk_test.go (about)

     1  package walk
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  	"sync"
     9  	"testing"
    10  
    11  	"github.com/pkg/errors"
    12  	"github.com/rclone/rclone/fs"
    13  	_ "github.com/rclone/rclone/fs/accounting"
    14  	"github.com/rclone/rclone/fs/filter"
    15  	"github.com/rclone/rclone/fs/fserrors"
    16  	"github.com/rclone/rclone/fstest/mockdir"
    17  	"github.com/rclone/rclone/fstest/mockfs"
    18  	"github.com/rclone/rclone/fstest/mockobject"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  var errDirNotFound, errorBoom error
    24  
    25  func init() {
    26  	errDirNotFound = fserrors.FsError(fs.ErrorDirNotFound)
    27  	fserrors.Count(errDirNotFound)
    28  	errorBoom = fserrors.FsError(errors.New("boom"))
    29  	fserrors.Count(errorBoom)
    30  }
    31  
    32  type (
    33  	listResult struct {
    34  		entries fs.DirEntries
    35  		err     error
    36  	}
    37  
    38  	listResults map[string]listResult
    39  
    40  	errorMap map[string]error
    41  
    42  	listDirs struct {
    43  		mu          sync.Mutex
    44  		t           *testing.T
    45  		fs          fs.Fs
    46  		includeAll  bool
    47  		results     listResults
    48  		walkResults listResults
    49  		walkErrors  errorMap
    50  		finalError  error
    51  		checkMaps   bool
    52  		maxLevel    int
    53  	}
    54  )
    55  
    56  func newListDirs(t *testing.T, f fs.Fs, includeAll bool, results listResults, walkErrors errorMap, finalError error) *listDirs {
    57  	return &listDirs{
    58  		t:           t,
    59  		fs:          f,
    60  		includeAll:  includeAll,
    61  		results:     results,
    62  		walkErrors:  walkErrors,
    63  		walkResults: listResults{},
    64  		finalError:  finalError,
    65  		checkMaps:   true,
    66  		maxLevel:    -1,
    67  	}
    68  }
    69  
    70  // NoCheckMaps marks the maps as to be ignored at the end
    71  func (ls *listDirs) NoCheckMaps() *listDirs {
    72  	ls.checkMaps = false
    73  	return ls
    74  }
    75  
    76  // SetLevel(1) turns off recursion
    77  func (ls *listDirs) SetLevel(maxLevel int) *listDirs {
    78  	ls.maxLevel = maxLevel
    79  	return ls
    80  }
    81  
    82  // ListDir returns the expected listing for the directory
    83  func (ls *listDirs) ListDir(ctx context.Context, f fs.Fs, includeAll bool, dir string) (entries fs.DirEntries, err error) {
    84  	ls.mu.Lock()
    85  	defer ls.mu.Unlock()
    86  	assert.Equal(ls.t, ls.fs, f)
    87  	assert.Equal(ls.t, ls.includeAll, includeAll)
    88  
    89  	// Fetch results for this path
    90  	result, ok := ls.results[dir]
    91  	if !ok {
    92  		ls.t.Errorf("Unexpected list of %q", dir)
    93  		return nil, errors.New("unexpected list")
    94  	}
    95  	delete(ls.results, dir)
    96  
    97  	// Put expected results for call of WalkFn
    98  	ls.walkResults[dir] = result
    99  
   100  	return result.entries, result.err
   101  }
   102  
   103  // ListR returns the expected listing for the directory using ListR
   104  func (ls *listDirs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (err error) {
   105  	ls.mu.Lock()
   106  	defer ls.mu.Unlock()
   107  
   108  	var errorReturn error
   109  	for dirPath, result := range ls.results {
   110  		// Put expected results for call of WalkFn
   111  		// Note that we don't call the function at all if we got an error
   112  		if result.err != nil {
   113  			errorReturn = result.err
   114  		}
   115  		if errorReturn == nil {
   116  			err = callback(result.entries)
   117  			require.NoError(ls.t, err)
   118  			ls.walkResults[dirPath] = result
   119  		}
   120  	}
   121  	ls.results = listResults{}
   122  	return errorReturn
   123  }
   124  
   125  // IsFinished checks everything expected was used up
   126  func (ls *listDirs) IsFinished() {
   127  	if ls.checkMaps {
   128  		assert.Equal(ls.t, errorMap{}, ls.walkErrors)
   129  		assert.Equal(ls.t, listResults{}, ls.results)
   130  		assert.Equal(ls.t, listResults{}, ls.walkResults)
   131  	}
   132  }
   133  
   134  // WalkFn is called by the walk to test the expectations
   135  func (ls *listDirs) WalkFn(dir string, entries fs.DirEntries, err error) error {
   136  	ls.mu.Lock()
   137  	defer ls.mu.Unlock()
   138  	// ls.t.Logf("WalkFn(%q, %v, %q)", dir, entries, err)
   139  
   140  	// Fetch expected entries and err
   141  	result, ok := ls.walkResults[dir]
   142  	if !ok {
   143  		ls.t.Errorf("Unexpected walk of %q (result not found)", dir)
   144  		return errors.New("result not found")
   145  	}
   146  	delete(ls.walkResults, dir)
   147  
   148  	// Check arguments are as expected
   149  	assert.Equal(ls.t, result.entries, entries)
   150  	assert.Equal(ls.t, result.err, err)
   151  
   152  	// Fetch return value
   153  	returnErr, ok := ls.walkErrors[dir]
   154  	if !ok {
   155  		ls.t.Errorf("Unexpected walk of %q (error not found)", dir)
   156  		return errors.New("error not found")
   157  	}
   158  	delete(ls.walkErrors, dir)
   159  
   160  	return returnErr
   161  }
   162  
   163  // Walk does the walk and tests the expectations
   164  func (ls *listDirs) Walk() {
   165  	err := walk(context.Background(), nil, "", ls.includeAll, ls.maxLevel, ls.WalkFn, ls.ListDir)
   166  	assert.Equal(ls.t, ls.finalError, err)
   167  	ls.IsFinished()
   168  }
   169  
   170  // WalkR does the walkR and tests the expectations
   171  func (ls *listDirs) WalkR() {
   172  	err := walkR(context.Background(), nil, "", ls.includeAll, ls.maxLevel, ls.WalkFn, ls.ListR)
   173  	assert.Equal(ls.t, ls.finalError, err)
   174  	if ls.finalError == nil {
   175  		ls.IsFinished()
   176  	}
   177  }
   178  
   179  func testWalkEmpty(t *testing.T) *listDirs {
   180  	return newListDirs(t, nil, false,
   181  		listResults{
   182  			"": {entries: fs.DirEntries{}, err: nil},
   183  		},
   184  		errorMap{
   185  			"": nil,
   186  		},
   187  		nil,
   188  	)
   189  }
   190  func TestWalkEmpty(t *testing.T)  { testWalkEmpty(t).Walk() }
   191  func TestWalkREmpty(t *testing.T) { testWalkEmpty(t).WalkR() }
   192  
   193  func testWalkEmptySkip(t *testing.T) *listDirs {
   194  	return newListDirs(t, nil, true,
   195  		listResults{
   196  			"": {entries: fs.DirEntries{}, err: nil},
   197  		},
   198  		errorMap{
   199  			"": ErrorSkipDir,
   200  		},
   201  		nil,
   202  	)
   203  }
   204  func TestWalkEmptySkip(t *testing.T)  { testWalkEmptySkip(t).Walk() }
   205  func TestWalkREmptySkip(t *testing.T) { testWalkEmptySkip(t).WalkR() }
   206  
   207  func testWalkNotFound(t *testing.T) *listDirs {
   208  	return newListDirs(t, nil, true,
   209  		listResults{
   210  			"": {err: errDirNotFound},
   211  		},
   212  		errorMap{
   213  			"": errDirNotFound,
   214  		},
   215  		errDirNotFound,
   216  	)
   217  }
   218  func TestWalkNotFound(t *testing.T)  { testWalkNotFound(t).Walk() }
   219  func TestWalkRNotFound(t *testing.T) { testWalkNotFound(t).WalkR() }
   220  
   221  func TestWalkNotFoundMaskError(t *testing.T) {
   222  	// this doesn't work for WalkR
   223  	newListDirs(t, nil, true,
   224  		listResults{
   225  			"": {err: errDirNotFound},
   226  		},
   227  		errorMap{
   228  			"": nil,
   229  		},
   230  		nil,
   231  	).Walk()
   232  }
   233  
   234  func TestWalkNotFoundSkipError(t *testing.T) {
   235  	// this doesn't work for WalkR
   236  	newListDirs(t, nil, true,
   237  		listResults{
   238  			"": {err: errDirNotFound},
   239  		},
   240  		errorMap{
   241  			"": ErrorSkipDir,
   242  		},
   243  		nil,
   244  	).Walk()
   245  }
   246  
   247  func testWalkLevels(t *testing.T, maxLevel int) *listDirs {
   248  	da := mockdir.New("a")
   249  	oA := mockobject.Object("A")
   250  	db := mockdir.New("a/b")
   251  	oB := mockobject.Object("a/B")
   252  	dc := mockdir.New("a/b/c")
   253  	oC := mockobject.Object("a/b/C")
   254  	dd := mockdir.New("a/b/c/d")
   255  	oD := mockobject.Object("a/b/c/D")
   256  	return newListDirs(t, nil, false,
   257  		listResults{
   258  			"":        {entries: fs.DirEntries{oA, da}, err: nil},
   259  			"a":       {entries: fs.DirEntries{oB, db}, err: nil},
   260  			"a/b":     {entries: fs.DirEntries{oC, dc}, err: nil},
   261  			"a/b/c":   {entries: fs.DirEntries{oD, dd}, err: nil},
   262  			"a/b/c/d": {entries: fs.DirEntries{}, err: nil},
   263  		},
   264  		errorMap{
   265  			"":        nil,
   266  			"a":       nil,
   267  			"a/b":     nil,
   268  			"a/b/c":   nil,
   269  			"a/b/c/d": nil,
   270  		},
   271  		nil,
   272  	).SetLevel(maxLevel)
   273  }
   274  func TestWalkLevels(t *testing.T)               { testWalkLevels(t, -1).Walk() }
   275  func TestWalkRLevels(t *testing.T)              { testWalkLevels(t, -1).WalkR() }
   276  func TestWalkLevelsNoRecursive10(t *testing.T)  { testWalkLevels(t, 10).Walk() }
   277  func TestWalkRLevelsNoRecursive10(t *testing.T) { testWalkLevels(t, 10).WalkR() }
   278  
   279  func TestWalkNDirTree(t *testing.T) {
   280  	ls := testWalkLevels(t, -1)
   281  	entries, err := walkNDirTree(context.Background(), nil, "", ls.includeAll, ls.maxLevel, ls.ListDir)
   282  	require.NoError(t, err)
   283  	assert.Equal(t, `/
   284    A
   285    a/
   286  a/
   287    B
   288    b/
   289  a/b/
   290    C
   291    c/
   292  a/b/c/
   293    D
   294    d/
   295  a/b/c/d/
   296  `, entries.String())
   297  }
   298  
   299  func testWalkLevelsNoRecursive(t *testing.T) *listDirs {
   300  	da := mockdir.New("a")
   301  	oA := mockobject.Object("A")
   302  	return newListDirs(t, nil, false,
   303  		listResults{
   304  			"": {entries: fs.DirEntries{oA, da}, err: nil},
   305  		},
   306  		errorMap{
   307  			"": nil,
   308  		},
   309  		nil,
   310  	).SetLevel(1)
   311  }
   312  func TestWalkLevelsNoRecursive(t *testing.T)  { testWalkLevelsNoRecursive(t).Walk() }
   313  func TestWalkRLevelsNoRecursive(t *testing.T) { testWalkLevelsNoRecursive(t).WalkR() }
   314  
   315  func testWalkLevels2(t *testing.T) *listDirs {
   316  	da := mockdir.New("a")
   317  	oA := mockobject.Object("A")
   318  	db := mockdir.New("a/b")
   319  	oB := mockobject.Object("a/B")
   320  	return newListDirs(t, nil, false,
   321  		listResults{
   322  			"":  {entries: fs.DirEntries{oA, da}, err: nil},
   323  			"a": {entries: fs.DirEntries{oB, db}, err: nil},
   324  		},
   325  		errorMap{
   326  			"":  nil,
   327  			"a": nil,
   328  		},
   329  		nil,
   330  	).SetLevel(2)
   331  }
   332  func TestWalkLevels2(t *testing.T)  { testWalkLevels2(t).Walk() }
   333  func TestWalkRLevels2(t *testing.T) { testWalkLevels2(t).WalkR() }
   334  
   335  func testWalkSkip(t *testing.T) *listDirs {
   336  	da := mockdir.New("a")
   337  	db := mockdir.New("a/b")
   338  	dc := mockdir.New("a/b/c")
   339  	return newListDirs(t, nil, false,
   340  		listResults{
   341  			"":    {entries: fs.DirEntries{da}, err: nil},
   342  			"a":   {entries: fs.DirEntries{db}, err: nil},
   343  			"a/b": {entries: fs.DirEntries{dc}, err: nil},
   344  		},
   345  		errorMap{
   346  			"":    nil,
   347  			"a":   nil,
   348  			"a/b": ErrorSkipDir,
   349  		},
   350  		nil,
   351  	)
   352  }
   353  func TestWalkSkip(t *testing.T)  { testWalkSkip(t).Walk() }
   354  func TestWalkRSkip(t *testing.T) { testWalkSkip(t).WalkR() }
   355  
   356  func walkErrors(t *testing.T, expectedErr error) *listDirs {
   357  	lr := listResults{}
   358  	em := errorMap{}
   359  	de := make(fs.DirEntries, 10)
   360  	for i := range de {
   361  		path := string('0' + i)
   362  		de[i] = mockdir.New(path)
   363  		lr[path] = listResult{entries: nil, err: fs.ErrorDirNotFound}
   364  		em[path] = fs.ErrorDirNotFound
   365  	}
   366  	lr[""] = listResult{entries: de, err: nil}
   367  	em[""] = nil
   368  	return newListDirs(t, nil, true,
   369  		lr,
   370  		em,
   371  		expectedErr,
   372  	).NoCheckMaps()
   373  }
   374  
   375  func testWalkErrors(t *testing.T) *listDirs {
   376  	return walkErrors(t, errDirNotFound)
   377  }
   378  
   379  func testWalkRErrors(t *testing.T) *listDirs {
   380  	return walkErrors(t, fs.ErrorDirNotFound)
   381  }
   382  
   383  func TestWalkErrors(t *testing.T)  { testWalkErrors(t).Walk() }
   384  func TestWalkRErrors(t *testing.T) { testWalkRErrors(t).WalkR() }
   385  
   386  func makeTree(level int, terminalErrors bool) (listResults, errorMap) {
   387  	lr := listResults{}
   388  	em := errorMap{}
   389  	var fill func(path string, level int)
   390  	fill = func(path string, level int) {
   391  		de := fs.DirEntries{}
   392  		if level > 0 {
   393  			for _, a := range "0123456789" {
   394  				subPath := string(a)
   395  				if path != "" {
   396  					subPath = path + "/" + subPath
   397  				}
   398  				de = append(de, mockdir.New(subPath))
   399  				fill(subPath, level-1)
   400  			}
   401  		}
   402  		lr[path] = listResult{entries: de, err: nil}
   403  		em[path] = nil
   404  		if level == 0 && terminalErrors {
   405  			em[path] = errorBoom
   406  		}
   407  	}
   408  	fill("", level)
   409  	return lr, em
   410  }
   411  
   412  func testWalkMulti(t *testing.T) *listDirs {
   413  	lr, em := makeTree(3, false)
   414  	return newListDirs(t, nil, true,
   415  		lr,
   416  		em,
   417  		nil,
   418  	)
   419  }
   420  func TestWalkMulti(t *testing.T)  { testWalkMulti(t).Walk() }
   421  func TestWalkRMulti(t *testing.T) { testWalkMulti(t).WalkR() }
   422  
   423  func testWalkMultiErrors(t *testing.T) *listDirs {
   424  	lr, em := makeTree(3, true)
   425  	return newListDirs(t, nil, true,
   426  		lr,
   427  		em,
   428  		errorBoom,
   429  	).NoCheckMaps()
   430  }
   431  func TestWalkMultiErrors(t *testing.T)  { testWalkMultiErrors(t).Walk() }
   432  func TestWalkRMultiErrors(t *testing.T) { testWalkMultiErrors(t).Walk() }
   433  
   434  // a very simple listRcallback function
   435  func makeListRCallback(entries fs.DirEntries, err error) fs.ListRFn {
   436  	return func(ctx context.Context, dir string, callback fs.ListRCallback) error {
   437  		if err == nil {
   438  			err = callback(entries)
   439  		}
   440  		return err
   441  	}
   442  }
   443  
   444  func TestWalkRDirTree(t *testing.T) {
   445  	for _, test := range []struct {
   446  		entries fs.DirEntries
   447  		want    string
   448  		err     error
   449  		root    string
   450  		level   int
   451  	}{
   452  		{fs.DirEntries{}, "/\n", nil, "", -1},
   453  		{fs.DirEntries{mockobject.Object("a")}, `/
   454    a
   455  `, nil, "", -1},
   456  		{fs.DirEntries{mockobject.Object("a/b")}, `/
   457    a/
   458  a/
   459    b
   460  `, nil, "", -1},
   461  		{fs.DirEntries{mockobject.Object("a/b/c/d")}, `/
   462    a/
   463  a/
   464    b/
   465  a/b/
   466    c/
   467  a/b/c/
   468    d
   469  `, nil, "", -1},
   470  		{fs.DirEntries{mockobject.Object("a")}, "", errorBoom, "", -1},
   471  		{fs.DirEntries{
   472  			mockobject.Object("0/1/2/3"),
   473  			mockobject.Object("4/5/6/7"),
   474  			mockobject.Object("8/9/a/b"),
   475  			mockobject.Object("c/d/e/f"),
   476  			mockobject.Object("g/h/i/j"),
   477  			mockobject.Object("k/l/m/n"),
   478  			mockobject.Object("o/p/q/r"),
   479  			mockobject.Object("s/t/u/v"),
   480  			mockobject.Object("w/x/y/z"),
   481  		}, `/
   482    0/
   483    4/
   484    8/
   485    c/
   486    g/
   487    k/
   488    o/
   489    s/
   490    w/
   491  0/
   492    1/
   493  0/1/
   494    2/
   495  0/1/2/
   496    3
   497  4/
   498    5/
   499  4/5/
   500    6/
   501  4/5/6/
   502    7
   503  8/
   504    9/
   505  8/9/
   506    a/
   507  8/9/a/
   508    b
   509  c/
   510    d/
   511  c/d/
   512    e/
   513  c/d/e/
   514    f
   515  g/
   516    h/
   517  g/h/
   518    i/
   519  g/h/i/
   520    j
   521  k/
   522    l/
   523  k/l/
   524    m/
   525  k/l/m/
   526    n
   527  o/
   528    p/
   529  o/p/
   530    q/
   531  o/p/q/
   532    r
   533  s/
   534    t/
   535  s/t/
   536    u/
   537  s/t/u/
   538    v
   539  w/
   540    x/
   541  w/x/
   542    y/
   543  w/x/y/
   544    z
   545  `, nil, "", -1},
   546  		{fs.DirEntries{
   547  			mockobject.Object("a/b/c/d/e/f1"),
   548  			mockobject.Object("a/b/c/d/e/f2"),
   549  			mockobject.Object("a/b/c/d/e/f3"),
   550  		}, `a/b/c/
   551    d/
   552  a/b/c/d/
   553    e/
   554  a/b/c/d/e/
   555    f1
   556    f2
   557    f3
   558  `, nil, "a/b/c", -1},
   559  		{fs.DirEntries{
   560  			mockobject.Object("A"),
   561  			mockobject.Object("a/B"),
   562  			mockobject.Object("a/b/C"),
   563  			mockobject.Object("a/b/c/D"),
   564  			mockobject.Object("a/b/c/d/E"),
   565  		}, `/
   566    A
   567    a/
   568  a/
   569    B
   570    b/
   571  `, nil, "", 2},
   572  		{fs.DirEntries{
   573  			mockobject.Object("a/b/c"),
   574  			mockobject.Object("a/b/c/d/e"),
   575  		}, `/
   576    a/
   577  a/
   578    b/
   579  `, nil, "", 2},
   580  	} {
   581  		r, err := walkRDirTree(context.Background(), nil, test.root, true, test.level, makeListRCallback(test.entries, test.err))
   582  		assert.Equal(t, test.err, err, fmt.Sprintf("%+v", test))
   583  		assert.Equal(t, test.want, r.String(), fmt.Sprintf("%+v", test))
   584  	}
   585  }
   586  
   587  func TestWalkRDirTreeExclude(t *testing.T) {
   588  	for _, test := range []struct {
   589  		entries     fs.DirEntries
   590  		want        string
   591  		err         error
   592  		root        string
   593  		level       int
   594  		excludeFile string
   595  		includeAll  bool
   596  	}{
   597  		{fs.DirEntries{mockobject.Object("a"), mockobject.Object("ignore")}, "", nil, "", -1, "ignore", false},
   598  		{fs.DirEntries{mockobject.Object("a")}, `/
   599    a
   600  `, nil, "", -1, "ignore", false},
   601  		{fs.DirEntries{
   602  			mockobject.Object("a"),
   603  			mockobject.Object("b/b"),
   604  			mockobject.Object("b/.ignore"),
   605  		}, `/
   606    a
   607  `, nil, "", -1, ".ignore", false},
   608  		{fs.DirEntries{
   609  			mockobject.Object("a"),
   610  			mockobject.Object("b/.ignore"),
   611  			mockobject.Object("b/b"),
   612  		}, `/
   613    a
   614    b/
   615  b/
   616    .ignore
   617    b
   618  `, nil, "", -1, ".ignore", true},
   619  		{fs.DirEntries{
   620  			mockobject.Object("a"),
   621  			mockobject.Object("b/b"),
   622  			mockobject.Object("b/c/d/e"),
   623  			mockobject.Object("b/c/ign"),
   624  			mockobject.Object("b/c/x"),
   625  		}, `/
   626    a
   627    b/
   628  b/
   629    b
   630  `, nil, "", -1, "ign", false},
   631  		{fs.DirEntries{
   632  			mockobject.Object("a"),
   633  			mockobject.Object("b/b"),
   634  			mockobject.Object("b/c/d/e"),
   635  			mockobject.Object("b/c/ign"),
   636  			mockobject.Object("b/c/x"),
   637  		}, `/
   638    a
   639    b/
   640  b/
   641    b
   642    c/
   643  b/c/
   644    d/
   645    ign
   646    x
   647  b/c/d/
   648    e
   649  `, nil, "", -1, "ign", true},
   650  	} {
   651  		filter.Active.Opt.ExcludeFile = test.excludeFile
   652  		r, err := walkRDirTree(context.Background(), nil, test.root, test.includeAll, test.level, makeListRCallback(test.entries, test.err))
   653  		assert.Equal(t, test.err, err, fmt.Sprintf("%+v", test))
   654  		assert.Equal(t, test.want, r.String(), fmt.Sprintf("%+v", test))
   655  	}
   656  	// Set to default value, to avoid side effects
   657  	filter.Active.Opt.ExcludeFile = ""
   658  }
   659  
   660  func TestListType(t *testing.T) {
   661  	assert.Equal(t, true, ListObjects.Objects())
   662  	assert.Equal(t, false, ListObjects.Dirs())
   663  	assert.Equal(t, false, ListDirs.Objects())
   664  	assert.Equal(t, true, ListDirs.Dirs())
   665  	assert.Equal(t, true, ListAll.Objects())
   666  	assert.Equal(t, true, ListAll.Dirs())
   667  
   668  	var (
   669  		a           = mockobject.Object("a")
   670  		b           = mockobject.Object("b")
   671  		dir         = mockdir.New("dir")
   672  		adir        = mockobject.Object("dir/a")
   673  		dir2        = mockdir.New("dir2")
   674  		origEntries = fs.DirEntries{
   675  			a, b, dir, adir, dir2,
   676  		}
   677  		dirEntries = fs.DirEntries{
   678  			dir, dir2,
   679  		}
   680  		objEntries = fs.DirEntries{
   681  			a, b, adir,
   682  		}
   683  	)
   684  	copyOrigEntries := func() (out fs.DirEntries) {
   685  		out = make(fs.DirEntries, len(origEntries))
   686  		copy(out, origEntries)
   687  		return out
   688  	}
   689  
   690  	got := copyOrigEntries()
   691  	ListAll.Filter(&got)
   692  	assert.Equal(t, origEntries, got)
   693  
   694  	got = copyOrigEntries()
   695  	ListObjects.Filter(&got)
   696  	assert.Equal(t, objEntries, got)
   697  
   698  	got = copyOrigEntries()
   699  	ListDirs.Filter(&got)
   700  	assert.Equal(t, dirEntries, got)
   701  }
   702  
   703  func TestListR(t *testing.T) {
   704  	objects := fs.DirEntries{
   705  		mockobject.Object("a"),
   706  		mockobject.Object("b"),
   707  		mockdir.New("dir"),
   708  		mockobject.Object("dir/a"),
   709  		mockobject.Object("dir/b"),
   710  		mockobject.Object("dir/c"),
   711  	}
   712  	f := mockfs.NewFs("mock", "/")
   713  	var got []string
   714  	clearCallback := func() {
   715  		got = nil
   716  	}
   717  	callback := func(entries fs.DirEntries) error {
   718  		for _, entry := range entries {
   719  			got = append(got, entry.Remote())
   720  		}
   721  		return nil
   722  	}
   723  	doListR := func(ctx context.Context, dir string, callback fs.ListRCallback) error {
   724  		var os fs.DirEntries
   725  		for _, o := range objects {
   726  			if dir == "" || strings.HasPrefix(o.Remote(), dir+"/") {
   727  				os = append(os, o)
   728  			}
   729  		}
   730  		return callback(os)
   731  	}
   732  
   733  	// Setup filter
   734  	oldFilter := filter.Active
   735  	defer func() {
   736  		filter.Active = oldFilter
   737  	}()
   738  
   739  	var err error
   740  	filter.Active, err = filter.NewFilter(nil)
   741  	require.NoError(t, err)
   742  	require.NoError(t, filter.Active.AddRule("+ b"))
   743  	require.NoError(t, filter.Active.AddRule("- *"))
   744  
   745  	// Base case
   746  	clearCallback()
   747  	err = listR(context.Background(), f, "", true, ListAll, callback, doListR, false)
   748  	require.NoError(t, err)
   749  	require.Equal(t, []string{"a", "b", "dir", "dir/a", "dir/b", "dir/c"}, got)
   750  
   751  	// Base case - with Objects
   752  	clearCallback()
   753  	err = listR(context.Background(), f, "", true, ListObjects, callback, doListR, false)
   754  	require.NoError(t, err)
   755  	require.Equal(t, []string{"a", "b", "dir/a", "dir/b", "dir/c"}, got)
   756  
   757  	// Base case - with Dirs
   758  	clearCallback()
   759  	err = listR(context.Background(), f, "", true, ListDirs, callback, doListR, false)
   760  	require.NoError(t, err)
   761  	require.Equal(t, []string{"dir"}, got)
   762  
   763  	// With filter
   764  	clearCallback()
   765  	err = listR(context.Background(), f, "", false, ListAll, callback, doListR, false)
   766  	require.NoError(t, err)
   767  	require.Equal(t, []string{"b", "dir", "dir/b"}, got)
   768  
   769  	// With filter - with Objects
   770  	clearCallback()
   771  	err = listR(context.Background(), f, "", false, ListObjects, callback, doListR, false)
   772  	require.NoError(t, err)
   773  	require.Equal(t, []string{"b", "dir/b"}, got)
   774  
   775  	// With filter - with Dir
   776  	clearCallback()
   777  	err = listR(context.Background(), f, "", false, ListDirs, callback, doListR, false)
   778  	require.NoError(t, err)
   779  	require.Equal(t, []string{"dir"}, got)
   780  
   781  	// With filter and subdir
   782  	clearCallback()
   783  	err = listR(context.Background(), f, "dir", false, ListAll, callback, doListR, false)
   784  	require.NoError(t, err)
   785  	require.Equal(t, []string{"dir/b"}, got)
   786  
   787  	// Now bucket based
   788  	objects = fs.DirEntries{
   789  		mockobject.Object("a"),
   790  		mockobject.Object("b"),
   791  		mockobject.Object("dir/a"),
   792  		mockobject.Object("dir/b"),
   793  		mockobject.Object("dir/subdir/c"),
   794  		mockdir.New("dir/subdir"),
   795  	}
   796  
   797  	// Base case
   798  	clearCallback()
   799  	err = listR(context.Background(), f, "", true, ListAll, callback, doListR, true)
   800  	require.NoError(t, err)
   801  	require.Equal(t, []string{"a", "b", "dir/a", "dir/b", "dir/subdir/c", "dir/subdir", "dir"}, got)
   802  
   803  	// With filter
   804  	clearCallback()
   805  	err = listR(context.Background(), f, "", false, ListAll, callback, doListR, true)
   806  	require.NoError(t, err)
   807  	require.Equal(t, []string{"b", "dir/b", "dir/subdir", "dir"}, got)
   808  
   809  	// With filter and subdir
   810  	clearCallback()
   811  	err = listR(context.Background(), f, "dir", false, ListAll, callback, doListR, true)
   812  	require.NoError(t, err)
   813  	require.Equal(t, []string{"dir/b", "dir/subdir"}, got)
   814  
   815  	// With filter and subdir - with Objects
   816  	clearCallback()
   817  	err = listR(context.Background(), f, "dir", false, ListObjects, callback, doListR, true)
   818  	require.NoError(t, err)
   819  	require.Equal(t, []string{"dir/b"}, got)
   820  
   821  	// With filter and subdir - with Dirs
   822  	clearCallback()
   823  	err = listR(context.Background(), f, "dir", false, ListDirs, callback, doListR, true)
   824  	require.NoError(t, err)
   825  	require.Equal(t, []string{"dir/subdir"}, got)
   826  }
   827  
   828  func TestDirMapAdd(t *testing.T) {
   829  	type add struct {
   830  		dir  string
   831  		sent bool
   832  	}
   833  	for i, test := range []struct {
   834  		root string
   835  		in   []add
   836  		want map[string]bool
   837  	}{
   838  		{
   839  			root: "",
   840  			in: []add{
   841  				{"", true},
   842  			},
   843  			want: map[string]bool{},
   844  		},
   845  		{
   846  			root: "",
   847  			in: []add{
   848  				{"a/b/c", true},
   849  			},
   850  			want: map[string]bool{
   851  				"a/b/c": true,
   852  				"a/b":   false,
   853  				"a":     false,
   854  			},
   855  		},
   856  		{
   857  			root: "",
   858  			in: []add{
   859  				{"a/b/c", true},
   860  				{"a/b", true},
   861  			},
   862  			want: map[string]bool{
   863  				"a/b/c": true,
   864  				"a/b":   true,
   865  				"a":     false,
   866  			},
   867  		},
   868  		{
   869  			root: "",
   870  			in: []add{
   871  				{"a/b", true},
   872  				{"a/b/c", false},
   873  			},
   874  			want: map[string]bool{
   875  				"a/b/c": false,
   876  				"a/b":   true,
   877  				"a":     false,
   878  			},
   879  		},
   880  		{
   881  			root: "root",
   882  			in: []add{
   883  				{"root/a/b", true},
   884  				{"root/a/b/c", false},
   885  			},
   886  			want: map[string]bool{
   887  				"root/a/b/c": false,
   888  				"root/a/b":   true,
   889  				"root/a":     false,
   890  			},
   891  		},
   892  	} {
   893  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   894  			dm := newDirMap(test.root)
   895  			for _, item := range test.in {
   896  				dm.add(item.dir, item.sent)
   897  			}
   898  			assert.Equal(t, test.want, dm.m)
   899  		})
   900  	}
   901  }
   902  
   903  func TestDirMapAddEntries(t *testing.T) {
   904  	dm := newDirMap("")
   905  	entries := fs.DirEntries{
   906  		mockobject.Object("dir/a"),
   907  		mockobject.Object("dir/b"),
   908  		mockdir.New("dir"),
   909  		mockobject.Object("dir2/a"),
   910  		mockobject.Object("dir2/b"),
   911  	}
   912  	require.NoError(t, dm.addEntries(entries))
   913  	assert.Equal(t, map[string]bool{"dir": true, "dir2": false}, dm.m)
   914  }
   915  
   916  func TestDirMapSendEntries(t *testing.T) {
   917  	var got []string
   918  	clearCallback := func() {
   919  		got = nil
   920  	}
   921  	callback := func(entries fs.DirEntries) error {
   922  		for _, entry := range entries {
   923  			got = append(got, entry.Remote())
   924  		}
   925  		return nil
   926  	}
   927  
   928  	// general test
   929  	dm := newDirMap("")
   930  	entries := fs.DirEntries{
   931  		mockobject.Object("dir/a"),
   932  		mockobject.Object("dir/b"),
   933  		mockdir.New("dir"),
   934  		mockobject.Object("dir2/a"),
   935  		mockobject.Object("dir2/b"),
   936  		mockobject.Object("dir1/a"),
   937  		mockobject.Object("dir3/b"),
   938  	}
   939  	require.NoError(t, dm.addEntries(entries))
   940  	clearCallback()
   941  	err := dm.sendEntries(callback)
   942  	require.NoError(t, err)
   943  	assert.Equal(t, []string{
   944  		"dir1",
   945  		"dir2",
   946  		"dir3",
   947  	}, got)
   948  
   949  	// return error from callback
   950  	callback2 := func(entries fs.DirEntries) error {
   951  		return io.EOF
   952  	}
   953  	err = dm.sendEntries(callback2)
   954  	require.Equal(t, io.EOF, err)
   955  
   956  	// empty
   957  	dm = newDirMap("")
   958  	clearCallback()
   959  	err = dm.sendEntries(callback)
   960  	require.NoError(t, err)
   961  	assert.Equal(t, []string(nil), got)
   962  }