package filepathfilter import ( "runtime" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestPatternMatch(t *testing.T) { for _, wildcard := range []string{"*", "*.*"} { assertPatternMatch(t, wildcard, "a", "a/", "a.a", "a/b", "a/b/", "a/b.b", "a/b/c", "a/b/c/", "a/b/c.c", ) } assertPatternMatch(t, "filename.txt", "filename.txt") assertPatternMatch(t, "*.txt", "filename.txt") refutePatternMatch(t, "*.tx", "filename.txt") assertPatternMatch(t, "f*.txt", "filename.txt") refutePatternMatch(t, "g*.txt", "filename.txt") assertPatternMatch(t, "file*", "filename.txt") refutePatternMatch(t, "file", "filename.txt") // With no path separators, should match in subfolders assertPatternMatch(t, "*.txt", "sub/filename.txt") refutePatternMatch(t, "*.tx", "sub/filename.txt") assertPatternMatch(t, "f*.txt", "sub/filename.txt") refutePatternMatch(t, "g*.txt", "sub/filename.txt") assertPatternMatch(t, "file*", "sub/filename.txt") refutePatternMatch(t, "file", "sub/filename.txt") // matches only in subdir assertPatternMatch(t, "sub/*.txt", "sub/filename.txt") refutePatternMatch(t, "sub/*.txt", "top/sub/filename.txt", "sub/filename.dat", "other/filename.txt", ) // Needs wildcard for exact filename assertPatternMatch(t, "**/filename.txt", "sub/sub/sub/filename.txt") // Should not match dots to subparts refutePatternMatch(t, "*.ign", "sub/shouldignoreme.txt") // Path specific assertPatternMatch(t, "sub", "sub/", "sub", "sub/filename.txt", "top/sub/", "top/sub", "top/sub/filename.txt", ) assertPatternMatch(t, "sub/", "sub/filename.txt", "top/sub/filename.txt") assertPatternMatch(t, "/sub", "sub/", "sub", "sub/filename.txt") assertPatternMatch(t, "/sub/", "sub/filename.txt") refutePatternMatch(t, "/sub", "subfilename.txt", "top/sub/", "top/sub", "top/sub/filename.txt") refutePatternMatch(t, "sub", "subfilename.txt") refutePatternMatch(t, "sub/", "subfilename.txt") refutePatternMatch(t, "/sub/", "subfilename.txt", "top/sub/filename.txt") // nested path assertPatternMatch(t, "top/sub", "top/sub/filename.txt", "top/sub/", "top/sub", "root/top/sub/filename.txt", "root/top/sub/", "root/top/sub", ) assertPatternMatch(t, "top/sub/", "top/sub/filename.txt") assertPatternMatch(t, "top/sub/", "root/top/sub/filename.txt") assertPatternMatch(t, "/top/sub", "top/sub/", "top/sub", "top/sub/filename.txt") assertPatternMatch(t, "/top/sub/", "top/sub/filename.txt") refutePatternMatch(t, "top/sub", "top/subfilename.txt") refutePatternMatch(t, "top/sub/", "top/subfilename.txt") refutePatternMatch(t, "/top/sub", "top/subfilename.txt", "root/top/sub/filename.txt", "root/top/sub/", "root/top/sub", ) refutePatternMatch(t, "/top/sub/", "root/top/sub/filename.txt", "top/subfilename.txt", ) // Absolute assertPatternMatch(t, "*.dat", "/path/to/sub/.git/test.dat") assertPatternMatch(t, "**/.git", "/path/to/sub/.git") // Match anything assertPatternMatch(t, ".", "path.txt") assertPatternMatch(t, "./", "path.txt") assertPatternMatch(t, ".\\", "path.txt") } func assertPatternMatch(t *testing.T, pattern string, filenames ...string) { p := NewPattern(pattern) for _, filename := range filenames { assert.True(t, p.Match(filename), "%q should match pattern %q", filename, pattern) } } func refutePatternMatch(t *testing.T, pattern string, filenames ...string) { p := NewPattern(pattern) for _, filename := range filenames { assert.False(t, p.Match(filename), "%q should not match pattern %q", filename, pattern) } } type filterTest struct { expectedResult bool expectedPattern string includes []string excludes []string } type filterPrefixTest struct { expected bool prefixes []string includes []string excludes []string } func (c *filterPrefixTest) Assert(t *testing.T) { f := New(c.platformIncludes(), c.platformExcludes()) prefixes := c.prefixes if runtime.GOOS == "windows" { prefixes = toWindowsPaths(prefixes) } for _, prefix := range prefixes { assert.Equal(t, c.expected, f.HasPrefix(prefix), "expected=%v, prefix=%s", c.expected, prefix) } } func (c *filterPrefixTest) platformIncludes() []string { if runtime.GOOS == "windows" { return toWindowsPaths(c.includes) } return c.includes } func (c *filterPrefixTest) platformExcludes() []string { if runtime.GOOS == "windows" { return toWindowsPaths(c.excludes) } return c.excludes } func toWindowsPaths(paths []string) []string { var out []string for _, path := range paths { out = append(out, strings.Replace(path, "/", "\\", -1)) } return out } func TestFilterHasPrefix(t *testing.T) { prefixes := []string{"foo", "foo/", "foo/bar", "foo/bar/baz", "foo/bar/baz/"} for desc, c := range map[string]*filterPrefixTest{ "empty filter": {true, prefixes, nil, nil}, "path prefix pattern": {true, prefixes, []string{"/foo/bar/baz"}, nil}, "path pattern": {true, prefixes, []string{"foo/bar/baz"}, nil}, "simple ext pattern": {true, prefixes, []string{"*.dat"}, nil}, "pathless wildcard pattern": {true, prefixes, []string{"foo*.dat"}, nil}, "double wildcard pattern": {true, prefixes, []string{"foo/**/baz"}, nil}, "include other dir": {false, prefixes, []string{"other"}, nil}, "exclude pattern": {true, prefixes, nil, []string{"other"}}, "exclude simple ext pattern": {true, prefixes, nil, []string{"*.dat"}}, "exclude pathless wildcard pattern": {true, prefixes, nil, []string{"foo*.dat"}}, } { t.Run(desc, c.Assert) } prefixes = []string{"foo", "foo/", "foo/bar"} for desc, c := range map[string]*filterPrefixTest{ "exclude path prefix pattern": {true, prefixes, nil, []string{"/foo/bar/baz"}}, "exclude path pattern": {true, prefixes, nil, []string{"foo/bar/baz"}}, "exclude double wildcard pattern": {true, prefixes, nil, []string{"foo/**/baz"}}, } { t.Run(desc, c.Assert) } prefixes = []string{"foo/bar/baz", "foo/bar/baz/"} for desc, c := range map[string]*filterPrefixTest{ "exclude path prefix pattern": {false, prefixes, nil, []string{"/foo/bar/baz"}}, "exclude path pattern": {false, prefixes, nil, []string{"foo/bar/baz"}}, } { t.Run(desc, c.Assert) } prefixes = []string{"foo/bar/baz", "foo/test/baz"} for desc, c := range map[string]*filterPrefixTest{ "exclude double wildcard pattern": {false, prefixes, nil, []string{"foo/**/baz"}}, } { t.Run(desc, c.Assert) } } func TestFilterAllows(t *testing.T) { cases := []filterTest{ // Null case filterTest{true, "", nil, nil}, // Inclusion filterTest{true, "*.dat", []string{"*.dat"}, nil}, filterTest{true, "file*.dat", []string{"file*.dat"}, nil}, filterTest{true, "file*", []string{"file*"}, nil}, filterTest{true, "*name.dat", []string{"*name.dat"}, nil}, filterTest{false, "", []string{"/*.dat"}, nil}, filterTest{false, "", []string{"otherfolder/*.dat"}, nil}, filterTest{false, "", []string{"*.nam"}, nil}, filterTest{true, "test/filename.dat", []string{"test/filename.dat"}, nil}, filterTest{true, "test/filename.dat", []string{"test/filename.dat"}, nil}, filterTest{false, "", []string{"blank", "something", "foo"}, nil}, filterTest{false, "", []string{"test/notfilename.dat"}, nil}, filterTest{true, "test", []string{"test"}, nil}, filterTest{true, "test/*", []string{"test/*"}, nil}, filterTest{false, "", []string{"nottest"}, nil}, filterTest{false, "", []string{"nottest/*"}, nil}, filterTest{true, "test/fil*", []string{"test/fil*"}, nil}, filterTest{false, "", []string{"test/g*"}, nil}, filterTest{true, "tes*/*", []string{"tes*/*"}, nil}, filterTest{true, "[Tt]est/[Ff]ilename.dat", []string{"[Tt]est/[Ff]ilename.dat"}, nil}, // Exclusion filterTest{false, "*.dat", nil, []string{"*.dat"}}, filterTest{false, "file*.dat", nil, []string{"file*.dat"}}, filterTest{false, "file*", nil, []string{"file*"}}, filterTest{false, "*name.dat", nil, []string{"*name.dat"}}, filterTest{true, "", nil, []string{"/*.dat"}}, filterTest{true, "", nil, []string{"otherfolder/*.dat"}}, filterTest{false, "test/filename.dat", nil, []string{"test/filename.dat"}}, filterTest{false, "test/filename.dat", nil, []string{"blank", "something", "test/filename.dat", "foo"}}, filterTest{true, "", nil, []string{"blank", "something", "foo"}}, filterTest{true, "", nil, []string{"test/notfilename.dat"}}, filterTest{false, "test", nil, []string{"test"}}, filterTest{false, "test/*", nil, []string{"test/*"}}, filterTest{true, "", nil, []string{"nottest"}}, filterTest{true, "", nil, []string{"nottest/*"}}, filterTest{false, "test/fil*", nil, []string{"test/fil*"}}, filterTest{true, "", nil, []string{"test/g*"}}, filterTest{false, "tes*/*", nil, []string{"tes*/*"}}, filterTest{false, "[Tt]est/[Ff]ilename.dat", nil, []string{"[Tt]est/[Ff]ilename.dat"}}, // // Both filterTest{true, "test/filename.dat", []string{"test/filename.dat"}, []string{"test/notfilename.dat"}}, filterTest{false, "test/filename.dat", []string{"test"}, []string{"test/filename.dat"}}, filterTest{true, "test/*", []string{"test/*"}, []string{"test/notfile*"}}, filterTest{false, "test/file*", []string{"test/*"}, []string{"test/file*"}}, filterTest{false, "test/filename.dat", []string{"another/*", "test/*"}, []string{"test/notfilename.dat", "test/filename.dat"}}, } for _, c := range cases { if runtime.GOOS == "windows" { c.expectedPattern = strings.Replace(c.expectedPattern, "/", "\\", -1) } filter := New(c.includes, c.excludes) r1 := filter.Allows("test/filename.dat") pattern, r2 := filter.AllowsPattern("test/filename.dat") assert.Equal(t, r1, r2, "filepathfilter: expected Allows() and AllowsPattern() to return identical result") assert.Equal(t, c.expectedResult, r2, "includes: %v excludes: %v", c.includes, c.excludes) assert.Equal(t, c.expectedPattern, pattern, "filepathfilter: expected pattern match of: %q, got: %q", c.expectedPattern, pattern) if runtime.GOOS == "windows" { // also test with \ path separators, tolerate mixed separators for i, inc := range c.includes { c.includes[i] = strings.Replace(inc, "/", "\\", -1) } for i, ex := range c.excludes { c.excludes[i] = strings.Replace(ex, "/", "\\", -1) } filter = New(c.includes, c.excludes) r1 = filter.Allows("test/filename.dat") pattern, r2 = filter.AllowsPattern("test/filename.dat") assert.Equal(t, r1, r2, "filepathfilter: expected Allows() and AllowsPattern() to return identical result") assert.Equal(t, c.expectedResult, r1, c) assert.Equal(t, c.expectedPattern, pattern, "filepathfilter: expected pattern match of: %q, got: %q", c.expectedPattern, pattern) } } } func TestFilterReportsIncludePatterns(t *testing.T) { filter := New([]string{"*.foo", "*.bar"}, nil) assert.Equal(t, []string{"*.foo", "*.bar"}, filter.Include()) } func TestFilterReportsExcludePatterns(t *testing.T) { filter := New(nil, []string{"*.baz", "*.quux"}) assert.Equal(t, []string{"*.baz", "*.quux"}, filter.Exclude()) }