github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/fs/filter/filter_test.go (about) 1 package filter 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "strings" 9 "sync" 10 "testing" 11 "time" 12 13 "github.com/rclone/rclone/fs" 14 "github.com/rclone/rclone/fstest/mockobject" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 ) 18 19 func TestNewFilterDefault(t *testing.T) { 20 f, err := NewFilter(nil) 21 require.NoError(t, err) 22 assert.False(t, f.Opt.DeleteExcluded) 23 assert.Equal(t, fs.SizeSuffix(-1), f.Opt.MinSize) 24 assert.Equal(t, fs.SizeSuffix(-1), f.Opt.MaxSize) 25 assert.Len(t, f.fileRules.rules, 0) 26 assert.Len(t, f.dirRules.rules, 0) 27 assert.Nil(t, f.files) 28 assert.True(t, f.InActive()) 29 } 30 31 // testFile creates a temp file with the contents 32 func testFile(t *testing.T, contents string) string { 33 out, err := ioutil.TempFile("", "filter_test") 34 require.NoError(t, err) 35 defer func() { 36 err := out.Close() 37 require.NoError(t, err) 38 }() 39 _, err = out.Write([]byte(contents)) 40 require.NoError(t, err) 41 s := out.Name() 42 return s 43 } 44 45 func TestNewFilterForbiddenMixOfFilesFromAndFilterRule(t *testing.T) { 46 Opt := DefaultOpt 47 48 // Set up the input 49 Opt.FilterRule = []string{"- filter1", "- filter1b"} 50 Opt.FilesFrom = []string{testFile(t, "#comment\nfiles1\nfiles2\n")} 51 52 rm := func(p string) { 53 err := os.Remove(p) 54 if err != nil { 55 t.Logf("error removing %q: %v", p, err) 56 } 57 } 58 // Reset the input 59 defer func() { 60 rm(Opt.FilesFrom[0]) 61 }() 62 63 _, err := NewFilter(&Opt) 64 require.Error(t, err) 65 require.Contains(t, err.Error(), "The usage of --files-from overrides all other filters") 66 } 67 68 func TestNewFilterWithFilesFromAlone(t *testing.T) { 69 Opt := DefaultOpt 70 71 // Set up the input 72 Opt.FilesFrom = []string{testFile(t, "#comment\nfiles1\nfiles2\n")} 73 74 rm := func(p string) { 75 err := os.Remove(p) 76 if err != nil { 77 t.Logf("error removing %q: %v", p, err) 78 } 79 } 80 // Reset the input 81 defer func() { 82 rm(Opt.FilesFrom[0]) 83 }() 84 85 f, err := NewFilter(&Opt) 86 require.NoError(t, err) 87 assert.Len(t, f.files, 2) 88 for _, name := range []string{"files1", "files2"} { 89 _, ok := f.files[name] 90 if !ok { 91 t.Errorf("Didn't find file %q in f.files", name) 92 } 93 } 94 } 95 96 func TestNewFilterFullExceptFilesFromOpt(t *testing.T) { 97 Opt := DefaultOpt 98 99 mins := fs.SizeSuffix(100 * 1024) 100 maxs := fs.SizeSuffix(1000 * 1024) 101 102 // Set up the input 103 Opt.DeleteExcluded = true 104 Opt.FilterRule = []string{"- filter1", "- filter1b"} 105 Opt.FilterFrom = []string{testFile(t, "#comment\n+ filter2\n- filter3\n")} 106 Opt.ExcludeRule = []string{"exclude1"} 107 Opt.ExcludeFrom = []string{testFile(t, "#comment\nexclude2\nexclude3\n")} 108 Opt.IncludeRule = []string{"include1"} 109 Opt.IncludeFrom = []string{testFile(t, "#comment\ninclude2\ninclude3\n")} 110 Opt.MinSize = mins 111 Opt.MaxSize = maxs 112 113 rm := func(p string) { 114 err := os.Remove(p) 115 if err != nil { 116 t.Logf("error removing %q: %v", p, err) 117 } 118 } 119 // Reset the input 120 defer func() { 121 rm(Opt.FilterFrom[0]) 122 rm(Opt.ExcludeFrom[0]) 123 rm(Opt.IncludeFrom[0]) 124 }() 125 126 f, err := NewFilter(&Opt) 127 require.NoError(t, err) 128 assert.True(t, f.Opt.DeleteExcluded) 129 assert.Equal(t, f.Opt.MinSize, mins) 130 assert.Equal(t, f.Opt.MaxSize, maxs) 131 got := f.DumpFilters() 132 want := `--- File filter rules --- 133 + (^|/)include1$ 134 + (^|/)include2$ 135 + (^|/)include3$ 136 - (^|/)exclude1$ 137 - (^|/)exclude2$ 138 - (^|/)exclude3$ 139 - (^|/)filter1$ 140 - (^|/)filter1b$ 141 + (^|/)filter2$ 142 - (^|/)filter3$ 143 - ^.*$ 144 --- Directory filter rules --- 145 + ^.*$ 146 - ^.*$` 147 assert.Equal(t, want, got) 148 assert.False(t, f.InActive()) 149 } 150 151 type includeTest struct { 152 in string 153 size int64 154 modTime int64 155 want bool 156 } 157 158 func testInclude(t *testing.T, f *Filter, tests []includeTest) { 159 for _, test := range tests { 160 got := f.Include(test.in, test.size, time.Unix(test.modTime, 0)) 161 assert.Equal(t, test.want, got, fmt.Sprintf("in=%q, size=%v, modTime=%v", test.in, test.size, time.Unix(test.modTime, 0))) 162 } 163 } 164 165 type includeDirTest struct { 166 in string 167 want bool 168 } 169 170 func testDirInclude(t *testing.T, f *Filter, tests []includeDirTest) { 171 for _, test := range tests { 172 got, err := f.IncludeDirectory(context.Background(), nil)(test.in) 173 require.NoError(t, err) 174 assert.Equal(t, test.want, got, test.in) 175 } 176 } 177 178 func TestNewFilterIncludeFiles(t *testing.T) { 179 f, err := NewFilter(nil) 180 require.NoError(t, err) 181 err = f.AddFile("file1.jpg") 182 require.NoError(t, err) 183 err = f.AddFile("/file2.jpg") 184 require.NoError(t, err) 185 assert.Equal(t, FilesMap{ 186 "file1.jpg": {}, 187 "file2.jpg": {}, 188 }, f.files) 189 assert.Equal(t, FilesMap{}, f.dirs) 190 testInclude(t, f, []includeTest{ 191 {"file1.jpg", 0, 0, true}, 192 {"file2.jpg", 1, 0, true}, 193 {"potato/file2.jpg", 2, 0, false}, 194 {"file3.jpg", 3, 0, false}, 195 }) 196 assert.False(t, f.InActive()) 197 } 198 199 func TestNewFilterIncludeFilesDirs(t *testing.T) { 200 f, err := NewFilter(nil) 201 require.NoError(t, err) 202 for _, path := range []string{ 203 "path/to/dir/file1.png", 204 "/path/to/dir/file2.png", 205 "/path/to/file3.png", 206 "/path/to/dir2/file4.png", 207 } { 208 err = f.AddFile(path) 209 require.NoError(t, err) 210 } 211 assert.Equal(t, FilesMap{ 212 "path": {}, 213 "path/to": {}, 214 "path/to/dir": {}, 215 "path/to/dir2": {}, 216 }, f.dirs) 217 testDirInclude(t, f, []includeDirTest{ 218 {"path", true}, 219 {"path/to", true}, 220 {"path/to/", true}, 221 {"/path/to", true}, 222 {"/path/to/", true}, 223 {"path/to/dir", true}, 224 {"path/to/dir2", true}, 225 {"path/too", false}, 226 {"path/three", false}, 227 {"four", false}, 228 }) 229 } 230 231 func TestNewFilterHaveFilesFrom(t *testing.T) { 232 f, err := NewFilter(nil) 233 require.NoError(t, err) 234 235 assert.Equal(t, false, f.HaveFilesFrom()) 236 237 require.NoError(t, f.AddFile("file")) 238 239 assert.Equal(t, true, f.HaveFilesFrom()) 240 } 241 242 func TestNewFilterMakeListR(t *testing.T) { 243 f, err := NewFilter(nil) 244 require.NoError(t, err) 245 246 // Check error if no files 247 listR := f.MakeListR(context.Background(), nil) 248 err = listR(context.Background(), "", nil) 249 assert.EqualError(t, err, errFilesFromNotSet.Error()) 250 251 // Add some files 252 for _, path := range []string{ 253 "path/to/dir/file1.png", 254 "/path/to/dir/file2.png", 255 "/path/to/file3.png", 256 "/path/to/dir2/file4.png", 257 "notfound", 258 } { 259 err = f.AddFile(path) 260 require.NoError(t, err) 261 } 262 263 assert.Equal(t, 5, len(f.files)) 264 265 // NewObject function for MakeListR 266 newObjects := FilesMap{} 267 var newObjectMu sync.Mutex 268 NewObject := func(ctx context.Context, remote string) (fs.Object, error) { 269 newObjectMu.Lock() 270 defer newObjectMu.Unlock() 271 if remote == "notfound" { 272 return nil, fs.ErrorObjectNotFound 273 } else if remote == "error" { 274 return nil, assert.AnError 275 } 276 newObjects[remote] = struct{}{} 277 return mockobject.New(remote), nil 278 279 } 280 281 // Callback for ListRFn 282 listRObjects := FilesMap{} 283 var callbackMu sync.Mutex 284 listRcallback := func(entries fs.DirEntries) error { 285 callbackMu.Lock() 286 defer callbackMu.Unlock() 287 for _, entry := range entries { 288 listRObjects[entry.Remote()] = struct{}{} 289 } 290 return nil 291 } 292 293 // Make the listR and call it 294 listR = f.MakeListR(context.Background(), NewObject) 295 err = listR(context.Background(), "", listRcallback) 296 require.NoError(t, err) 297 298 // Check that the correct objects were created and listed 299 want := FilesMap{ 300 "path/to/dir/file1.png": {}, 301 "path/to/dir/file2.png": {}, 302 "path/to/file3.png": {}, 303 "path/to/dir2/file4.png": {}, 304 } 305 assert.Equal(t, want, newObjects) 306 assert.Equal(t, want, listRObjects) 307 308 // Now check an error is returned from NewObject 309 require.NoError(t, f.AddFile("error")) 310 err = listR(context.Background(), "", listRcallback) 311 require.EqualError(t, err, assert.AnError.Error()) 312 } 313 314 func TestNewFilterMinSize(t *testing.T) { 315 f, err := NewFilter(nil) 316 require.NoError(t, err) 317 f.Opt.MinSize = 100 318 testInclude(t, f, []includeTest{ 319 {"file1.jpg", 100, 0, true}, 320 {"file2.jpg", 101, 0, true}, 321 {"potato/file2.jpg", 99, 0, false}, 322 }) 323 assert.False(t, f.InActive()) 324 } 325 326 func TestNewFilterMaxSize(t *testing.T) { 327 f, err := NewFilter(nil) 328 require.NoError(t, err) 329 f.Opt.MaxSize = 100 330 testInclude(t, f, []includeTest{ 331 {"file1.jpg", 100, 0, true}, 332 {"file2.jpg", 101, 0, false}, 333 {"potato/file2.jpg", 99, 0, true}, 334 }) 335 assert.False(t, f.InActive()) 336 } 337 338 func TestNewFilterMinAndMaxAge(t *testing.T) { 339 f, err := NewFilter(nil) 340 require.NoError(t, err) 341 f.ModTimeFrom = time.Unix(1440000002, 0) 342 f.ModTimeTo = time.Unix(1440000003, 0) 343 testInclude(t, f, []includeTest{ 344 {"file1.jpg", 100, 1440000000, false}, 345 {"file2.jpg", 101, 1440000001, false}, 346 {"file3.jpg", 102, 1440000002, true}, 347 {"potato/file1.jpg", 98, 1440000003, true}, 348 {"potato/file2.jpg", 99, 1440000004, false}, 349 }) 350 assert.False(t, f.InActive()) 351 } 352 353 func TestNewFilterMinAge(t *testing.T) { 354 f, err := NewFilter(nil) 355 require.NoError(t, err) 356 f.ModTimeTo = time.Unix(1440000002, 0) 357 testInclude(t, f, []includeTest{ 358 {"file1.jpg", 100, 1440000000, true}, 359 {"file2.jpg", 101, 1440000001, true}, 360 {"file3.jpg", 102, 1440000002, true}, 361 {"potato/file1.jpg", 98, 1440000003, false}, 362 {"potato/file2.jpg", 99, 1440000004, false}, 363 }) 364 assert.False(t, f.InActive()) 365 } 366 367 func TestNewFilterMaxAge(t *testing.T) { 368 f, err := NewFilter(nil) 369 require.NoError(t, err) 370 f.ModTimeFrom = time.Unix(1440000002, 0) 371 testInclude(t, f, []includeTest{ 372 {"file1.jpg", 100, 1440000000, false}, 373 {"file2.jpg", 101, 1440000001, false}, 374 {"file3.jpg", 102, 1440000002, true}, 375 {"potato/file1.jpg", 98, 1440000003, true}, 376 {"potato/file2.jpg", 99, 1440000004, true}, 377 }) 378 assert.False(t, f.InActive()) 379 } 380 381 func TestNewFilterMatches(t *testing.T) { 382 f, err := NewFilter(nil) 383 require.NoError(t, err) 384 add := func(s string) { 385 err := f.AddRule(s) 386 require.NoError(t, err) 387 } 388 add("+ cleared") 389 add("!") 390 add("- /file1.jpg") 391 add("+ /file2.png") 392 add("+ /*.jpg") 393 add("- /*.png") 394 add("- /potato") 395 add("+ /sausage1") 396 add("+ /sausage2*") 397 add("+ /sausage3**") 398 add("+ /a/*.jpg") 399 add("- *") 400 testInclude(t, f, []includeTest{ 401 {"cleared", 100, 0, false}, 402 {"file1.jpg", 100, 0, false}, 403 {"file2.png", 100, 0, true}, 404 {"FILE2.png", 100, 0, false}, 405 {"afile2.png", 100, 0, false}, 406 {"file3.jpg", 101, 0, true}, 407 {"file4.png", 101, 0, false}, 408 {"potato", 101, 0, false}, 409 {"sausage1", 101, 0, true}, 410 {"sausage1/potato", 101, 0, false}, 411 {"sausage2potato", 101, 0, true}, 412 {"sausage2/potato", 101, 0, false}, 413 {"sausage3/potato", 101, 0, true}, 414 {"a/one.jpg", 101, 0, true}, 415 {"a/one.png", 101, 0, false}, 416 {"unicorn", 99, 0, false}, 417 }) 418 testDirInclude(t, f, []includeDirTest{ 419 {"sausage1", false}, 420 {"sausage2", false}, 421 {"sausage2/sub", false}, 422 {"sausage2/sub/dir", false}, 423 {"sausage3", true}, 424 {"SAUSAGE3", false}, 425 {"sausage3/sub", true}, 426 {"sausage3/sub/dir", true}, 427 {"sausage4", false}, 428 {"a", true}, 429 }) 430 assert.False(t, f.InActive()) 431 } 432 433 func TestNewFilterMatchesIgnoreCase(t *testing.T) { 434 f, err := NewFilter(nil) 435 require.NoError(t, err) 436 f.Opt.IgnoreCase = true 437 add := func(s string) { 438 err := f.AddRule(s) 439 require.NoError(t, err) 440 } 441 add("+ /file2.png") 442 add("+ /sausage3**") 443 add("- *") 444 testInclude(t, f, []includeTest{ 445 {"file2.png", 100, 0, true}, 446 {"FILE2.png", 100, 0, true}, 447 }) 448 testDirInclude(t, f, []includeDirTest{ 449 {"sausage3", true}, 450 {"SAUSAGE3", true}, 451 }) 452 assert.False(t, f.InActive()) 453 } 454 455 func TestFilterAddDirRuleOrFileRule(t *testing.T) { 456 for _, test := range []struct { 457 included bool 458 glob string 459 want string 460 }{ 461 { 462 false, 463 "potato", 464 `--- File filter rules --- 465 - (^|/)potato$ 466 --- Directory filter rules ---`, 467 }, 468 { 469 true, 470 "potato", 471 `--- File filter rules --- 472 + (^|/)potato$ 473 --- Directory filter rules --- 474 + ^.*$`, 475 }, 476 { 477 false, 478 "*", 479 `--- File filter rules --- 480 - (^|/)[^/]*$ 481 --- Directory filter rules --- 482 - ^.*$`, 483 }, 484 { 485 true, 486 "*", 487 `--- File filter rules --- 488 + (^|/)[^/]*$ 489 --- Directory filter rules --- 490 + ^.*$`, 491 }, 492 { 493 false, 494 ".*{,/**}", 495 `--- File filter rules --- 496 - (^|/)\.[^/]*(|/.*)$ 497 --- Directory filter rules --- 498 - (^|/)\.[^/]*(|/.*)$`, 499 }, 500 { 501 true, 502 "a/b/c/d", 503 `--- File filter rules --- 504 + (^|/)a/b/c/d$ 505 --- Directory filter rules --- 506 + (^|/)a/b/c/$ 507 + (^|/)a/b/$ 508 + (^|/)a/$`, 509 }, 510 } { 511 f, err := NewFilter(nil) 512 require.NoError(t, err) 513 err = f.Add(test.included, test.glob) 514 require.NoError(t, err) 515 got := f.DumpFilters() 516 assert.Equal(t, test.want, got, fmt.Sprintf("Add(%v, %q)", test.included, test.glob)) 517 } 518 } 519 520 func TestFilterForEachLine(t *testing.T) { 521 file := testFile(t, `; comment 522 one 523 # another comment 524 525 526 two 527 # indented comment 528 three 529 four 530 five 531 six `) 532 defer func() { 533 err := os.Remove(file) 534 require.NoError(t, err) 535 }() 536 lines := []string{} 537 err := forEachLine(file, func(s string) error { 538 lines = append(lines, s) 539 return nil 540 }) 541 require.NoError(t, err) 542 assert.Equal(t, "one,two,three,four,five,six", strings.Join(lines, ",")) 543 } 544 545 func TestFilterMatchesFromDocs(t *testing.T) { 546 for _, test := range []struct { 547 glob string 548 included bool 549 file string 550 ignoreCase bool 551 }{ 552 {"file.jpg", true, "file.jpg", false}, 553 {"file.jpg", true, "directory/file.jpg", false}, 554 {"file.jpg", false, "afile.jpg", false}, 555 {"file.jpg", false, "directory/afile.jpg", false}, 556 {"/file.jpg", true, "file.jpg", false}, 557 {"/file.jpg", false, "afile.jpg", false}, 558 {"/file.jpg", false, "directory/file.jpg", false}, 559 {"*.jpg", true, "file.jpg", false}, 560 {"*.jpg", true, "directory/file.jpg", false}, 561 {"*.jpg", false, "file.jpg/anotherfile.png", false}, 562 {"dir/**", true, "dir/file.jpg", false}, 563 {"dir/**", true, "dir/dir1/dir2/file.jpg", false}, 564 {"dir/**", false, "directory/file.jpg", false}, 565 {"dir/**", false, "adir/file.jpg", false}, 566 {"l?ss", true, "less", false}, 567 {"l?ss", true, "lass", false}, 568 {"l?ss", false, "floss", false}, 569 {"h[ae]llo", true, "hello", false}, 570 {"h[ae]llo", true, "hallo", false}, 571 {"h[ae]llo", false, "hullo", false}, 572 {"{one,two}_potato", true, "one_potato", false}, 573 {"{one,two}_potato", true, "two_potato", false}, 574 {"{one,two}_potato", false, "three_potato", false}, 575 {"{one,two}_potato", false, "_potato", false}, 576 {"\\*.jpg", true, "*.jpg", false}, 577 {"\\\\.jpg", true, "\\.jpg", false}, 578 {"\\[one\\].jpg", true, "[one].jpg", false}, 579 {"potato", true, "potato", false}, 580 {"potato", false, "POTATO", false}, 581 {"potato", true, "potato", true}, 582 {"potato", true, "POTATO", true}, 583 } { 584 f, err := NewFilter(nil) 585 require.NoError(t, err) 586 if test.ignoreCase { 587 f.Opt.IgnoreCase = true 588 } 589 err = f.Add(true, test.glob) 590 require.NoError(t, err) 591 err = f.Add(false, "*") 592 require.NoError(t, err) 593 included := f.Include(test.file, 0, time.Unix(0, 0)) 594 if included != test.included { 595 t.Errorf("%q match %q: want %v got %v", test.glob, test.file, test.included, included) 596 } 597 } 598 } 599 600 func TestNewFilterUsesDirectoryFilters(t *testing.T) { 601 for i, test := range []struct { 602 rules []string 603 want bool 604 }{ 605 { 606 rules: []string{}, 607 want: false, 608 }, 609 { 610 rules: []string{ 611 "+ *", 612 }, 613 want: false, 614 }, 615 { 616 rules: []string{ 617 "+ *.jpg", 618 "- *", 619 }, 620 want: false, 621 }, 622 { 623 rules: []string{ 624 "- *.jpg", 625 }, 626 want: false, 627 }, 628 { 629 rules: []string{ 630 "- *.jpg", 631 "+ *", 632 }, 633 want: false, 634 }, 635 { 636 rules: []string{ 637 "+ dir/*.jpg", 638 "- *", 639 }, 640 want: true, 641 }, 642 { 643 rules: []string{ 644 "+ dir/**", 645 }, 646 want: true, 647 }, 648 { 649 rules: []string{ 650 "- dir/**", 651 }, 652 want: true, 653 }, 654 { 655 rules: []string{ 656 "- /dir/**", 657 }, 658 want: true, 659 }, 660 } { 661 what := fmt.Sprintf("#%d", i) 662 f, err := NewFilter(nil) 663 require.NoError(t, err) 664 for _, rule := range test.rules { 665 err := f.AddRule(rule) 666 require.NoError(t, err, what) 667 } 668 got := f.UsesDirectoryFilters() 669 assert.Equal(t, test.want, got, fmt.Sprintf("%s: %s", what, f.DumpFilters())) 670 } 671 }