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

     1  package walk
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  
    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' + rune(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  		exclude string
   452  	}{
   453  		{
   454  			entries: fs.DirEntries{},
   455  			want:    "/\n",
   456  			level:   -1,
   457  		},
   458  		{
   459  			entries: fs.DirEntries{mockobject.Object("a")},
   460  			want: `/
   461    a
   462  `,
   463  			level: -1,
   464  		},
   465  		{
   466  			entries: fs.DirEntries{mockobject.Object("a/b")},
   467  			want: `/
   468    a/
   469  a/
   470    b
   471  `,
   472  			level: -1,
   473  		},
   474  		{
   475  			entries: fs.DirEntries{mockobject.Object("a/b/c/d")},
   476  			want: `/
   477    a/
   478  a/
   479    b/
   480  a/b/
   481    c/
   482  a/b/c/
   483    d
   484  `,
   485  			level: -1,
   486  		},
   487  		{
   488  			entries: fs.DirEntries{mockobject.Object("a")},
   489  			err:     errorBoom,
   490  			level:   -1,
   491  		},
   492  		{
   493  			entries: fs.DirEntries{
   494  				mockobject.Object("0/1/2/3"),
   495  				mockobject.Object("4/5/6/7"),
   496  				mockobject.Object("8/9/a/b"),
   497  				mockobject.Object("c/d/e/f"),
   498  				mockobject.Object("g/h/i/j"),
   499  				mockobject.Object("k/l/m/n"),
   500  				mockobject.Object("o/p/q/r"),
   501  				mockobject.Object("s/t/u/v"),
   502  				mockobject.Object("w/x/y/z"),
   503  			},
   504  			want: `/
   505    0/
   506    4/
   507    8/
   508    c/
   509    g/
   510    k/
   511    o/
   512    s/
   513    w/
   514  0/
   515    1/
   516  0/1/
   517    2/
   518  0/1/2/
   519    3
   520  4/
   521    5/
   522  4/5/
   523    6/
   524  4/5/6/
   525    7
   526  8/
   527    9/
   528  8/9/
   529    a/
   530  8/9/a/
   531    b
   532  c/
   533    d/
   534  c/d/
   535    e/
   536  c/d/e/
   537    f
   538  g/
   539    h/
   540  g/h/
   541    i/
   542  g/h/i/
   543    j
   544  k/
   545    l/
   546  k/l/
   547    m/
   548  k/l/m/
   549    n
   550  o/
   551    p/
   552  o/p/
   553    q/
   554  o/p/q/
   555    r
   556  s/
   557    t/
   558  s/t/
   559    u/
   560  s/t/u/
   561    v
   562  w/
   563    x/
   564  w/x/
   565    y/
   566  w/x/y/
   567    z
   568  `,
   569  			level: -1,
   570  		},
   571  		{
   572  			entries: fs.DirEntries{
   573  				mockobject.Object("a/b/c/d/e/f1"),
   574  				mockobject.Object("a/b/c/d/e/f2"),
   575  				mockobject.Object("a/b/c/d/e/f3"),
   576  			},
   577  			want: `a/b/c/
   578    d/
   579  a/b/c/d/
   580    e/
   581  a/b/c/d/e/
   582    f1
   583    f2
   584    f3
   585  `,
   586  			root:  "a/b/c",
   587  			level: -1,
   588  		},
   589  		{
   590  			entries: fs.DirEntries{
   591  				mockobject.Object("A"),
   592  				mockobject.Object("a/B"),
   593  				mockobject.Object("a/b/C"),
   594  				mockobject.Object("a/b/c/D"),
   595  				mockobject.Object("a/b/c/d/E"),
   596  			},
   597  			want: `/
   598    A
   599    a/
   600  a/
   601    B
   602    b/
   603  a/b/
   604  `,
   605  			level: 2,
   606  		},
   607  		{
   608  			entries: fs.DirEntries{
   609  				mockobject.Object("a/b/c"),
   610  				mockobject.Object("a/b/c/d/e"),
   611  			},
   612  			want: `/
   613    a/
   614  a/
   615    b/
   616  a/b/
   617  `,
   618  			level: 2,
   619  		},
   620  		{
   621  			entries: fs.DirEntries{
   622  				mockobject.Object("a/.bzEmpty"),
   623  				mockobject.Object("a/b1/.bzEmpty"),
   624  				mockobject.Object("a/b2/.bzEmpty"),
   625  			},
   626  			want: `/
   627    a/
   628  a/
   629    .bzEmpty
   630    b1/
   631    b2/
   632  a/b1/
   633    .bzEmpty
   634  a/b2/
   635    .bzEmpty
   636  `,
   637  			level:   -1,
   638  			exclude: ""},
   639  		{
   640  			entries: fs.DirEntries{
   641  				mockobject.Object("a/.bzEmpty"),
   642  				mockobject.Object("a/b1/.bzEmpty"),
   643  				mockobject.Object("a/b2/.bzEmpty"),
   644  			},
   645  			want: `/
   646    a/
   647  a/
   648    b1/
   649    b2/
   650  a/b1/
   651  a/b2/
   652  `,
   653  			level:   -1,
   654  			exclude: ".bzEmpty",
   655  		},
   656  	} {
   657  		ctx := context.Background()
   658  		if test.exclude != "" {
   659  			fi, err := filter.NewFilter(nil)
   660  			require.NoError(t, err)
   661  			require.NoError(t, fi.Add(false, test.exclude))
   662  			// Change the active filter
   663  			ctx = filter.ReplaceConfig(ctx, fi)
   664  
   665  		}
   666  		r, err := walkRDirTree(ctx, nil, test.root, test.exclude == "", test.level, makeListRCallback(test.entries, test.err))
   667  		what := fmt.Sprintf("%+v", test)
   668  		assert.Equal(t, test.err, err, what)
   669  		assert.Equal(t, test.want, r.String(), what)
   670  	}
   671  }
   672  
   673  func TestWalkRDirTreeExclude(t *testing.T) {
   674  	ctx := context.Background()
   675  	fi := filter.GetConfig(ctx)
   676  	for _, test := range []struct {
   677  		entries     fs.DirEntries
   678  		want        string
   679  		err         error
   680  		root        string
   681  		level       int
   682  		excludeFile string
   683  		includeAll  bool
   684  	}{
   685  		{fs.DirEntries{mockobject.Object("a"), mockobject.Object("ignore")}, "", nil, "", -1, "ignore", false},
   686  		{fs.DirEntries{mockobject.Object("a")}, `/
   687    a
   688  `, nil, "", -1, "ignore", false},
   689  		{fs.DirEntries{
   690  			mockobject.Object("a"),
   691  			mockobject.Object("b/b"),
   692  			mockobject.Object("b/.ignore"),
   693  		}, `/
   694    a
   695  `, nil, "", -1, ".ignore", false},
   696  		{fs.DirEntries{
   697  			mockobject.Object("a"),
   698  			mockobject.Object("b/.ignore"),
   699  			mockobject.Object("b/b"),
   700  		}, `/
   701    a
   702    b/
   703  b/
   704    .ignore
   705    b
   706  `, nil, "", -1, ".ignore", true},
   707  		{fs.DirEntries{
   708  			mockobject.Object("a"),
   709  			mockobject.Object("b/b"),
   710  			mockobject.Object("b/c/d/e"),
   711  			mockobject.Object("b/c/ign"),
   712  			mockobject.Object("b/c/x"),
   713  		}, `/
   714    a
   715    b/
   716  b/
   717    b
   718  `, nil, "", -1, "ign", false},
   719  		{fs.DirEntries{
   720  			mockobject.Object("a"),
   721  			mockobject.Object("b/b"),
   722  			mockobject.Object("b/c/d/e"),
   723  			mockobject.Object("b/c/ign"),
   724  			mockobject.Object("b/c/x"),
   725  		}, `/
   726    a
   727    b/
   728  b/
   729    b
   730    c/
   731  b/c/
   732    d/
   733    ign
   734    x
   735  b/c/d/
   736    e
   737  `, nil, "", -1, "ign", true},
   738  	} {
   739  		fi.Opt.ExcludeFile = []string{test.excludeFile}
   740  		r, err := walkRDirTree(context.Background(), nil, test.root, test.includeAll, test.level, makeListRCallback(test.entries, test.err))
   741  		assert.Equal(t, test.err, err, fmt.Sprintf("%+v", test))
   742  		assert.Equal(t, test.want, r.String(), fmt.Sprintf("%+v", test))
   743  	}
   744  	// Set to default value, to avoid side effects
   745  	fi.Opt.ExcludeFile = nil
   746  }
   747  
   748  func TestListType(t *testing.T) {
   749  	assert.Equal(t, true, ListObjects.Objects())
   750  	assert.Equal(t, false, ListObjects.Dirs())
   751  	assert.Equal(t, false, ListDirs.Objects())
   752  	assert.Equal(t, true, ListDirs.Dirs())
   753  	assert.Equal(t, true, ListAll.Objects())
   754  	assert.Equal(t, true, ListAll.Dirs())
   755  
   756  	var (
   757  		a           = mockobject.Object("a")
   758  		b           = mockobject.Object("b")
   759  		dir         = mockdir.New("dir")
   760  		adir        = mockobject.Object("dir/a")
   761  		dir2        = mockdir.New("dir2")
   762  		origEntries = fs.DirEntries{
   763  			a, b, dir, adir, dir2,
   764  		}
   765  		dirEntries = fs.DirEntries{
   766  			dir, dir2,
   767  		}
   768  		objEntries = fs.DirEntries{
   769  			a, b, adir,
   770  		}
   771  	)
   772  	copyOrigEntries := func() (out fs.DirEntries) {
   773  		out = make(fs.DirEntries, len(origEntries))
   774  		copy(out, origEntries)
   775  		return out
   776  	}
   777  
   778  	got := copyOrigEntries()
   779  	ListAll.Filter(&got)
   780  	assert.Equal(t, origEntries, got)
   781  
   782  	got = copyOrigEntries()
   783  	ListObjects.Filter(&got)
   784  	assert.Equal(t, objEntries, got)
   785  
   786  	got = copyOrigEntries()
   787  	ListDirs.Filter(&got)
   788  	assert.Equal(t, dirEntries, got)
   789  }
   790  
   791  func TestListR(t *testing.T) {
   792  	ctx := context.Background()
   793  	objects := fs.DirEntries{
   794  		mockobject.Object("a"),
   795  		mockobject.Object("b"),
   796  		mockdir.New("dir"),
   797  		mockobject.Object("dir/a"),
   798  		mockobject.Object("dir/b"),
   799  		mockobject.Object("dir/c"),
   800  	}
   801  	f, err := mockfs.NewFs(ctx, "mock", "/", nil)
   802  	require.NoError(t, err)
   803  	var got []string
   804  	clearCallback := func() {
   805  		got = nil
   806  	}
   807  	callback := func(entries fs.DirEntries) error {
   808  		for _, entry := range entries {
   809  			got = append(got, entry.Remote())
   810  		}
   811  		return nil
   812  	}
   813  	doListR := func(ctx context.Context, dir string, callback fs.ListRCallback) error {
   814  		var os fs.DirEntries
   815  		for _, o := range objects {
   816  			if dir == "" || strings.HasPrefix(o.Remote(), dir+"/") {
   817  				os = append(os, o)
   818  			}
   819  		}
   820  		return callback(os)
   821  	}
   822  
   823  	fi, err := filter.NewFilter(nil)
   824  	require.NoError(t, err)
   825  	require.NoError(t, fi.AddRule("+ b"))
   826  	require.NoError(t, fi.AddRule("- *"))
   827  
   828  	// Change the active filter
   829  	ctx = filter.ReplaceConfig(ctx, fi)
   830  
   831  	// Base case
   832  	clearCallback()
   833  	err = listR(ctx, f, "", true, ListAll, callback, doListR, false)
   834  	require.NoError(t, err)
   835  	require.Equal(t, []string{"a", "b", "dir", "dir/a", "dir/b", "dir/c"}, got)
   836  
   837  	// Base case - with Objects
   838  	clearCallback()
   839  	err = listR(ctx, f, "", true, ListObjects, callback, doListR, false)
   840  	require.NoError(t, err)
   841  	require.Equal(t, []string{"a", "b", "dir/a", "dir/b", "dir/c"}, got)
   842  
   843  	// Base case - with Dirs
   844  	clearCallback()
   845  	err = listR(ctx, f, "", true, ListDirs, callback, doListR, false)
   846  	require.NoError(t, err)
   847  	require.Equal(t, []string{"dir"}, got)
   848  
   849  	// With filter
   850  	clearCallback()
   851  	err = listR(ctx, f, "", false, ListAll, callback, doListR, false)
   852  	require.NoError(t, err)
   853  	require.Equal(t, []string{"b", "dir", "dir/b"}, got)
   854  
   855  	// With filter - with Objects
   856  	clearCallback()
   857  	err = listR(ctx, f, "", false, ListObjects, callback, doListR, false)
   858  	require.NoError(t, err)
   859  	require.Equal(t, []string{"b", "dir/b"}, got)
   860  
   861  	// With filter - with Dir
   862  	clearCallback()
   863  	err = listR(ctx, f, "", false, ListDirs, callback, doListR, false)
   864  	require.NoError(t, err)
   865  	require.Equal(t, []string{"dir"}, got)
   866  
   867  	// With filter and subdir
   868  	clearCallback()
   869  	err = listR(ctx, f, "dir", false, ListAll, callback, doListR, false)
   870  	require.NoError(t, err)
   871  	require.Equal(t, []string{"dir/b"}, got)
   872  
   873  	// Now bucket-based
   874  	objects = fs.DirEntries{
   875  		mockobject.Object("a"),
   876  		mockobject.Object("b"),
   877  		mockobject.Object("dir/a"),
   878  		mockobject.Object("dir/b"),
   879  		mockobject.Object("dir/subdir/c"),
   880  		mockdir.New("dir/subdir"),
   881  	}
   882  
   883  	// Base case
   884  	clearCallback()
   885  	err = listR(ctx, f, "", true, ListAll, callback, doListR, true)
   886  	require.NoError(t, err)
   887  	require.Equal(t, []string{"a", "b", "dir/a", "dir/b", "dir/subdir/c", "dir/subdir", "dir"}, got)
   888  
   889  	// With filter
   890  	clearCallback()
   891  	err = listR(ctx, f, "", false, ListAll, callback, doListR, true)
   892  	require.NoError(t, err)
   893  	require.Equal(t, []string{"b", "dir/b", "dir/subdir", "dir"}, got)
   894  
   895  	// With filter and subdir
   896  	clearCallback()
   897  	err = listR(ctx, f, "dir", false, ListAll, callback, doListR, true)
   898  	require.NoError(t, err)
   899  	require.Equal(t, []string{"dir/b", "dir/subdir"}, got)
   900  
   901  	// With filter and subdir - with Objects
   902  	clearCallback()
   903  	err = listR(ctx, f, "dir", false, ListObjects, callback, doListR, true)
   904  	require.NoError(t, err)
   905  	require.Equal(t, []string{"dir/b"}, got)
   906  
   907  	// With filter and subdir - with Dirs
   908  	clearCallback()
   909  	err = listR(ctx, f, "dir", false, ListDirs, callback, doListR, true)
   910  	require.NoError(t, err)
   911  	require.Equal(t, []string{"dir/subdir"}, got)
   912  }
   913  
   914  func TestDirMapAdd(t *testing.T) {
   915  	type add struct {
   916  		dir  string
   917  		sent bool
   918  	}
   919  	for i, test := range []struct {
   920  		root string
   921  		in   []add
   922  		want map[string]bool
   923  	}{
   924  		{
   925  			root: "",
   926  			in: []add{
   927  				{"", true},
   928  			},
   929  			want: map[string]bool{},
   930  		},
   931  		{
   932  			root: "",
   933  			in: []add{
   934  				{"a/b/c", true},
   935  			},
   936  			want: map[string]bool{
   937  				"a/b/c": true,
   938  				"a/b":   false,
   939  				"a":     false,
   940  			},
   941  		},
   942  		{
   943  			root: "",
   944  			in: []add{
   945  				{"a/b/c", true},
   946  				{"a/b", true},
   947  			},
   948  			want: map[string]bool{
   949  				"a/b/c": true,
   950  				"a/b":   true,
   951  				"a":     false,
   952  			},
   953  		},
   954  		{
   955  			root: "",
   956  			in: []add{
   957  				{"a/b", true},
   958  				{"a/b/c", false},
   959  			},
   960  			want: map[string]bool{
   961  				"a/b/c": false,
   962  				"a/b":   true,
   963  				"a":     false,
   964  			},
   965  		},
   966  		{
   967  			root: "root",
   968  			in: []add{
   969  				{"root/a/b", true},
   970  				{"root/a/b/c", false},
   971  			},
   972  			want: map[string]bool{
   973  				"root/a/b/c": false,
   974  				"root/a/b":   true,
   975  				"root/a":     false,
   976  			},
   977  		},
   978  	} {
   979  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   980  			dm := newDirMap(test.root)
   981  			for _, item := range test.in {
   982  				dm.add(item.dir, item.sent)
   983  			}
   984  			assert.Equal(t, test.want, dm.m)
   985  		})
   986  	}
   987  }
   988  
   989  func TestDirMapAddEntries(t *testing.T) {
   990  	dm := newDirMap("")
   991  	entries := fs.DirEntries{
   992  		mockobject.Object("dir/a"),
   993  		mockobject.Object("dir/b"),
   994  		mockdir.New("dir"),
   995  		mockobject.Object("dir2/a"),
   996  		mockobject.Object("dir2/b"),
   997  	}
   998  	require.NoError(t, dm.addEntries(entries))
   999  	assert.Equal(t, map[string]bool{"dir": true, "dir2": false}, dm.m)
  1000  }
  1001  
  1002  func TestDirMapSendEntries(t *testing.T) {
  1003  	var got []string
  1004  	clearCallback := func() {
  1005  		got = nil
  1006  	}
  1007  	callback := func(entries fs.DirEntries) error {
  1008  		for _, entry := range entries {
  1009  			got = append(got, entry.Remote())
  1010  		}
  1011  		return nil
  1012  	}
  1013  
  1014  	// general test
  1015  	dm := newDirMap("")
  1016  	entries := fs.DirEntries{
  1017  		mockobject.Object("dir/a"),
  1018  		mockobject.Object("dir/b"),
  1019  		mockdir.New("dir"),
  1020  		mockobject.Object("dir2/a"),
  1021  		mockobject.Object("dir2/b"),
  1022  		mockobject.Object("dir1/a"),
  1023  		mockobject.Object("dir3/b"),
  1024  	}
  1025  	require.NoError(t, dm.addEntries(entries))
  1026  	clearCallback()
  1027  	err := dm.sendEntries(callback)
  1028  	require.NoError(t, err)
  1029  	assert.Equal(t, []string{
  1030  		"dir1",
  1031  		"dir2",
  1032  		"dir3",
  1033  	}, got)
  1034  
  1035  	// return error from callback
  1036  	callback2 := func(entries fs.DirEntries) error {
  1037  		return io.EOF
  1038  	}
  1039  	err = dm.sendEntries(callback2)
  1040  	require.Equal(t, io.EOF, err)
  1041  
  1042  	// empty
  1043  	dm = newDirMap("")
  1044  	clearCallback()
  1045  	err = dm.sendEntries(callback)
  1046  	require.NoError(t, err)
  1047  	assert.Equal(t, []string(nil), got)
  1048  }