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)
74 if err != nil {
75 t.Fatalf("newCatfileReader: %v", err)
76 }
77 defer cr.Close()
78
79 // hello.txt
80 size, missing, err := cr.Next()
81 if err != nil {
82 t.Fatalf("Next hello.txt: %v", err)
83 }
84 if missing {
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, err = cr.Next()
100 if err != nil {
101 t.Fatalf("Next empty.txt: %v", err)
102 }
103 if size != 0 {
104 t.Errorf("empty.txt size = %d, want 0", size)
105 }
106
107 // binary.bin — read content and verify binary data survives.
108 size, missing, err = cr.Next()
109 if err != nil {
110 t.Fatalf("Next binary.bin: %v", err)
111 }
112 binContent := make([]byte, size)
113 if _, err := io.ReadFull(cr, binContent); err != nil {
114 t.Fatalf("ReadFull binary.bin: %v", err)
115 }
116 if binContent[0] != 0x00 || binContent[3] != '\n' {
117 t.Errorf("binary.bin unexpected leading bytes: %x", binContent[:5])
118 }
119
120 // large.bin
121 size, missing, err = cr.Next()
122 if err != nil {
123 t.Fatalf("Next large.bin: %v", err)
124 }
125 if size != 64*1024 {
126 t.Errorf("large.bin size = %d, want %d", size, 64*1024)
127 }
128 largeContent := make([]byte, size)
129 if _, err := io.ReadFull(cr, largeContent); err != nil {
130 t.Fatalf("ReadFull large.bin: %v", err)
131 }
132
133 // EOF after all entries.
134 _, _, err = cr.Next()
135 if err != io.EOF {
136 t.Errorf("expected io.EOF after last entry, got %v", err)
137 }
138}
139
140func TestCatfileReader_Skip(t *testing.T) {
141 repoDir, blobs := createTestRepo(t)
142
143 ids := []plumbing.Hash{
144 blobs["hello.txt"],
145 blobs["large.bin"],
146 blobs["binary.bin"],
147 }
148
149 cr, err := newCatfileReader(repoDir, ids)
150 if err != nil {
151 t.Fatalf("newCatfileReader: %v", err)
152 }
153 defer cr.Close()
154
155 // Skip hello.txt by calling Next again without reading.
156 _, _, err = cr.Next()
157 if err != nil {
158 t.Fatalf("Next hello.txt: %v", err)
159 }
160
161 // Skip large.bin too.
162 size, _, err := cr.Next()
163 if err != nil {
164 t.Fatalf("Next large.bin: %v", err)
165 }
166 if size != 64*1024 {
167 t.Errorf("large.bin size = %d, want %d", size, 64*1024)
168 }
169
170 // Read binary.bin after skipping two entries.
171 size, _, err = cr.Next()
172 if err != nil {
173 t.Fatalf("Next binary.bin: %v", err)
174 }
175 content := make([]byte, size)
176 if _, err := io.ReadFull(cr, content); err != nil {
177 t.Fatalf("ReadFull binary.bin: %v", err)
178 }
179 if content[0] != 0x00 {
180 t.Errorf("binary.bin first byte = %x, want 0x00", content[0])
181 }
182}
183
184func TestCatfileReader_Missing(t *testing.T) {
185 repoDir, blobs := createTestRepo(t)
186
187 fakeHash := plumbing.NewHash("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
188 ids := []plumbing.Hash{
189 blobs["hello.txt"],
190 fakeHash,
191 blobs["empty.txt"],
192 }
193
194 cr, err := newCatfileReader(repoDir, ids)
195 if err != nil {
196 t.Fatalf("newCatfileReader: %v", err)
197 }
198 defer cr.Close()
199
200 // hello.txt — read normally.
201 size, missing, err := cr.Next()
202 if err != nil || missing {
203 t.Fatalf("Next hello.txt: err=%v missing=%v", err, missing)
204 }
205 content := make([]byte, size)
206 if _, err := io.ReadFull(cr, content); err != nil {
207 t.Fatalf("ReadFull hello.txt: %v", err)
208 }
209 if string(content) != "hello world\n" {
210 t.Errorf("hello.txt = %q", content)
211 }
212
213 // fakeHash — missing.
214 _, missing, err = cr.Next()
215 if err != nil {
216 t.Fatalf("Next fakeHash: %v", err)
217 }
218 if !missing {
219 t.Error("expected fakeHash to be missing")
220 }
221
222 // empty.txt — still works after missing entry.
223 size, missing, err = cr.Next()
224 if err != nil || missing {
225 t.Fatalf("Next empty.txt: err=%v missing=%v", err, missing)
226 }
227 if size != 0 {
228 t.Errorf("empty.txt size = %d, want 0", size)
229 }
230}