fork of https://github.com/sourcegraph/zoekt
1package gitindex
2
3import (
4 "io"
5 "os"
6 "os/exec"
7 "path/filepath"
8 "testing"
9
10 "github.com/go-git/go-git/v5/plumbing"
11)
12
13// createTestRepo creates a git repo with various test files and returns
14// the repo path and a map of filename -> blob SHA.
15func createTestRepo(t *testing.T) (string, map[string]plumbing.Hash) {
16 t.Helper()
17 dir := t.TempDir()
18 repoDir := filepath.Join(dir, "repo")
19
20 script := `
21set -e
22git init -b main repo
23cd repo
24git config user.email "test@test.com"
25git config user.name "Test"
26
27# Normal text file
28echo "hello world" > hello.txt
29
30# Empty file
31touch empty.txt
32
33# Binary file with newlines embedded
34printf '\x00\x01\x02\nhello\nworld\n\x03\x04' > binary.bin
35
36# Large-ish file (64KB of data)
37dd if=/dev/urandom bs=1024 count=64 of=large.bin 2>/dev/null
38
39git add -A
40git commit -m "initial"
41`
42 cmd := exec.Command("/bin/sh", "-c", script)
43 cmd.Dir = dir
44 cmd.Stderr = os.Stderr
45 if err := cmd.Run(); err != nil {
46 t.Fatalf("create test repo: %v", err)
47 }
48
49 // Get blob SHAs for each file.
50 blobs := map[string]plumbing.Hash{}
51 for _, name := range []string{"hello.txt", "empty.txt", "binary.bin", "large.bin"} {
52 out, err := exec.Command("git", "-C", repoDir, "rev-parse", "HEAD:"+name).Output()
53 if err != nil {
54 t.Fatalf("rev-parse %s: %v", name, err)
55 }
56 sha := string(out[:len(out)-1]) // trim newline
57 blobs[name] = plumbing.NewHash(sha)
58 }
59
60 return repoDir, blobs
61}
62
63func TestCatfileReader(t *testing.T) {
64 repoDir, blobs := createTestRepo(t)
65
66 ids := []plumbing.Hash{
67 blobs["hello.txt"],
68 blobs["empty.txt"],
69 blobs["binary.bin"],
70 blobs["large.bin"],
71 }
72
73 cr, err := newCatfileReader(repoDir, ids, catfileReaderOptions{})
74 if err != nil {
75 t.Fatalf("newCatfileReader: %v", err)
76 }
77 defer cr.Close()
78
79 // hello.txt
80 size, missing, excluded, err := cr.Next()
81 if err != nil {
82 t.Fatalf("Next hello.txt: %v", err)
83 }
84 if missing || excluded {
85 t.Fatal("hello.txt unexpectedly missing")
86 }
87 if size != 12 {
88 t.Errorf("hello.txt size = %d, want 12", size)
89 }
90 content := make([]byte, size)
91 if _, err := io.ReadFull(cr, content); err != nil {
92 t.Fatalf("ReadFull hello.txt: %v", err)
93 }
94 if string(content) != "hello world\n" {
95 t.Errorf("hello.txt content = %q", content)
96 }
97
98 // empty.txt
99 size, missing, excluded, err = cr.Next()
100 if err != nil {
101 t.Fatalf("Next empty.txt: %v", err)
102 }
103 if missing || excluded {
104 t.Fatal("empty.txt unexpectedly missing")
105 }
106 if size != 0 {
107 t.Errorf("empty.txt size = %d, want 0", size)
108 }
109
110 // binary.bin — read content and verify binary data survives.
111 size, missing, excluded, err = cr.Next()
112 if err != nil {
113 t.Fatalf("Next binary.bin: %v", err)
114 }
115 if missing || excluded {
116 t.Fatal("binary.bin unexpectedly missing")
117 }
118 binContent := make([]byte, size)
119 if _, err := io.ReadFull(cr, binContent); err != nil {
120 t.Fatalf("ReadFull binary.bin: %v", err)
121 }
122 if binContent[0] != 0x00 || binContent[3] != '\n' {
123 t.Errorf("binary.bin unexpected leading bytes: %x", binContent[:5])
124 }
125
126 // large.bin
127 size, missing, excluded, err = cr.Next()
128 if err != nil {
129 t.Fatalf("Next large.bin: %v", err)
130 }
131 if missing || excluded {
132 t.Fatal("large.bin unexpectedly missing")
133 }
134 if size != 64*1024 {
135 t.Errorf("large.bin size = %d, want %d", size, 64*1024)
136 }
137 largeContent := make([]byte, size)
138 if _, err := io.ReadFull(cr, largeContent); err != nil {
139 t.Fatalf("ReadFull large.bin: %v", err)
140 }
141
142 // EOF after all entries.
143 _, _, _, err = cr.Next()
144 if err != io.EOF {
145 t.Errorf("expected io.EOF after last entry, got %v", err)
146 }
147}
148
149func TestCatfileReader_Skip(t *testing.T) {
150 repoDir, blobs := createTestRepo(t)
151
152 ids := []plumbing.Hash{
153 blobs["hello.txt"],
154 blobs["large.bin"],
155 blobs["binary.bin"],
156 }
157
158 cr, err := newCatfileReader(repoDir, ids, catfileReaderOptions{})
159 if err != nil {
160 t.Fatalf("newCatfileReader: %v", err)
161 }
162 defer cr.Close()
163
164 // Skip hello.txt by calling Next again without reading.
165 _, _, _, err = cr.Next()
166 if err != nil {
167 t.Fatalf("Next hello.txt: %v", err)
168 }
169
170 // Skip large.bin too.
171 size, _, _, err := cr.Next()
172 if err != nil {
173 t.Fatalf("Next large.bin: %v", err)
174 }
175 if size != 64*1024 {
176 t.Errorf("large.bin size = %d, want %d", size, 64*1024)
177 }
178
179 // Read binary.bin after skipping two entries.
180 size, _, _, err = cr.Next()
181 if err != nil {
182 t.Fatalf("Next binary.bin: %v", err)
183 }
184 content := make([]byte, size)
185 if _, err := io.ReadFull(cr, content); err != nil {
186 t.Fatalf("ReadFull binary.bin: %v", err)
187 }
188 if content[0] != 0x00 {
189 t.Errorf("binary.bin first byte = %x, want 0x00", content[0])
190 }
191}
192
193func TestCatfileReader_Missing(t *testing.T) {
194 repoDir, blobs := createTestRepo(t)
195
196 fakeHash := plumbing.NewHash("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
197 ids := []plumbing.Hash{
198 blobs["hello.txt"],
199 fakeHash,
200 blobs["empty.txt"],
201 }
202
203 cr, err := newCatfileReader(repoDir, ids, catfileReaderOptions{})
204 if err != nil {
205 t.Fatalf("newCatfileReader: %v", err)
206 }
207 defer cr.Close()
208
209 // hello.txt — read normally.
210 size, missing, excluded, err := cr.Next()
211 if err != nil || missing || excluded {
212 t.Fatalf("Next hello.txt: err=%v missing=%v excluded=%v", err, missing, excluded)
213 }
214 content := make([]byte, size)
215 if _, err := io.ReadFull(cr, content); err != nil {
216 t.Fatalf("ReadFull hello.txt: %v", err)
217 }
218 if string(content) != "hello world\n" {
219 t.Errorf("hello.txt = %q", content)
220 }
221
222 // fakeHash — missing.
223 _, missing, excluded, err = cr.Next()
224 if err != nil {
225 t.Fatalf("Next fakeHash: %v", err)
226 }
227 if excluded {
228 t.Error("expected fakeHash to be missing, not excluded")
229 }
230 if !missing {
231 t.Error("expected fakeHash to be missing")
232 }
233
234 // empty.txt — still works after missing entry.
235 size, missing, excluded, err = cr.Next()
236 if err != nil || missing || excluded {
237 t.Fatalf("Next empty.txt: err=%v missing=%v excluded=%v", err, missing, excluded)
238 }
239 if size != 0 {
240 t.Errorf("empty.txt size = %d, want 0", size)
241 }
242}
243
244func TestCatfileReader_Excluded(t *testing.T) {
245 repoDir, blobs := createTestRepo(t)
246
247 ids := []plumbing.Hash{
248 blobs["large.bin"],
249 blobs["hello.txt"],
250 }
251
252 cr, err := newCatfileReader(repoDir, ids, catfileReaderOptions{filterSpec: "blob:limit=1k"})
253 if err != nil {
254 t.Fatalf("newCatfileReader: %v", err)
255 }
256 defer cr.Close()
257
258 _, missing, excluded, err := cr.Next()
259 if err != nil {
260 t.Fatalf("Next large.bin: %v", err)
261 }
262 if missing {
263 t.Fatal("large.bin unexpectedly missing")
264 }
265 if !excluded {
266 t.Fatal("large.bin unexpectedly included")
267 }
268
269 size, missing, excluded, err := cr.Next()
270 if err != nil {
271 t.Fatalf("Next hello.txt: %v", err)
272 }
273 if missing || excluded {
274 t.Fatalf("hello.txt unexpectedly skipped: missing=%v excluded=%v", missing, excluded)
275 }
276 content := make([]byte, size)
277 if _, err := io.ReadFull(cr, content); err != nil {
278 t.Fatalf("ReadFull hello.txt: %v", err)
279 }
280 if string(content) != "hello world\n" {
281 t.Errorf("hello.txt = %q", content)
282 }
283}