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