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 }