package iox import ( "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}, } } // 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 TestGlobFSMultiplePatterns(t *testing.T) { memfs := setupFS() gfs := NewGlobFS(memfs, "*.go", "*.md", "assets/*", "docs/guide.md", ".gitignore") 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(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 TestGlobFSOpen(t *testing.T) { memfs := setupFS() gfs := NewGlobFS(memfs, "*.go", "*.md", "assets/*", "docs/guide.md", ".gitignore") 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(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 TestGlobFSReadFile(t *testing.T) { memfs := setupFS() gfs := NewGlobFS(memfs, "*.go", "*.md", "assets/*", ".gitignore") 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(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 TestGlobFSRelativePaths(t *testing.T) { memfs := setupFS() gfs := NewGlobFS(memfs, "docs/*.md") 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 TestGlobFSNoMatchesOpen(t *testing.T) { gfs := NewGlobFS(setupFS(), "*.xyz") _, err := gfs.Open("main.go") if err == nil { t.Fatal("expected error when opening file with no matches") } } func TestGlobFSNoMatchesStat(t *testing.T) { gfs := NewGlobFS(setupFS(), "*.xyz") _, err := fs.Stat(gfs, "main.go") if err == nil { t.Fatal("expected error with no matches: stat") } } func TestGlobFSNoMatchesReadDir(t *testing.T) { gfs := NewGlobFS(setupFS(), "*.xyz") _, err := fs.ReadDir(gfs, "main.go") if err == nil { t.Fatal("expected error with no matches: readdir") } } func TestGlobFSNoMatchesReadFile(t *testing.T) { gfs := NewGlobFS(setupFS(), "*.xyz") _, err := fs.ReadFile(gfs, "main.go") if err == nil { t.Fatal("expected error with no matches: readfile") } } func TestGlobFS_IntegrationWithStdlib(t *testing.T) { memfs := setupFS() gfs := NewGlobFS(memfs, "*.go", "docs/guide.md") // 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 { 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) } }