package glob import ( "archive/zip" "bytes" "io" "io/fs" "reflect" "sort" "testing" "testing/fstest" ) func setupFS() fs.ReadDirFS { // Create an in-memory FS with a mix of files and directories return fstest.MapFS{ "main.go": &fstest.MapFile{Data: []byte("package main")}, "main_test.go": &fstest.MapFile{Data: []byte("package main_test")}, "README.md": &fstest.MapFile{Data: []byte("# readme")}, "LICENSE": &fstest.MapFile{Data: []byte("MIT")}, "docs/guide.md": &fstest.MapFile{Data: []byte("Docs")}, "docs/other.txt": &fstest.MapFile{Data: []byte("Other")}, "docs/hidden/.keep": &fstest.MapFile{Data: []byte("")}, "assets/img.png": &fstest.MapFile{Data: []byte("PNG")}, "assets/style.css": &fstest.MapFile{Data: []byte("CSS")}, ".gitignore": &fstest.MapFile{Data: []byte("*.log")}, ".hiddenfile": &fstest.MapFile{Data: []byte("")}, "emptydir": &fstest.MapFile{Mode: fs.ModeDir | 0o755}, } } // helper to get base names for easier comparison func basenames(entries []fs.DirEntry) []string { names := []string{} for _, e := range entries { names = append(names, e.Name()) } sort.Strings(names) return names } func TestGlobFS_MultiplePatterns(t *testing.T) { memfs := setupFS() gfs, err := NewGlobFS(memfs, "*.go", "*.md", "assets/*", "docs/guide.md", ".gitignore") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } tests := []struct { path string want []string wantErr bool }{ {path: ".", want: []string{"README.md", "assets", "docs", "main.go", "main_test.go", ".gitignore"}}, {path: "assets", want: []string{"img.png", "style.css"}}, {path: "docs", want: []string{"guide.md"}}, {path: "docs/hidden", want: []string{}, wantErr: true}, {path: "emptydir", want: []string{}, wantErr: true}, } for _, tc := range tests { tc := tc // capture range variable t.Run(escape(tc.path), func(t *testing.T) { entries, err := fs.ReadDir(gfs, tc.path) if tc.wantErr && err == nil { t.Errorf("expected error, got nil") return } if !tc.wantErr && err != nil { t.Errorf("unexpected error: %v", err) return } got := basenames(entries) sort.Strings(tc.want) if !reflect.DeepEqual(got, tc.want) { t.Errorf("got %v; want %v", got, tc.want) } }) } } func TestGlobFS_Open(t *testing.T) { memfs := setupFS() gfs, err := NewGlobFS(memfs, "*.go", "*.md", "assets/*", "docs/guide.md", ".gitignore") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } type test struct { path string wantErr bool } tests := []test{ {path: "main.go"}, {path: "README.md"}, {path: "LICENSE", wantErr: true}, {path: "assets/img.png"}, {path: "assets/style.css"}, {path: "assets/nonexistent.png", wantErr: true}, {path: "docs/guide.md"}, {path: "docs/other.txt", wantErr: true}, {path: ".gitignore"}, {path: ".hiddenfile", wantErr: true}, {path: "docs/hidden/.keep", wantErr: true}, {path: "emptydir", wantErr: true}, {path: "docs"}, // allowed because it contains matching file(s) {path: "assets"}, // allowed because it contains matching file(s) } for _, tc := range tests { tc := tc t.Run(escape(tc.path), func(t *testing.T) { f, err := gfs.Open(tc.path) if tc.wantErr && err == nil { t.Errorf("expected error, got file") if f != nil { f.Close() } } else if !tc.wantErr && err != nil { t.Errorf("unexpected error: %v", err) } else if !tc.wantErr && err == nil { info, _ := f.Stat() if info.IsDir() { _, derr := fs.ReadDir(gfs, tc.path) if derr != nil && !tc.wantErr { t.Errorf("unexpected error: %v", derr) } } f.Close() } }) } } func TestGlobFS_ReadFile(t *testing.T) { memfs := setupFS() gfs, err := NewGlobFS(memfs, "*.go", "*.md", "assets/*", ".gitignore") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } tests := []struct { name string want []byte wantErr bool }{ {name: "main.go", want: []byte("package main")}, {name: "main_test.go", want: []byte("package main_test")}, {name: "README.md", want: []byte("# readme")}, {name: "assets/img.png", want: []byte("PNG")}, {name: "assets/style.css", want: []byte("CSS")}, {name: ".gitignore", want: []byte("*.log")}, {name: "LICENSE", wantErr: true}, // not allowed by filter {name: "docs/guide.md", wantErr: true}, // not allowed by filter {name: "docs/hidden/.keep", wantErr: true}, // not allowed by filter {name: "doesnotexist.txt", wantErr: true}, // does not exist } for _, tc := range tests { tc := tc t.Run(escape(tc.name), func(t *testing.T) { got, err := fs.ReadFile(gfs, tc.name) if tc.wantErr { if err == nil { t.Errorf("expected error, got nil (got=%q)", got) } } else { if err != nil { t.Errorf("unexpected error: %v", err) } if string(got) != string(tc.want) { t.Errorf("got %q; want %q", got, tc.want) } } }) } } func TestGlobFS_RelativePaths(t *testing.T) { memfs := setupFS() gfs, err := NewGlobFS(memfs, "docs/*.md") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } entries, err := fs.ReadDir(gfs, "docs") if err != nil { t.Fatal(err) } got := basenames(entries) want := []string{"guide.md"} if !reflect.DeepEqual(got, want) { t.Errorf("docs/*.md: got %v, want %v", got, want) } } func TestGlobFS_NoMatchesOpen(t *testing.T) { gfs, err := NewGlobFS(setupFS(), "*.xyz") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } _, err = gfs.Open("main.go") if err == nil { t.Fatal("expected error when opening file with no matches") } } func TestGlobFS_NoMatchesStat(t *testing.T) { gfs, err := NewGlobFS(setupFS(), "*.xyz") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } _, err = fs.Stat(gfs, "main.go") if err == nil { t.Fatal("expected error with no matches: stat") } } func TestGlobFS_NoMatchesReadDir(t *testing.T) { gfs, err := NewGlobFS(setupFS(), "*.xyz") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } _, err = fs.ReadDir(gfs, "main.go") if err == nil { t.Fatal("expected error with no matches: readdir") } } func TestGlobFS_NoMatchesReadFile(t *testing.T) { gfs, err := NewGlobFS(setupFS(), "*.xyz") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } _, err = fs.ReadFile(gfs, "main.go") if err == nil { t.Fatal("expected error with no matches: readfile") } } func TestGlobFS_MatchEmptyDirExact(t *testing.T) { // the trailing slash indicates that the directory should be included gfs, err := NewGlobFS(setupFS(), "emptydir/") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } _, err = fs.ReadDir(gfs, "emptydir") if err != nil { t.Errorf("unexpected error: %v", err) } } func TestGlobFS_MatchEmptyDirExact2(t *testing.T) { // the trailing slash indicates that the directory should be included gfs, err := NewGlobFS(setupFS(), "emptydir/*") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } _, err = fs.ReadDir(gfs, "emptydir") if err != nil { t.Errorf("unexpected error: %v", err) } } func TestGlobFS_NoMatchEmptyDirExact(t *testing.T) { // no traling slash indicates that the directory must be a file to be included gfs, err := NewGlobFS(setupFS(), "emptydir") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } _, err = fs.ReadDir(gfs, "emptydir") if err == nil { t.Fatal("expected error with no matches: readfile") } } func TestGlobFS_IntegrationWithStdlibWalkDir(t *testing.T) { memfs := setupFS() gfs, err := NewGlobFS(memfs, "*.go", "docs/guide.md") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } // Use fs.WalkDir with our filtered FS var walked []string err = fs.WalkDir(gfs, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { t.Fatalf("the %q caused: %v", path, err) return err } walked = append(walked, path) return nil }) if err != nil { t.Fatal(err) } // Only files and dirs matching or containing matches should appear for _, p := range walked { if p == "." || p == "main.go" || p == "main_test.go" || p == "docs" || p == "docs/guide.md" { continue } t.Errorf("WalkDir: unexpected path %q", p) } } func TestGlobFS_InvalidPattern(t *testing.T) { _, err := NewGlobFS(setupFS(), "[invalid") if err == nil { t.Fatal("expected error for invalid pattern, got nil") } } func TestGlobFS_WildcardInDirSegment(t *testing.T) { gfs, err := NewGlobFS(setupFS(), "docs/*/*.md") if err != nil { t.Fatalf("unexpected error: %v", err) } entries, err := fs.ReadDir(gfs, "docs/hidden") if err != nil { t.Errorf("unexpected error: %v", err) } if len(entries) != 0 { t.Errorf("expected no entries, got %v", basenames(entries)) } } func TestGlobFS_DeeplyNestedMatch(t *testing.T) { memfs := fstest.MapFS{ "a/b/c/d.txt": &fstest.MapFile{Data: []byte("deep")}, } gfs, err := NewGlobFS(memfs, "a/b/c/*.txt") if err != nil { t.Fatalf("unexpected error: %v", err) } data, err := fs.ReadFile(gfs, "a/b/c/d.txt") if err != nil { t.Errorf("unexpected error: %v", err) } if string(data) != "deep" { t.Errorf("got %q, want %q", data, "deep") } } func TestGlobFS_HiddenFilesOnly(t *testing.T) { gfs, err := NewGlobFS(setupFS(), ".*") if err != nil { t.Fatalf("unexpected error: %v", err) } entries, err := fs.ReadDir(gfs, ".") if err != nil { t.Fatalf("unexpected error: %v", err) } got := basenames(entries) want := []string{".gitignore", ".hiddenfile"} if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) } } // Test directory pattern matching with various directory globs func TestGlobFS_DirectoryPatterns(t *testing.T) { memfs := fstest.MapFS{ "foo/bar/baz.txt": &fstest.MapFile{Data: []byte("baz")}, "foo/bar/qux.txt": &fstest.MapFile{Data: []byte("qux")}, "foo/readme.md": &fstest.MapFile{Data: []byte("readme")}, "foo/empty/.keep": &fstest.MapFile{Data: []byte("")}, // represent empty dir by a file inside "top.txt": &fstest.MapFile{Data: []byte("top")}, } t.Run("single dir segment wildcard", func(t *testing.T) { gfs, err := NewGlobFS(memfs, "foo/bar/*") if err != nil { t.Fatalf("unexpected error: %v", err) } entries, err := fs.ReadDir(gfs, "foo/bar") if err != nil { t.Fatalf("unexpected error: %v", err) } got := basenames(entries) want := []string{"baz.txt", "qux.txt"} if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) } }) t.Run("recursive dir wildcard", func(t *testing.T) { gfs, err := NewGlobFS(memfs, "foo/bar/*") if err != nil { t.Fatalf("unexpected error: %v", err) } entries, err := fs.ReadDir(gfs, "foo/bar") if err != nil { t.Fatalf("unexpected error: %v", err) } got := basenames(entries) want := []string{"baz.txt", "qux.txt"} if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) } entries, err = fs.ReadDir(gfs, "foo") if err != nil { t.Fatalf("unexpected error: %v", err) } got = basenames(entries) want = []string{"bar"} if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) } }) t.Run("match empty directory", func(t *testing.T) { gfs, err := NewGlobFS(memfs, "foo/empty/") if err != nil { t.Fatalf("unexpected error: %v", err) } entries, err := fs.ReadDir(gfs, "foo/empty") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(entries) != 0 { t.Errorf("expected empty, got %v", basenames(entries)) } }) t.Run("top-level dir wildcard", func(t *testing.T) { gfs, err := NewGlobFS(memfs, "*/bar/*") if err != nil { t.Fatalf("unexpected error: %v", err) } entries, err := fs.ReadDir(gfs, "foo/bar") if err != nil { t.Fatalf("unexpected error: %v", err) } got := basenames(entries) want := []string{"baz.txt", "qux.txt"} if !reflect.DeepEqual(got, want) { t.Errorf("got %v, want %v", got, want) } }) } func TestGlobFS_IntegrationWithStdlibZipWriter(t *testing.T) { gfs, err := NewGlobFS(setupFS(), "*") if err != nil { t.Errorf("unexpected error while creating glob fs: %v", err) } want := map[string]string{ "main.go": "package main", "main_test.go": "package main_test", "README.md": "# readme", "LICENSE": "MIT", ".gitignore": "*.log", ".hiddenfile": "", } buf := new(bytes.Buffer) wr := zip.NewWriter(buf) err = wr.AddFS(gfs) if err != nil { t.Fatalf("adding fs to zip writer: %v", err) } err = wr.Close() if err != nil { t.Fatalf("close zip writer: %v", err) } rd, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(len(buf.Bytes()))) if err != nil { t.Fatalf("invalid zip archive: %v", err) } got := make(map[string]string) for _, f := range rd.File { rc, err := f.Open() if err != nil { t.Fatalf("cannot open file %s: %v", f.Name, err) } content, err := io.ReadAll(rc) defer rc.Close() if err != nil { t.Fatalf("cannot read file %s: %v", f.Name, err) } got[f.Name] = string(content) } // Compare expected vs actual. for name, exp := range want { act, ok := got[name] if !ok { t.Errorf("expected file %q not found in zip", name) continue } if act != exp { t.Errorf("content mismatch for %q:\nexpected: %q\nactual: %q", name, exp, act) } } // Check for unexpected extra files. for name := range got { if _, ok := want[name]; !ok { t.Errorf("unexpected file %q found in zip", name) } } }