Files
2026-07-13 13:00:08 +08:00

114 lines
3.4 KiB
Go

package builtin
import (
"context"
"path/filepath"
"sort"
"strings"
"testing"
)
func TestGrepGlobalExcludesFile(t *testing.T) {
dir := mkRepo(t)
writeFileT(t, filepath.Join(dir, "keep.txt"), "NEEDLE\n")
writeFileT(t, filepath.Join(dir, "notes.tmp"), "NEEDLE\n")
cfgDir := t.TempDir()
excludes := filepath.Join(cfgDir, "global_ignore")
writeFileT(t, excludes, "*.tmp\n")
gitconfig := filepath.Join(cfgDir, "gitconfig")
writeFileT(t, gitconfig, "[core]\n\texcludesFile = "+filepath.ToSlash(excludes)+"\n")
t.Setenv("GIT_CONFIG_GLOBAL", gitconfig)
out := runTool(t, grepTool{}, map[string]any{"pattern": "NEEDLE", "path": dir})
if !strings.Contains(out, "keep.txt") {
t.Fatalf("kept file must be found: %q", out)
}
if strings.Contains(out, "notes.tmp") {
t.Fatalf("a file matched by core.excludesFile must be skipped: %q", out)
}
}
// TestGrepWorkspaceE2E drives grep through the real per-workspace tool assembly
// (builtin.Workspace, what the desktop and ACP frontends use) against a repo that
// exercises every ignore rule at once: root + nested + /anchored .gitignore,
// nested negation, hidden dirs, and vendor dirs.
func TestGrepWorkspaceE2E(t *testing.T) {
// Neutralize any machine-global git ignore so the fixture is hermetic.
t.Setenv("GIT_CONFIG_GLOBAL", filepath.Join(t.TempDir(), "none"))
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
repo := mkRepo(t)
writeFileT(t, filepath.Join(repo, ".gitignore"), "*.log\nbuild/\n/dist/\n")
writeFileT(t, filepath.Join(repo, "src", ".gitignore"), "generated.go\n")
writeFileT(t, filepath.Join(repo, "pkg", ".gitignore"), "*.log\n!keep.log\n")
keep := map[string]string{
"src/app.go": "NEEDLE app",
"pkg/keep.log": "NEEDLE re-included",
"README.md": "NEEDLE readme",
}
drop := map[string]string{
"src/generated.go": "NEEDLE nested-ignored",
"app.log": "NEEDLE root-log",
"build/out.txt": "NEEDLE ignored-dir",
"dist/bundle.js": "NEEDLE anchored-dir",
"pkg/drop.log": "NEEDLE nested-log",
".github/ci.yml": "NEEDLE hidden",
"node_modules/x/index.js": "NEEDLE vendor",
}
for rel, body := range keep {
writeFileT(t, filepath.Join(repo, filepath.FromSlash(rel)), body+"\n")
}
for rel, body := range drop {
writeFileT(t, filepath.Join(repo, filepath.FromSlash(rel)), body+"\n")
}
grep := byName(Workspace{Dir: repo}.Tools())["grep"]
out, err := grep.Execute(context.Background(), argsJSON(t, map[string]any{"pattern": "NEEDLE", "path": "."}))
if err != nil {
t.Fatal(err)
}
found := matchedRelFiles(repo, out)
for rel := range keep {
if !found[rel] {
t.Errorf("expected %s in results, got %v", rel, sortedKeys(found))
}
}
for rel := range drop {
if found[rel] {
t.Errorf("did not expect %s in results (got %v)", rel, sortedKeys(found))
}
}
}
// matchedRelFiles parses grep's "path:line:text" output into the set of matched
// files, relative to repo with forward slashes.
func matchedRelFiles(repo, out string) map[string]bool {
found := map[string]bool{}
for _, line := range strings.Split(out, "\n") {
i := strings.LastIndex(line, ":")
if i < 0 {
continue
}
j := strings.LastIndex(line[:i], ":")
if j < 0 {
continue
}
if rel, err := filepath.Rel(repo, line[:j]); err == nil {
found[filepath.ToSlash(rel)] = true
}
}
return found
}
func sortedKeys(m map[string]bool) []string {
var ks []string
for k := range m {
ks = append(ks, k)
}
sort.Strings(ks)
return ks
}