fork of https://github.com/sourcegraph/zoekt
1package archive
2
3import (
4 "archive/tar"
5 "archive/zip"
6 "compress/gzip"
7 "context"
8 "errors"
9 "flag"
10 "fmt"
11 "io"
12 "log"
13 "os"
14 "path/filepath"
15 "strings"
16 "testing"
17 "time"
18
19 "github.com/sourcegraph/zoekt"
20 "github.com/sourcegraph/zoekt/index"
21 "github.com/sourcegraph/zoekt/internal/shards"
22 "github.com/sourcegraph/zoekt/query"
23 "github.com/stretchr/testify/require"
24)
25
26func TestMain(m *testing.M) {
27 flag.Parse()
28 if !testing.Verbose() {
29 log.SetOutput(io.Discard)
30 }
31 os.Exit(m.Run())
32}
33
34var modTime = time.Date(2024, 9, 26, 0, 0, 0, 0, time.UTC)
35
36func writeArchive(w io.Writer, format string, files map[string]string) (err error) {
37 if format == "zip" {
38 zw := zip.NewWriter(w)
39 for name, body := range files {
40 header := &zip.FileHeader{
41 Name: name,
42 Method: zip.Deflate,
43 Modified: modTime,
44 }
45 f, err := zw.CreateHeader(header)
46 if err != nil {
47 return err
48 }
49 if _, err := f.Write([]byte(body)); err != nil {
50 return err
51 }
52 }
53 return zw.Close()
54 }
55
56 if format == "tgz" {
57 gw := gzip.NewWriter(w)
58 defer func() {
59 err2 := gw.Close()
60 if err == nil {
61 err = err2
62 }
63 }()
64 w = gw
65 format = "tar"
66 }
67
68 if format != "tar" {
69 return errors.New("expected tar")
70 }
71
72 tw := tar.NewWriter(w)
73
74 for name, body := range files {
75 hdr := &tar.Header{
76 Name: name,
77 Mode: 0o600,
78 Size: int64(len(body)),
79 ModTime: modTime,
80 }
81 if err := tw.WriteHeader(hdr); err != nil {
82 return err
83 }
84 if _, err := tw.Write([]byte(body)); err != nil {
85 return err
86 }
87 }
88 if err := tw.Close(); err != nil {
89 return err
90 }
91
92 return nil
93}
94
95// TestIndexArg tests zoekt-archive-index by creating an archive and then
96// indexing and executing searches and checking we get expected results.
97// Additionally, we test that the index is properly updated with the
98// -incremental=true option changing the options between indexes and ensuring
99// the results change as expected.
100func TestIndexIncrementally(t *testing.T) {
101 for _, format := range []string{"tar", "tgz", "zip"} {
102 t.Run(format, func(t *testing.T) {
103 testIndexIncrementally(t, format)
104 })
105 }
106}
107
108func testIndexIncrementally(t *testing.T, format string) {
109 indexDir := t.TempDir()
110
111 archive, err := os.CreateTemp("", "TestIndexArg-archive")
112 if err != nil {
113 t.Fatalf("TempFile: %v", err)
114 }
115 defer os.Remove(archive.Name())
116
117 fileSize := 1000
118
119 files := map[string]string{}
120 for i := 0; i < 4; i++ {
121 s := fmt.Sprintf("%d", i)
122 files["F"+s] = strings.Repeat("a", fileSize)
123 files["!F"+s] = strings.Repeat("a", fileSize)
124 }
125
126 err = writeArchive(archive, format, files)
127 if err != nil {
128 t.Fatalf("unable to create archive %v", err)
129 }
130 archive.Close()
131
132 // tests contain options used to build an index and the expected number of
133 // files in the result set based on the options.
134 tests := []struct {
135 largeFiles []string
136 wantNumFiles int
137 }{
138 {
139 largeFiles: []string{},
140 wantNumFiles: 0,
141 },
142 {
143 largeFiles: []string{"F0", "F2"},
144 wantNumFiles: 2,
145 },
146 {
147 largeFiles: []string{"F?", "!F2"},
148 wantNumFiles: 3,
149 },
150 {
151 largeFiles: []string{"F?", "!F2", "\\!F0"},
152 wantNumFiles: 4,
153 },
154 {
155 largeFiles: []string{"F?", "!F2", "\\!F0", "F2"},
156 wantNumFiles: 5,
157 },
158 }
159
160 for _, test := range tests {
161 largeFiles, wantNumFiles := test.largeFiles, test.wantNumFiles
162
163 bopts := index.Options{
164 SizeMax: fileSize - 1,
165 IndexDir: indexDir,
166 LargeFiles: largeFiles,
167 }
168 opts := Options{
169 Incremental: true,
170 Archive: archive.Name(),
171 Name: "repo",
172 Branch: "master",
173 Commit: "cccccccccccccccccccccccccccccccccccccccc",
174 Strip: 0,
175 }
176
177 if err := Index(opts, bopts); err != nil {
178 t.Fatalf("error creating index: %v", err)
179 }
180
181 ss, err := shards.NewDirectorySearcher(indexDir)
182 if err != nil {
183 t.Fatalf("NewDirectorySearcher(%s): %v", indexDir, err)
184 }
185 defer ss.Close()
186
187 q, err := query.Parse("aaa")
188 if err != nil {
189 t.Fatalf("Parse(aaa): %v", err)
190 }
191
192 var sOpts zoekt.SearchOptions
193 result, err := ss.Search(context.Background(), q, &sOpts)
194 if err != nil {
195 t.Fatalf("Search(%v): %v", q, err)
196 }
197
198 if len(result.Files) != wantNumFiles {
199 t.Errorf("got %v, want %d files.", result.Files, wantNumFiles)
200 }
201 }
202}
203
204// TestLatestCommitDate tests that the latest commit date is set correctly if
205// the mod time of the files has been set during the archive creation.
206func TestLatestCommitDate(t *testing.T) {
207 for _, format := range []string{"tar", "tgz", "zip"} {
208 t.Run(format, func(t *testing.T) {
209 testLatestCommitDate(t, format)
210 })
211 }
212}
213
214func testLatestCommitDate(t *testing.T, format string) {
215 // Create an archive
216 archive, err := os.CreateTemp("", "TestLatestCommitDate")
217 require.NoError(t, err)
218 defer os.Remove(archive.Name())
219
220 fileSize := 10
221 files := map[string]string{}
222 for i := 0; i < 4; i++ {
223 s := fmt.Sprintf("%d", i)
224 files["F"+s] = strings.Repeat("a", fileSize)
225 files["!F"+s] = strings.Repeat("a", fileSize)
226 }
227
228 err = writeArchive(archive, format, files)
229 if err != nil {
230 t.Fatalf("unable to create archive %v", err)
231 }
232 archive.Close()
233
234 // Index
235 indexDir := t.TempDir()
236 bopts := index.Options{
237 IndexDir: indexDir,
238 }
239 opts := Options{
240 Archive: archive.Name(),
241 Name: "repo",
242 Branch: "master",
243 Commit: "cccccccccccccccccccccccccccccccccccccccc",
244 }
245
246 err = Index(opts, bopts)
247 require.NoError(t, err)
248
249 // Read the metadata of the index we just created and check the latest commit date.
250 f, err := os.Open(indexDir)
251 require.NoError(t, err)
252
253 indexFiles, err := f.Readdirnames(1)
254 require.Len(t, indexFiles, 1)
255
256 repos, _, err := index.ReadMetadataPath(filepath.Join(indexDir, indexFiles[0]))
257 require.NoError(t, err)
258 require.Len(t, repos, 1)
259 require.True(t, repos[0].LatestCommitDate.Equal(modTime))
260}