1
2
3
4
5 package codehost
6
7 import (
8 "archive/zip"
9 "bytes"
10 "flag"
11 "internal/testenv"
12 "io"
13 "io/fs"
14 "log"
15 "os"
16 "os/exec"
17 "path"
18 "path/filepath"
19 "reflect"
20 "strings"
21 "testing"
22 "time"
23 )
24
25 func TestMain(m *testing.M) {
26
27
28 flag.Parse()
29 os.Exit(testMain(m))
30 }
31
32 const (
33 gitrepo1 = "https://vcs-test.golang.org/git/gitrepo1"
34 hgrepo1 = "https://vcs-test.golang.org/hg/hgrepo1"
35 )
36
37 var altRepos = []string{
38 "localGitRepo",
39 hgrepo1,
40 }
41
42
43
44
45
46 var localGitRepo string
47
48 func testMain(m *testing.M) int {
49 dir, err := os.MkdirTemp("", "gitrepo-test-")
50 if err != nil {
51 log.Fatal(err)
52 }
53 defer os.RemoveAll(dir)
54
55 if testenv.HasExternalNetwork() && testenv.HasExec() {
56 if _, err := exec.LookPath("git"); err == nil {
57
58
59
60
61 localGitRepo = filepath.Join(dir, "gitrepo2")
62 if _, err := Run("", "git", "clone", "--mirror", gitrepo1, localGitRepo); err != nil {
63 log.Fatal(err)
64 }
65 if _, err := Run(localGitRepo, "git", "config", "daemon.uploadarch", "true"); err != nil {
66 log.Fatal(err)
67 }
68 }
69 }
70
71 return m.Run()
72 }
73
74 func testRepo(t *testing.T, remote string) (Repo, error) {
75 if remote == "localGitRepo" {
76
77
78
79 var url string
80 if strings.HasPrefix(localGitRepo, "/") {
81 url = "file://" + localGitRepo
82 } else {
83 url = "file:///" + filepath.ToSlash(localGitRepo)
84 }
85 testenv.MustHaveExecPath(t, "git")
86 return LocalGitRepo(url)
87 }
88 vcs := "git"
89 for _, k := range []string{"hg"} {
90 if strings.Contains(remote, "/"+k+"/") {
91 vcs = k
92 }
93 }
94 testenv.MustHaveExecPath(t, vcs)
95 return NewRepo(vcs, remote)
96 }
97
98 var tagsTests = []struct {
99 repo string
100 prefix string
101 tags []string
102 }{
103 {gitrepo1, "xxx", []string{}},
104 {gitrepo1, "", []string{"v1.2.3", "v1.2.4-annotated", "v2.0.1", "v2.0.2", "v2.3"}},
105 {gitrepo1, "v", []string{"v1.2.3", "v1.2.4-annotated", "v2.0.1", "v2.0.2", "v2.3"}},
106 {gitrepo1, "v1", []string{"v1.2.3", "v1.2.4-annotated"}},
107 {gitrepo1, "2", []string{}},
108 }
109
110 func TestTags(t *testing.T) {
111 testenv.MustHaveExternalNetwork(t)
112 testenv.MustHaveExec(t)
113
114 for _, tt := range tagsTests {
115 f := func(t *testing.T) {
116 r, err := testRepo(t, tt.repo)
117 if err != nil {
118 t.Fatal(err)
119 }
120 tags, err := r.Tags(tt.prefix)
121 if err != nil {
122 t.Fatal(err)
123 }
124 if !reflect.DeepEqual(tags, tt.tags) {
125 t.Errorf("Tags: incorrect tags\nhave %v\nwant %v", tags, tt.tags)
126 }
127 }
128 t.Run(path.Base(tt.repo)+"/"+tt.prefix, f)
129 if tt.repo == gitrepo1 {
130 for _, tt.repo = range altRepos {
131 t.Run(path.Base(tt.repo)+"/"+tt.prefix, f)
132 }
133 }
134 }
135 }
136
137 var latestTests = []struct {
138 repo string
139 info *RevInfo
140 }{
141 {
142 gitrepo1,
143 &RevInfo{
144 Name: "ede458df7cd0fdca520df19a33158086a8a68e81",
145 Short: "ede458df7cd0",
146 Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
147 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
148 Tags: []string{"v1.2.3", "v1.2.4-annotated"},
149 },
150 },
151 {
152 hgrepo1,
153 &RevInfo{
154 Name: "18518c07eb8ed5c80221e997e518cccaa8c0c287",
155 Short: "18518c07eb8e",
156 Version: "18518c07eb8ed5c80221e997e518cccaa8c0c287",
157 Time: time.Date(2018, 6, 27, 16, 16, 30, 0, time.UTC),
158 },
159 },
160 }
161
162 func TestLatest(t *testing.T) {
163 testenv.MustHaveExternalNetwork(t)
164 testenv.MustHaveExec(t)
165
166 for _, tt := range latestTests {
167 f := func(t *testing.T) {
168 r, err := testRepo(t, tt.repo)
169 if err != nil {
170 t.Fatal(err)
171 }
172 info, err := r.Latest()
173 if err != nil {
174 t.Fatal(err)
175 }
176 if !reflect.DeepEqual(info, tt.info) {
177 t.Errorf("Latest: incorrect info\nhave %+v\nwant %+v", *info, *tt.info)
178 }
179 }
180 t.Run(path.Base(tt.repo), f)
181 if tt.repo == gitrepo1 {
182 tt.repo = "localGitRepo"
183 t.Run(path.Base(tt.repo), f)
184 }
185 }
186 }
187
188 var readFileTests = []struct {
189 repo string
190 rev string
191 file string
192 err string
193 data string
194 }{
195 {
196 repo: gitrepo1,
197 rev: "latest",
198 file: "README",
199 data: "",
200 },
201 {
202 repo: gitrepo1,
203 rev: "v2",
204 file: "another.txt",
205 data: "another\n",
206 },
207 {
208 repo: gitrepo1,
209 rev: "v2.3.4",
210 file: "another.txt",
211 err: fs.ErrNotExist.Error(),
212 },
213 }
214
215 func TestReadFile(t *testing.T) {
216 testenv.MustHaveExternalNetwork(t)
217 testenv.MustHaveExec(t)
218
219 for _, tt := range readFileTests {
220 f := func(t *testing.T) {
221 r, err := testRepo(t, tt.repo)
222 if err != nil {
223 t.Fatal(err)
224 }
225 data, err := r.ReadFile(tt.rev, tt.file, 100)
226 if err != nil {
227 if tt.err == "" {
228 t.Fatalf("ReadFile: unexpected error %v", err)
229 }
230 if !strings.Contains(err.Error(), tt.err) {
231 t.Fatalf("ReadFile: wrong error %q, want %q", err, tt.err)
232 }
233 if len(data) != 0 {
234 t.Errorf("ReadFile: non-empty data %q with error %v", data, err)
235 }
236 return
237 }
238 if tt.err != "" {
239 t.Fatalf("ReadFile: no error, wanted %v", tt.err)
240 }
241 if string(data) != tt.data {
242 t.Errorf("ReadFile: incorrect data\nhave %q\nwant %q", data, tt.data)
243 }
244 }
245 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, f)
246 if tt.repo == gitrepo1 {
247 for _, tt.repo = range altRepos {
248 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, f)
249 }
250 }
251 }
252 }
253
254 var readZipTests = []struct {
255 repo string
256 rev string
257 subdir string
258 err string
259 files map[string]uint64
260 }{
261 {
262 repo: gitrepo1,
263 rev: "v2.3.4",
264 subdir: "",
265 files: map[string]uint64{
266 "prefix/": 0,
267 "prefix/README": 0,
268 "prefix/v2": 3,
269 },
270 },
271 {
272 repo: hgrepo1,
273 rev: "v2.3.4",
274 subdir: "",
275 files: map[string]uint64{
276 "prefix/.hg_archival.txt": ^uint64(0),
277 "prefix/README": 0,
278 "prefix/v2": 3,
279 },
280 },
281
282 {
283 repo: gitrepo1,
284 rev: "v2",
285 subdir: "",
286 files: map[string]uint64{
287 "prefix/": 0,
288 "prefix/README": 0,
289 "prefix/v2": 3,
290 "prefix/another.txt": 8,
291 "prefix/foo.txt": 13,
292 },
293 },
294 {
295 repo: hgrepo1,
296 rev: "v2",
297 subdir: "",
298 files: map[string]uint64{
299 "prefix/.hg_archival.txt": ^uint64(0),
300 "prefix/README": 0,
301 "prefix/v2": 3,
302 "prefix/another.txt": 8,
303 "prefix/foo.txt": 13,
304 },
305 },
306
307 {
308 repo: gitrepo1,
309 rev: "v3",
310 subdir: "",
311 files: map[string]uint64{
312 "prefix/": 0,
313 "prefix/v3/": 0,
314 "prefix/v3/sub/": 0,
315 "prefix/v3/sub/dir/": 0,
316 "prefix/v3/sub/dir/file.txt": 16,
317 "prefix/README": 0,
318 },
319 },
320 {
321 repo: hgrepo1,
322 rev: "v3",
323 subdir: "",
324 files: map[string]uint64{
325 "prefix/.hg_archival.txt": ^uint64(0),
326 "prefix/.hgtags": 405,
327 "prefix/v3/sub/dir/file.txt": 16,
328 "prefix/README": 0,
329 },
330 },
331
332 {
333 repo: gitrepo1,
334 rev: "v3",
335 subdir: "v3/sub/dir",
336 files: map[string]uint64{
337 "prefix/": 0,
338 "prefix/v3/": 0,
339 "prefix/v3/sub/": 0,
340 "prefix/v3/sub/dir/": 0,
341 "prefix/v3/sub/dir/file.txt": 16,
342 },
343 },
344 {
345 repo: hgrepo1,
346 rev: "v3",
347 subdir: "v3/sub/dir",
348 files: map[string]uint64{
349 "prefix/v3/sub/dir/file.txt": 16,
350 },
351 },
352
353 {
354 repo: gitrepo1,
355 rev: "v3",
356 subdir: "v3/sub",
357 files: map[string]uint64{
358 "prefix/": 0,
359 "prefix/v3/": 0,
360 "prefix/v3/sub/": 0,
361 "prefix/v3/sub/dir/": 0,
362 "prefix/v3/sub/dir/file.txt": 16,
363 },
364 },
365 {
366 repo: hgrepo1,
367 rev: "v3",
368 subdir: "v3/sub",
369 files: map[string]uint64{
370 "prefix/v3/sub/dir/file.txt": 16,
371 },
372 },
373
374 {
375 repo: gitrepo1,
376 rev: "aaaaaaaaab",
377 subdir: "",
378 err: "unknown revision",
379 },
380 {
381 repo: hgrepo1,
382 rev: "aaaaaaaaab",
383 subdir: "",
384 err: "unknown revision",
385 },
386
387 {
388 repo: "https://github.com/rsc/vgotest1",
389 rev: "submod/v1.0.4",
390 subdir: "submod",
391 files: map[string]uint64{
392 "prefix/": 0,
393 "prefix/submod/": 0,
394 "prefix/submod/go.mod": 53,
395 "prefix/submod/pkg/": 0,
396 "prefix/submod/pkg/p.go": 31,
397 },
398 },
399 }
400
401 type zipFile struct {
402 name string
403 size int64
404 }
405
406 func TestReadZip(t *testing.T) {
407 testenv.MustHaveExternalNetwork(t)
408 testenv.MustHaveExec(t)
409
410 for _, tt := range readZipTests {
411 f := func(t *testing.T) {
412 r, err := testRepo(t, tt.repo)
413 if err != nil {
414 t.Fatal(err)
415 }
416 rc, err := r.ReadZip(tt.rev, tt.subdir, 100000)
417 if err != nil {
418 if tt.err == "" {
419 t.Fatalf("ReadZip: unexpected error %v", err)
420 }
421 if !strings.Contains(err.Error(), tt.err) {
422 t.Fatalf("ReadZip: wrong error %q, want %q", err, tt.err)
423 }
424 if rc != nil {
425 t.Errorf("ReadZip: non-nil io.ReadCloser with error %v", err)
426 }
427 return
428 }
429 defer rc.Close()
430 if tt.err != "" {
431 t.Fatalf("ReadZip: no error, wanted %v", tt.err)
432 }
433 zipdata, err := io.ReadAll(rc)
434 if err != nil {
435 t.Fatal(err)
436 }
437 z, err := zip.NewReader(bytes.NewReader(zipdata), int64(len(zipdata)))
438 if err != nil {
439 t.Fatalf("ReadZip: cannot read zip file: %v", err)
440 }
441 have := make(map[string]bool)
442 for _, f := range z.File {
443 size, ok := tt.files[f.Name]
444 if !ok {
445 t.Errorf("ReadZip: unexpected file %s", f.Name)
446 continue
447 }
448 have[f.Name] = true
449 if size != ^uint64(0) && f.UncompressedSize64 != size {
450 t.Errorf("ReadZip: file %s has unexpected size %d != %d", f.Name, f.UncompressedSize64, size)
451 }
452 }
453 for name := range tt.files {
454 if !have[name] {
455 t.Errorf("ReadZip: missing file %s", name)
456 }
457 }
458 }
459 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, f)
460 if tt.repo == gitrepo1 {
461 tt.repo = "localGitRepo"
462 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, f)
463 }
464 }
465 }
466
467 var hgmap = map[string]string{
468 "HEAD": "41964ddce1180313bdc01d0a39a2813344d6261d",
469 "9d02800338b8a55be062c838d1f02e0c5780b9eb": "8f49ee7a6ddcdec6f0112d9dca48d4a2e4c3c09e",
470 "76a00fb249b7f93091bc2c89a789dab1fc1bc26f": "88fde824ec8b41a76baa16b7e84212cee9f3edd0",
471 "ede458df7cd0fdca520df19a33158086a8a68e81": "41964ddce1180313bdc01d0a39a2813344d6261d",
472 "97f6aa59c81c623494825b43d39e445566e429a4": "c0cbbfb24c7c3c50c35c7b88e7db777da4ff625d",
473 }
474
475 var statTests = []struct {
476 repo string
477 rev string
478 err string
479 info *RevInfo
480 }{
481 {
482 repo: gitrepo1,
483 rev: "HEAD",
484 info: &RevInfo{
485 Name: "ede458df7cd0fdca520df19a33158086a8a68e81",
486 Short: "ede458df7cd0",
487 Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
488 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
489 Tags: []string{"v1.2.3", "v1.2.4-annotated"},
490 },
491 },
492 {
493 repo: gitrepo1,
494 rev: "v2",
495 info: &RevInfo{
496 Name: "9d02800338b8a55be062c838d1f02e0c5780b9eb",
497 Short: "9d02800338b8",
498 Version: "9d02800338b8a55be062c838d1f02e0c5780b9eb",
499 Time: time.Date(2018, 4, 17, 20, 00, 32, 0, time.UTC),
500 Tags: []string{"v2.0.2"},
501 },
502 },
503 {
504 repo: gitrepo1,
505 rev: "v2.3.4",
506 info: &RevInfo{
507 Name: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
508 Short: "76a00fb249b7",
509 Version: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
510 Time: time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
511 Tags: []string{"v2.0.1", "v2.3"},
512 },
513 },
514 {
515 repo: gitrepo1,
516 rev: "v2.3",
517 info: &RevInfo{
518 Name: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
519 Short: "76a00fb249b7",
520 Version: "v2.3",
521 Time: time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
522 Tags: []string{"v2.0.1", "v2.3"},
523 },
524 },
525 {
526 repo: gitrepo1,
527 rev: "v1.2.3",
528 info: &RevInfo{
529 Name: "ede458df7cd0fdca520df19a33158086a8a68e81",
530 Short: "ede458df7cd0",
531 Version: "v1.2.3",
532 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
533 Tags: []string{"v1.2.3", "v1.2.4-annotated"},
534 },
535 },
536 {
537 repo: gitrepo1,
538 rev: "ede458df",
539 info: &RevInfo{
540 Name: "ede458df7cd0fdca520df19a33158086a8a68e81",
541 Short: "ede458df7cd0",
542 Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
543 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
544 Tags: []string{"v1.2.3", "v1.2.4-annotated"},
545 },
546 },
547 {
548 repo: gitrepo1,
549 rev: "97f6aa59",
550 info: &RevInfo{
551 Name: "97f6aa59c81c623494825b43d39e445566e429a4",
552 Short: "97f6aa59c81c",
553 Version: "97f6aa59c81c623494825b43d39e445566e429a4",
554 Time: time.Date(2018, 4, 17, 20, 0, 19, 0, time.UTC),
555 },
556 },
557 {
558 repo: gitrepo1,
559 rev: "v1.2.4-annotated",
560 info: &RevInfo{
561 Name: "ede458df7cd0fdca520df19a33158086a8a68e81",
562 Short: "ede458df7cd0",
563 Version: "v1.2.4-annotated",
564 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
565 Tags: []string{"v1.2.3", "v1.2.4-annotated"},
566 },
567 },
568 {
569 repo: gitrepo1,
570 rev: "aaaaaaaaab",
571 err: "unknown revision",
572 },
573 }
574
575 func TestStat(t *testing.T) {
576 testenv.MustHaveExternalNetwork(t)
577 testenv.MustHaveExec(t)
578
579 for _, tt := range statTests {
580 f := func(t *testing.T) {
581 r, err := testRepo(t, tt.repo)
582 if err != nil {
583 t.Fatal(err)
584 }
585 info, err := r.Stat(tt.rev)
586 if err != nil {
587 if tt.err == "" {
588 t.Fatalf("Stat: unexpected error %v", err)
589 }
590 if !strings.Contains(err.Error(), tt.err) {
591 t.Fatalf("Stat: wrong error %q, want %q", err, tt.err)
592 }
593 if info != nil {
594 t.Errorf("Stat: non-nil info with error %q", err)
595 }
596 return
597 }
598 if !reflect.DeepEqual(info, tt.info) {
599 t.Errorf("Stat: incorrect info\nhave %+v\nwant %+v", *info, *tt.info)
600 }
601 }
602 t.Run(path.Base(tt.repo)+"/"+tt.rev, f)
603 if tt.repo == gitrepo1 {
604 for _, tt.repo = range altRepos {
605 old := tt
606 var m map[string]string
607 if tt.repo == hgrepo1 {
608 m = hgmap
609 }
610 if tt.info != nil {
611 info := *tt.info
612 tt.info = &info
613 tt.info.Name = remap(tt.info.Name, m)
614 tt.info.Version = remap(tt.info.Version, m)
615 tt.info.Short = remap(tt.info.Short, m)
616 }
617 tt.rev = remap(tt.rev, m)
618 t.Run(path.Base(tt.repo)+"/"+tt.rev, f)
619 tt = old
620 }
621 }
622 }
623 }
624
625 func remap(name string, m map[string]string) string {
626 if m[name] != "" {
627 return m[name]
628 }
629 if AllHex(name) {
630 for k, v := range m {
631 if strings.HasPrefix(k, name) {
632 return v[:len(name)]
633 }
634 }
635 }
636 return name
637 }
638
View as plain text