返回 DeepSeek-Reasonix
gitignore_e2e_test.go
根目录 / internal / tool / builtin / gitignore_e2e_test.go
1 package builtin
2
3 import (
4 "context"
5 "path/filepath"
6 "sort"
7 "strings"
8 "testing"
9 )
10
11 func TestGrepGlobalExcludesFile(t *testing.T) {
12 dir := mkRepo(t)
13 writeFileT(t, filepath.Join(dir, "keep.txt"), "NEEDLE\n")
14 writeFileT(t, filepath.Join(dir, "notes.tmp"), "NEEDLE\n")
15
16 cfgDir := t.TempDir()
17 excludes := filepath.Join(cfgDir, "global_ignore")
18 writeFileT(t, excludes, "*.tmp\n")
19 gitconfig := filepath.Join(cfgDir, "gitconfig")
20 writeFileT(t, gitconfig, "[core]\n\texcludesFile = "+filepath.ToSlash(excludes)+"\n")
21 t.Setenv("GIT_CONFIG_GLOBAL", gitconfig)
22
23 out := runTool(t, grepTool{}, map[string]any{"pattern": "NEEDLE", "path": dir})
24 if !strings.Contains(out, "keep.txt") {
25 t.Fatalf("kept file must be found: %q", out)
26 }
27 if strings.Contains(out, "notes.tmp") {
28 t.Fatalf("a file matched by core.excludesFile must be skipped: %q", out)
29 }
30 }
31
32 // TestGrepWorkspaceE2E drives grep through the real per-workspace tool assembly
33 // (builtin.Workspace, what the desktop and ACP frontends use) against a repo that
34 // exercises every ignore rule at once: root + nested + /anchored .gitignore,
35 // nested negation, hidden dirs, and vendor dirs.
36 func TestGrepWorkspaceE2E(t *testing.T) {
37 // Neutralize any machine-global git ignore so the fixture is hermetic.
38 t.Setenv("GIT_CONFIG_GLOBAL", filepath.Join(t.TempDir(), "none"))
39 t.Setenv("XDG_CONFIG_HOME", t.TempDir())
40
41 repo := mkRepo(t)
42 writeFileT(t, filepath.Join(repo, ".gitignore"), "*.log\nbuild/\n/dist/\n")
43 writeFileT(t, filepath.Join(repo, "src", ".gitignore"), "generated.go\n")
44 writeFileT(t, filepath.Join(repo, "pkg", ".gitignore"), "*.log\n!keep.log\n")
45
46 keep := map[string]string{
47 "src/app.go": "NEEDLE app",
48 "pkg/keep.log": "NEEDLE re-included",
49 "README.md": "NEEDLE readme",
50 }
51 drop := map[string]string{
52 "src/generated.go": "NEEDLE nested-ignored",
53 "app.log": "NEEDLE root-log",
54 "build/out.txt": "NEEDLE ignored-dir",
55 "dist/bundle.js": "NEEDLE anchored-dir",
56 "pkg/drop.log": "NEEDLE nested-log",
57 ".github/ci.yml": "NEEDLE hidden",
58 "node_modules/x/index.js": "NEEDLE vendor",
59 }
60 for rel, body := range keep {
61 writeFileT(t, filepath.Join(repo, filepath.FromSlash(rel)), body+"\n")
62 }
63 for rel, body := range drop {
64 writeFileT(t, filepath.Join(repo, filepath.FromSlash(rel)), body+"\n")
65 }
66
67 grep := byName(Workspace{Dir: repo}.Tools())["grep"]
68 out, err := grep.Execute(context.Background(), argsJSON(t, map[string]any{"pattern": "NEEDLE", "path": "."}))
69 if err != nil {
70 t.Fatal(err)
71 }
72
73 found := matchedRelFiles(repo, out)
74 for rel := range keep {
75 if !found[rel] {
76 t.Errorf("expected %s in results, got %v", rel, sortedKeys(found))
77 }
78 }
79 for rel := range drop {
80 if found[rel] {
81 t.Errorf("did not expect %s in results (got %v)", rel, sortedKeys(found))
82 }
83 }
84 }
85
86 // matchedRelFiles parses grep's "path:line:text" output into the set of matched
87 // files, relative to repo with forward slashes.
88 func matchedRelFiles(repo, out string) map[string]bool {
89 found := map[string]bool{}
90 for _, line := range strings.Split(out, "\n") {
91 i := strings.LastIndex(line, ":")
92 if i < 0 {
93 continue
94 }
95 j := strings.LastIndex(line[:i], ":")
96 if j < 0 {
97 continue
98 }
99 if rel, err := filepath.Rel(repo, line[:j]); err == nil {
100 found[filepath.ToSlash(rel)] = true
101 }
102 }
103 return found
104 }
105
106 func sortedKeys(m map[string]bool) []string {
107 var ks []string
108 for k := range m {
109 ks = append(ks, k)
110 }
111 sort.Strings(ks)
112 return ks
113 }
114
114 lines GO