package glob import ( "errors" "fmt" "reflect" "strings" "testing" "testing/fstest" ) type test struct { pattern, f string match bool err error } func escape(name string) string { // use a math division slash for correct visual return strings.ReplaceAll(name, "/", "∕") } func TestPattern_Match(t *testing.T) { tests := []test{ // Test cases not covered by path.Match {"main.go", "main.go", true, nil}, {"main_test.go", "main_test.go", true, nil}, {"foo/foo_test.go", "foo/foo_test.go", true, nil}, {"?.go", "m.go", true, nil}, {"*.go", "main.go", true, nil}, {"**/*.go", "main.go", true, nil}, {"*.go", "*.go", true, nil}, {"//", "", false, ErrBadPattern}, {"foo//", "", false, ErrBadPattern}, {"*?.go", "", false, ErrBadPattern}, {"?*.go", "", false, ErrBadPattern}, {"**?.go", "", false, ErrBadPattern}, {"**f", "", false, ErrBadPattern}, {"[a-", "", false, ErrBadPattern}, {"[a-\\", "", false, ErrBadPattern}, {"[\\", "", false, ErrBadPattern}, {"**/m.go", "foo.go", false, nil}, {"**/m.go", "foo/a.go", false, nil}, {"**/m.go", "m.go", true, nil}, {"**/m.go", "foo/m.go", true, nil}, {"**/m.go", "bar/m.go", true, nil}, {"**/m.go", "foo/bar/m.go", true, nil}, {"ab[cde]", "abc", true, nil}, {"ab[cde]", "abd", true, nil}, {"ab[cde]", "abe", true, nil}, {"ab[+-\\-]", "ab-", true, nil}, {"ab[\\--a]", "ab-", true, nil}, {"[a-fA-F]", "a", true, nil}, {"[a-fA-F]", "f", true, nil}, {"[a-fA-F]", "A", true, nil}, {"[a-fA-F]", "F", true, nil}, // The following test cases are taken from // https://github.com/golang/go/blob/master/src/path/match_test.go and are // provided here to test compatebility of the match implementation with the // test cases from the golang standard lib. {"abc", "abc", true, nil}, {"*", "abc", true, nil}, {"*c", "abc", true, nil}, {"a*", "a", true, nil}, {"a*", "abc", true, nil}, {"a*", "ab/c", false, nil}, {"a*/b", "abc/b", true, nil}, {"a*/b", "a/c/b", false, nil}, {"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil}, {"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil}, {"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil}, {"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil}, {"a*b?c*x", "abxbbxdbxebxczzx", true, nil}, {"a*b?c*x", "abxbbxdbxebxczzy", false, nil}, {"ab[c]", "abc", true, nil}, {"ab[b-d]", "abc", true, nil}, {"ab[e-g]", "abc", false, nil}, {"ab[^c]", "abc", false, nil}, {"ab[^b-d]", "abc", false, nil}, {"ab[^e-g]", "abc", true, nil}, {"a\\*b", "a*b", true, nil}, {"a\\*b", "ab", false, nil}, {"a?b", "a☺b", true, nil}, {"a[^a]b", "a☺b", true, nil}, {"a???b", "a☺b", false, nil}, {"a[^a][^a][^a]b", "a☺b", false, nil}, {"[a-ζ]*", "α", true, nil}, {"*[a-ζ]", "A", false, nil}, {"a?b", "a/b", false, nil}, {"a*b", "a/b", false, nil}, {"[\\]a]", "]", true, nil}, {"[\\-]", "-", true, nil}, {"[x\\-]", "x", true, nil}, {"[x\\-]", "-", true, nil}, {"[x\\-]", "z", false, nil}, {"[\\-x]", "x", true, nil}, {"[\\-x]", "-", true, nil}, {"[\\-x]", "a", false, nil}, {"[]a]", "]", false, ErrBadPattern}, {"[-]", "-", false, ErrBadPattern}, {"[x-]", "x", false, ErrBadPattern}, {"[x-]", "-", false, ErrBadPattern}, {"[x-]", "z", false, ErrBadPattern}, {"[-x]", "x", false, ErrBadPattern}, {"[-x]", "-", false, ErrBadPattern}, {"[-x]", "a", false, ErrBadPattern}, {"\\", "a", false, ErrBadPattern}, {"[a-b-c]", "a", false, ErrBadPattern}, {"[", "a", false, ErrBadPattern}, {"[^", "a", false, ErrBadPattern}, {"[^bc", "a", false, ErrBadPattern}, {"a[", "a", false, ErrBadPattern}, {"a[", "ab", false, ErrBadPattern}, {"a[", "x", false, ErrBadPattern}, {"a/b[", "x", false, ErrBadPattern}, {"*x", "xxx", true, nil}, } for _, tt := range tests { pat, err := New(tt.pattern) if err != tt.err && !errors.Is(err, tt.err) { t.Errorf("New(%#q): wanted error %v but got %v", tt.pattern, tt.err, err) } if pat != nil { match := pat.Match(tt.f) if match != tt.match { t.Errorf("New(%#q).Match(%#q): wanted match %v but got %v", tt.pattern, tt.f, tt.match, match) } } } } func TestPattern_MatchPrefix(t *testing.T) { tests := []test{ {"**/*.go", "foo/", true, nil}, {"**/*.go", "foo", true, nil}, {"**/*.go", "foo/bar/", true, nil}, {"**/*.go", "foo/bar", true, nil}, {"*/*.go", "foo", true, nil}, } for _, tc := range tests { tc := tc // capture range variable t.Run(fmt.Sprintf("%s (%s)", escape(tc.pattern), escape(tc.f)), func(t *testing.T) { pat, err := New(tc.pattern) if err != nil { t.Errorf("unexpected error: %v", err) return } got := pat.MatchPrefix(tc.f) if got != tc.match { t.Errorf("got %v; want %v", got, tc.match) } }) } } func TestPattern_GlobFS(t *testing.T) { fsys := fstest.MapFS{ "go.mod": &fstest.MapFile{Mode: 0644}, "go.sum": &fstest.MapFile{Mode: 0644}, "cmd/main.go": &fstest.MapFile{Mode: 0644}, "cmd/main_test.go": &fstest.MapFile{Mode: 0644}, "internal/tool/tool.go": &fstest.MapFile{Mode: 0644}, "internal/tool/tool_test.go": &fstest.MapFile{Mode: 0644}, "internal/cli/cli.go": &fstest.MapFile{Mode: 0644}, "internal/cli/cli_test.go": &fstest.MapFile{Mode: 0644}, } pat, err := New("**/*_test.go") if err != nil { t.Fatal(err) } files, err := pat.GlobFS(fsys, ".") if err != nil { t.Fatal(err) } expect := []string{ "cmd/main_test.go", "internal/cli/cli_test.go", "internal/tool/tool_test.go", } if !reflect.DeepEqual(expect, files) { t.Errorf("got %v; want %v", files, expect) } } func TestPattern_String(t *testing.T) { tests := []string{ "main.go", "*.go", "**/*.go", "foo/bar/*", "foo/?ar.go", "foo/[abc].go", "foo/[a-c].go", "foo/**/", "foo/*/bar.go", "foo/\\*bar.go", } for _, patstr := range tests { t.Run(escape(patstr), func(t *testing.T) { pat, err := New(patstr) if err != nil { t.Fatalf("New(%q) failed: %v", patstr, err) } if pat.String() != patstr { t.Fatalf("Pattern.String() = %q, want %q", pat.String(), patstr) } }) } }