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