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  }