1
2
3
4
5 package zip_test
6
7 import (
8 "archive/zip"
9 "bytes"
10 "crypto/sha256"
11 "encoding/hex"
12 "errors"
13 "fmt"
14 "io"
15 "os"
16 "os/exec"
17 "path"
18 "path/filepath"
19 "runtime"
20 "strings"
21 "sync"
22 "sync/atomic"
23 "testing"
24 "time"
25
26 "golang.org/x/mod/module"
27 "golang.org/x/mod/sumdb/dirhash"
28 modzip "golang.org/x/mod/zip"
29 "golang.org/x/tools/txtar"
30 )
31
32 const emptyHash = "h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
33
34 var gitOnce struct {
35 path string
36 err error
37 sync.Once
38 }
39
40 func init() {
41 if os.Getenv("GO_BUILDER_NAME") != "" || os.Getenv("GIT_TRACE_CURL") == "1" {
42
43
44 os.Setenv("GIT_TRACE_CURL", "1")
45 os.Setenv("GIT_TRACE_CURL_NO_DATA", "1")
46 os.Setenv("GIT_REDACT_COOKIES", "o,SSO,GSSO_Uberproxy")
47 }
48 }
49
50
51
52 func gitPath() (string, error) {
53 gitOnce.Do(func() {
54 path, err := exec.LookPath("git")
55 if err != nil {
56 gitOnce.err = err
57 return
58 }
59 if runtime.GOOS == "plan9" {
60 gitOnce.err = errors.New("plan9 git does not support the full git command line")
61 }
62 gitOnce.path = path
63 })
64
65 return gitOnce.path, gitOnce.err
66 }
67
68 func mustHaveGit(t testing.TB) {
69 if _, err := gitPath(); err != nil {
70 t.Helper()
71 t.Skipf("skipping: %v", err)
72 }
73 }
74
75 type testParams struct {
76 path, version, wantErr, hash string
77 archive *txtar.Archive
78 }
79
80
81
82
83 func readTest(file string) (testParams, error) {
84 var test testParams
85 var err error
86 test.archive, err = txtar.ParseFile(file)
87 if err != nil {
88 return testParams{}, err
89 }
90
91 lines := strings.Split(string(test.archive.Comment), "\n")
92 for n, line := range lines {
93 n++
94 if i := strings.IndexByte(line, '#'); i >= 0 {
95 line = line[:i]
96 }
97 line = strings.TrimSpace(line)
98 if line == "" {
99 continue
100 }
101 eq := strings.IndexByte(line, '=')
102 if eq < 0 {
103 return testParams{}, fmt.Errorf("%s:%d: missing = separator", file, n)
104 }
105 key, value := strings.TrimSpace(line[:eq]), strings.TrimSpace(line[eq+1:])
106 switch key {
107 case "path":
108 test.path = value
109 case "version":
110 test.version = value
111 case "wantErr":
112 test.wantErr = value
113 case "hash":
114 test.hash = value
115 default:
116 return testParams{}, fmt.Errorf("%s:%d: unknown key %q", file, n, key)
117 }
118 }
119
120 return test, nil
121 }
122
123 func extractTxtarToTempDir(t testing.TB, arc *txtar.Archive) (dir string, err error) {
124 dir = t.TempDir()
125 for _, f := range arc.Files {
126 filePath := filepath.Join(dir, f.Name)
127 if err := os.MkdirAll(filepath.Dir(filePath), 0777); err != nil {
128 return "", err
129 }
130 if err := os.WriteFile(filePath, f.Data, 0666); err != nil {
131 return "", err
132 }
133 }
134 return dir, nil
135 }
136
137 func extractTxtarToTempZip(t *testing.T, arc *txtar.Archive) (zipPath string, err error) {
138 zipPath = filepath.Join(t.TempDir(), "txtar.zip")
139
140 zipFile, err := os.Create(zipPath)
141 if err != nil {
142 return "", err
143 }
144 defer func() {
145 if cerr := zipFile.Close(); err == nil && cerr != nil {
146 err = cerr
147 }
148 }()
149
150 zw := zip.NewWriter(zipFile)
151 for _, f := range arc.Files {
152 zf, err := zw.Create(f.Name)
153 if err != nil {
154 return "", err
155 }
156 if _, err := zf.Write(f.Data); err != nil {
157 return "", err
158 }
159 }
160 if err := zw.Close(); err != nil {
161 return "", err
162 }
163 return zipFile.Name(), nil
164 }
165
166 type fakeFile struct {
167 name string
168 size uint64
169 data []byte
170 }
171
172 func (f fakeFile) Path() string { return f.name }
173 func (f fakeFile) Lstat() (os.FileInfo, error) { return fakeFileInfo{f}, nil }
174 func (f fakeFile) Open() (io.ReadCloser, error) {
175 if f.data != nil {
176 return io.NopCloser(bytes.NewReader(f.data)), nil
177 }
178 if f.size >= uint64(modzip.MaxZipFile<<1) {
179 return nil, fmt.Errorf("cannot open fakeFile of size %d", f.size)
180 }
181 return io.NopCloser(io.LimitReader(zeroReader{}, int64(f.size))), nil
182 }
183
184 type fakeFileInfo struct {
185 f fakeFile
186 }
187
188 func (fi fakeFileInfo) Name() string { return path.Base(fi.f.name) }
189 func (fi fakeFileInfo) Size() int64 { return int64(fi.f.size) }
190 func (fi fakeFileInfo) Mode() os.FileMode { return 0644 }
191 func (fi fakeFileInfo) ModTime() time.Time { return time.Time{} }
192 func (fi fakeFileInfo) IsDir() bool { return false }
193 func (fi fakeFileInfo) Sys() interface{} { return nil }
194
195 type zeroReader struct{}
196
197 func (r zeroReader) Read(b []byte) (int, error) {
198 for i := range b {
199 b[i] = 0
200 }
201 return len(b), nil
202 }
203
204 func formatCheckedFiles(cf modzip.CheckedFiles) string {
205 buf := &bytes.Buffer{}
206 fmt.Fprintf(buf, "valid:\n")
207 for _, f := range cf.Valid {
208 fmt.Fprintln(buf, f)
209 }
210 fmt.Fprintf(buf, "\nomitted:\n")
211 for _, f := range cf.Omitted {
212 fmt.Fprintf(buf, "%s: %v\n", f.Path, f.Err)
213 }
214 fmt.Fprintf(buf, "\ninvalid:\n")
215 for _, f := range cf.Invalid {
216 fmt.Fprintf(buf, "%s: %v\n", f.Path, f.Err)
217 }
218 return buf.String()
219 }
220
221
222
223
224
225 func TestCheckFiles(t *testing.T) {
226 testPaths, err := filepath.Glob(filepath.FromSlash("testdata/check_files/*.txt"))
227 if err != nil {
228 t.Fatal(err)
229 }
230 for _, testPath := range testPaths {
231 testPath := testPath
232 name := strings.TrimSuffix(filepath.Base(testPath), ".txt")
233 t.Run(name, func(t *testing.T) {
234 t.Parallel()
235
236
237 test, err := readTest(testPath)
238 if err != nil {
239 t.Fatal(err)
240 }
241 files := make([]modzip.File, 0, len(test.archive.Files))
242 var want string
243 for _, tf := range test.archive.Files {
244 if tf.Name == "want" {
245 want = string(tf.Data)
246 continue
247 }
248 files = append(files, fakeFile{
249 name: tf.Name,
250 size: uint64(len(tf.Data)),
251 data: tf.Data,
252 })
253 }
254
255
256 cf, _ := modzip.CheckFiles(files)
257 got := formatCheckedFiles(cf)
258 if got != want {
259 t.Errorf("got:\n%s\n\nwant:\n%s", got, want)
260 }
261
262
263
264 var gotErr, wantErr string
265 if len(cf.Invalid) > 0 {
266 wantErr = modzip.FileErrorList(cf.Invalid).Error()
267 }
268 if err := cf.Err(); err != nil {
269 gotErr = err.Error()
270 }
271 if gotErr != wantErr {
272 t.Errorf("got error:\n%s\n\nwant error:\n%s", gotErr, wantErr)
273 }
274 })
275 }
276 }
277
278
279
280
281
282 func TestCheckDir(t *testing.T) {
283 testPaths, err := filepath.Glob(filepath.FromSlash("testdata/check_dir/*.txt"))
284 if err != nil {
285 t.Fatal(err)
286 }
287 for _, testPath := range testPaths {
288 testPath := testPath
289 name := strings.TrimSuffix(filepath.Base(testPath), ".txt")
290 t.Run(name, func(t *testing.T) {
291 t.Parallel()
292
293
294 test, err := readTest(testPath)
295 if err != nil {
296 t.Fatal(err)
297 }
298 var want string
299 for i, f := range test.archive.Files {
300 if f.Name == "want" {
301 want = string(f.Data)
302 test.archive.Files = append(test.archive.Files[:i], test.archive.Files[i+1:]...)
303 break
304 }
305 }
306 tmpDir, err := extractTxtarToTempDir(t, test.archive)
307 if err != nil {
308 t.Fatal(err)
309 }
310
311
312 cf, err := modzip.CheckDir(tmpDir)
313 if err != nil && err.Error() != cf.Err().Error() {
314
315 t.Fatal(err)
316 }
317 rep := strings.NewReplacer(tmpDir, "$work", `'\''`, `'\''`, string(os.PathSeparator), "/")
318 got := rep.Replace(formatCheckedFiles(cf))
319 if got != want {
320 t.Errorf("got:\n%s\n\nwant:\n%s", got, want)
321 }
322
323
324
325 var gotErr, wantErr string
326 if len(cf.Invalid) > 0 {
327 wantErr = modzip.FileErrorList(cf.Invalid).Error()
328 }
329 if err := cf.Err(); err != nil {
330 gotErr = err.Error()
331 }
332 if gotErr != wantErr {
333 t.Errorf("got error:\n%s\n\nwant error:\n%s", gotErr, wantErr)
334 }
335 })
336 }
337 }
338
339
340
341
342 func TestCheckZip(t *testing.T) {
343 testPaths, err := filepath.Glob(filepath.FromSlash("testdata/check_zip/*.txt"))
344 if err != nil {
345 t.Fatal(err)
346 }
347 for _, testPath := range testPaths {
348 testPath := testPath
349 name := strings.TrimSuffix(filepath.Base(testPath), ".txt")
350 t.Run(name, func(t *testing.T) {
351 t.Parallel()
352
353
354 test, err := readTest(testPath)
355 if err != nil {
356 t.Fatal(err)
357 }
358 var want string
359 for i, f := range test.archive.Files {
360 if f.Name == "want" {
361 want = string(f.Data)
362 test.archive.Files = append(test.archive.Files[:i], test.archive.Files[i+1:]...)
363 break
364 }
365 }
366 tmpZipPath, err := extractTxtarToTempZip(t, test.archive)
367 if err != nil {
368 t.Fatal(err)
369 }
370
371
372 m := module.Version{Path: test.path, Version: test.version}
373 cf, err := modzip.CheckZip(m, tmpZipPath)
374 if err != nil && err.Error() != cf.Err().Error() {
375
376 t.Fatal(err)
377 }
378 got := formatCheckedFiles(cf)
379 if got != want {
380 t.Errorf("got:\n%s\n\nwant:\n%s", got, want)
381 }
382
383
384
385 var gotErr, wantErr string
386 if len(cf.Invalid) > 0 {
387 wantErr = modzip.FileErrorList(cf.Invalid).Error()
388 }
389 if err := cf.Err(); err != nil {
390 gotErr = err.Error()
391 }
392 if gotErr != wantErr {
393 t.Errorf("got error:\n%s\n\nwant error:\n%s", gotErr, wantErr)
394 }
395 })
396 }
397 }
398
399 func TestCreate(t *testing.T) {
400 testDir := filepath.FromSlash("testdata/create")
401 testEntries, err := os.ReadDir(testDir)
402 if err != nil {
403 t.Fatal(err)
404 }
405 for _, testEntry := range testEntries {
406 testEntry := testEntry
407 base := filepath.Base(testEntry.Name())
408 if filepath.Ext(base) != ".txt" {
409 continue
410 }
411 t.Run(base[:len(base)-len(".txt")], func(t *testing.T) {
412 t.Parallel()
413
414
415 testPath := filepath.Join(testDir, testEntry.Name())
416 test, err := readTest(testPath)
417 if err != nil {
418 t.Fatal(err)
419 }
420
421
422 tmpZip, err := os.CreateTemp(t.TempDir(), "TestCreate-*.zip")
423 if err != nil {
424 t.Fatal(err)
425 }
426 tmpZipPath := tmpZip.Name()
427 defer tmpZip.Close()
428 m := module.Version{Path: test.path, Version: test.version}
429 files := make([]modzip.File, len(test.archive.Files))
430 for i, tf := range test.archive.Files {
431 files[i] = fakeFile{
432 name: tf.Name,
433 size: uint64(len(tf.Data)),
434 data: tf.Data,
435 }
436 }
437 if err := modzip.Create(tmpZip, m, files); err != nil {
438 if test.wantErr == "" {
439 t.Fatalf("unexpected error: %v", err)
440 } else if !strings.Contains(err.Error(), test.wantErr) {
441 t.Fatalf("got error %q; want error containing %q", err.Error(), test.wantErr)
442 } else {
443 return
444 }
445 } else if test.wantErr != "" {
446 t.Fatalf("unexpected success; wanted error containing %q", test.wantErr)
447 }
448 if err := tmpZip.Close(); err != nil {
449 t.Fatal(err)
450 }
451
452
453 if hash, err := dirhash.HashZip(tmpZipPath, dirhash.Hash1); err != nil {
454 t.Fatal(err)
455 } else if hash != test.hash {
456 t.Fatalf("got hash: %q\nwant: %q", hash, test.hash)
457 }
458 })
459 }
460 }
461
462 func TestCreateFromDir(t *testing.T) {
463 testDir := filepath.FromSlash("testdata/create_from_dir")
464 testEntries, err := os.ReadDir(testDir)
465 if err != nil {
466 t.Fatal(err)
467 }
468 for _, testEntry := range testEntries {
469 testEntry := testEntry
470 base := filepath.Base(testEntry.Name())
471 if filepath.Ext(base) != ".txt" {
472 continue
473 }
474 t.Run(base[:len(base)-len(".txt")], func(t *testing.T) {
475 t.Parallel()
476
477
478 testPath := filepath.Join(testDir, testEntry.Name())
479 test, err := readTest(testPath)
480 if err != nil {
481 t.Fatal(err)
482 }
483
484
485 tmpDir, err := extractTxtarToTempDir(t, test.archive)
486 if err != nil {
487 t.Fatal(err)
488 }
489
490
491 tmpZip, err := os.CreateTemp(t.TempDir(), "TestCreateFromDir-*.zip")
492 if err != nil {
493 t.Fatal(err)
494 }
495 tmpZipPath := tmpZip.Name()
496 defer tmpZip.Close()
497 m := module.Version{Path: test.path, Version: test.version}
498 if err := modzip.CreateFromDir(tmpZip, m, tmpDir); err != nil {
499 if test.wantErr == "" {
500 t.Fatalf("unexpected error: %v", err)
501 } else if !strings.Contains(err.Error(), test.wantErr) {
502 t.Fatalf("got error %q; want error containing %q", err, test.wantErr)
503 } else {
504 return
505 }
506 } else if test.wantErr != "" {
507 t.Fatalf("unexpected success; want error containing %q", test.wantErr)
508 }
509
510
511 if hash, err := dirhash.HashZip(tmpZipPath, dirhash.Hash1); err != nil {
512 t.Fatal(err)
513 } else if hash != test.hash {
514 t.Fatalf("got hash: %q\nwant: %q", hash, test.hash)
515 }
516 })
517 }
518 }
519
520 func TestCreateFromDirSpecial(t *testing.T) {
521 for _, test := range []struct {
522 desc string
523 setup func(t *testing.T, tmpDir string) string
524 wantHash string
525 }{
526 {
527 desc: "ignore_empty_dir",
528 setup: func(t *testing.T, tmpDir string) string {
529 if err := os.Mkdir(filepath.Join(tmpDir, "empty"), 0777); err != nil {
530 t.Fatal(err)
531 }
532 return tmpDir
533 },
534 wantHash: emptyHash,
535 }, {
536 desc: "ignore_symlink",
537 setup: func(t *testing.T, tmpDir string) string {
538 if err := os.Symlink(tmpDir, filepath.Join(tmpDir, "link")); err != nil {
539 switch runtime.GOOS {
540 case "aix", "android", "darwin", "dragonfly", "freebsd", "illumos", "ios", "js", "linux", "netbsd", "openbsd", "solaris":
541
542 t.Fatal(err)
543 default:
544 t.Skipf("could not create symlink: %v", err)
545 }
546 }
547 return tmpDir
548 },
549 wantHash: emptyHash,
550 }, {
551 desc: "dir_is_vendor",
552 setup: func(t *testing.T, tmpDir string) string {
553 vendorDir := filepath.Join(tmpDir, "vendor")
554 if err := os.Mkdir(vendorDir, 0777); err != nil {
555 t.Fatal(err)
556 }
557 goModData := []byte("module example.com/m\n\ngo 1.13\n")
558 if err := os.WriteFile(filepath.Join(vendorDir, "go.mod"), goModData, 0666); err != nil {
559 t.Fatal(err)
560 }
561 return vendorDir
562 },
563 wantHash: "h1:XduFAgX/GaspZa8Jv4pfzoGEzNaU/r88PiCunijw5ok=",
564 },
565 } {
566 t.Run(test.desc, func(t *testing.T) {
567 dir := test.setup(t, t.TempDir())
568
569 tmpZipFile, err := os.CreateTemp(t.TempDir(), "TestCreateFromDir-*.zip")
570 if err != nil {
571 t.Fatal(err)
572 }
573 tmpZipPath := tmpZipFile.Name()
574 defer tmpZipFile.Close()
575
576 m := module.Version{Path: "example.com/m", Version: "v1.0.0"}
577 if err := modzip.CreateFromDir(tmpZipFile, m, dir); err != nil {
578 t.Fatal(err)
579 }
580 if err := tmpZipFile.Close(); err != nil {
581 t.Fatal(err)
582 }
583
584 if hash, err := dirhash.HashZip(tmpZipPath, dirhash.Hash1); err != nil {
585 t.Fatal(err)
586 } else if hash != test.wantHash {
587 t.Fatalf("got hash %q; want %q", hash, emptyHash)
588 }
589 })
590 }
591 }
592
593 func TestUnzip(t *testing.T) {
594 testDir := filepath.FromSlash("testdata/unzip")
595 testEntries, err := os.ReadDir(testDir)
596 if err != nil {
597 t.Fatal(err)
598 }
599 for _, testEntry := range testEntries {
600 base := filepath.Base(testEntry.Name())
601 if filepath.Ext(base) != ".txt" {
602 continue
603 }
604 t.Run(base[:len(base)-len(".txt")], func(t *testing.T) {
605
606 testPath := filepath.Join(testDir, testEntry.Name())
607 test, err := readTest(testPath)
608 if err != nil {
609 t.Fatal(err)
610 }
611
612
613 tmpZipPath, err := extractTxtarToTempZip(t, test.archive)
614 if err != nil {
615 t.Fatal(err)
616 }
617
618
619 tmpDir := t.TempDir()
620 m := module.Version{Path: test.path, Version: test.version}
621 if err := modzip.Unzip(tmpDir, m, tmpZipPath); err != nil {
622 if test.wantErr == "" {
623 t.Fatalf("unexpected error: %v", err)
624 } else if !strings.Contains(err.Error(), test.wantErr) {
625 t.Fatalf("got error %q; want error containing %q", err.Error(), test.wantErr)
626 } else {
627 return
628 }
629 } else if test.wantErr != "" {
630 t.Fatalf("unexpected success; wanted error containing %q", test.wantErr)
631 }
632
633
634 prefix := fmt.Sprintf("%s@%s/", test.path, test.version)
635 if hash, err := dirhash.HashDir(tmpDir, prefix, dirhash.Hash1); err != nil {
636 t.Fatal(err)
637 } else if hash != test.hash {
638 t.Fatalf("got hash %q\nwant: %q", hash, test.hash)
639 }
640 })
641 }
642 }
643
644 type sizeLimitTest struct {
645 desc string
646 files []modzip.File
647 wantErr string
648 wantCheckFilesErr string
649 wantCreateErr string
650 wantCheckZipErr string
651 wantUnzipErr string
652 }
653
654
655 var sizeLimitTests = [...]sizeLimitTest{
656 {
657 desc: "one_large",
658 files: []modzip.File{fakeFile{
659 name: "large.go",
660 size: modzip.MaxZipFile,
661 }},
662 }, {
663 desc: "one_too_large",
664 files: []modzip.File{fakeFile{
665 name: "large.go",
666 size: modzip.MaxZipFile + 1,
667 }},
668 wantCheckFilesErr: "module source tree too large",
669 wantCreateErr: "module source tree too large",
670 wantCheckZipErr: "total uncompressed size of module contents too large",
671 wantUnzipErr: "total uncompressed size of module contents too large",
672 }, {
673 desc: "total_large",
674 files: []modzip.File{
675 fakeFile{
676 name: "small.go",
677 size: 10,
678 },
679 fakeFile{
680 name: "large.go",
681 size: modzip.MaxZipFile - 10,
682 },
683 },
684 }, {
685 desc: "total_too_large",
686 files: []modzip.File{
687 fakeFile{
688 name: "small.go",
689 size: 10,
690 },
691 fakeFile{
692 name: "large.go",
693 size: modzip.MaxZipFile - 9,
694 },
695 },
696 wantCheckFilesErr: "module source tree too large",
697 wantCreateErr: "module source tree too large",
698 wantCheckZipErr: "total uncompressed size of module contents too large",
699 wantUnzipErr: "total uncompressed size of module contents too large",
700 }, {
701 desc: "large_gomod",
702 files: []modzip.File{fakeFile{
703 name: "go.mod",
704 size: modzip.MaxGoMod,
705 }},
706 }, {
707 desc: "too_large_gomod",
708 files: []modzip.File{fakeFile{
709 name: "go.mod",
710 size: modzip.MaxGoMod + 1,
711 }},
712 wantErr: "go.mod file too large",
713 }, {
714 desc: "large_license",
715 files: []modzip.File{fakeFile{
716 name: "LICENSE",
717 size: modzip.MaxLICENSE,
718 }},
719 }, {
720 desc: "too_large_license",
721 files: []modzip.File{fakeFile{
722 name: "LICENSE",
723 size: modzip.MaxLICENSE + 1,
724 }},
725 wantErr: "LICENSE file too large",
726 },
727 }
728
729 var sizeLimitVersion = module.Version{Path: "example.com/large", Version: "v1.0.0"}
730
731 func TestCreateSizeLimits(t *testing.T) {
732 if testing.Short() {
733 t.Skip("creating large files takes time")
734 }
735 tests := append(sizeLimitTests[:], sizeLimitTest{
736
737
738 desc: "negative",
739 files: []modzip.File{fakeFile{
740 name: "neg.go",
741 size: 0x8000000000000000,
742 }},
743 wantErr: "module source tree too large",
744 }, sizeLimitTest{
745 desc: "size_is_a_lie",
746 files: []modzip.File{fakeFile{
747 name: "lie.go",
748 size: 1,
749 data: []byte(`package large`),
750 }},
751 wantCreateErr: "larger than declared size",
752 })
753
754 for _, test := range tests {
755 test := test
756 t.Run(test.desc, func(t *testing.T) {
757 t.Parallel()
758
759 wantCheckFilesErr := test.wantCheckFilesErr
760 if wantCheckFilesErr == "" {
761 wantCheckFilesErr = test.wantErr
762 }
763 if _, err := modzip.CheckFiles(test.files); err == nil && wantCheckFilesErr != "" {
764 t.Fatalf("CheckFiles: unexpected success; want error containing %q", wantCheckFilesErr)
765 } else if err != nil && wantCheckFilesErr == "" {
766 t.Fatalf("CheckFiles: got error %q; want success", err)
767 } else if err != nil && !strings.Contains(err.Error(), wantCheckFilesErr) {
768 t.Fatalf("CheckFiles: got error %q; want error containing %q", err, wantCheckFilesErr)
769 }
770
771 wantCreateErr := test.wantCreateErr
772 if wantCreateErr == "" {
773 wantCreateErr = test.wantErr
774 }
775 if err := modzip.Create(io.Discard, sizeLimitVersion, test.files); err == nil && wantCreateErr != "" {
776 t.Fatalf("Create: unexpected success; want error containing %q", wantCreateErr)
777 } else if err != nil && wantCreateErr == "" {
778 t.Fatalf("Create: got error %q; want success", err)
779 } else if err != nil && !strings.Contains(err.Error(), wantCreateErr) {
780 t.Fatalf("Create: got error %q; want error containing %q", err, wantCreateErr)
781 }
782 })
783 }
784 }
785
786 func TestUnzipSizeLimits(t *testing.T) {
787 if testing.Short() {
788 t.Skip("creating large files takes time")
789 }
790 for _, test := range sizeLimitTests {
791 test := test
792 t.Run(test.desc, func(t *testing.T) {
793 t.Parallel()
794 tmpZipFile, err := os.CreateTemp(t.TempDir(), "TestUnzipSizeLimits-*.zip")
795 if err != nil {
796 t.Fatal(err)
797 }
798 tmpZipPath := tmpZipFile.Name()
799 defer tmpZipFile.Close()
800
801 zw := zip.NewWriter(tmpZipFile)
802 prefix := fmt.Sprintf("%s@%s/", sizeLimitVersion.Path, sizeLimitVersion.Version)
803 for _, tf := range test.files {
804 zf, err := zw.Create(prefix + tf.Path())
805 if err != nil {
806 t.Fatal(err)
807 }
808 rc, err := tf.Open()
809 if err != nil {
810 t.Fatal(err)
811 }
812 _, err = io.Copy(zf, rc)
813 rc.Close()
814 if err != nil {
815 t.Fatal(err)
816 }
817 }
818 if err := zw.Close(); err != nil {
819 t.Fatal(err)
820 }
821 if err := tmpZipFile.Close(); err != nil {
822 t.Fatal(err)
823 }
824
825 wantCheckZipErr := test.wantCheckZipErr
826 if wantCheckZipErr == "" {
827 wantCheckZipErr = test.wantErr
828 }
829 cf, err := modzip.CheckZip(sizeLimitVersion, tmpZipPath)
830 if err == nil {
831 err = cf.Err()
832 }
833 if err == nil && wantCheckZipErr != "" {
834 t.Fatalf("CheckZip: unexpected success; want error containing %q", wantCheckZipErr)
835 } else if err != nil && wantCheckZipErr == "" {
836 t.Fatalf("CheckZip: got error %q; want success", err)
837 } else if err != nil && !strings.Contains(err.Error(), wantCheckZipErr) {
838 t.Fatalf("CheckZip: got error %q; want error containing %q", err, wantCheckZipErr)
839 }
840
841 wantUnzipErr := test.wantUnzipErr
842 if wantUnzipErr == "" {
843 wantUnzipErr = test.wantErr
844 }
845 if err := modzip.Unzip(t.TempDir(), sizeLimitVersion, tmpZipPath); err == nil && wantUnzipErr != "" {
846 t.Fatalf("Unzip: unexpected success; want error containing %q", wantUnzipErr)
847 } else if err != nil && wantUnzipErr == "" {
848 t.Fatalf("Unzip: got error %q; want success", err)
849 } else if err != nil && !strings.Contains(err.Error(), wantUnzipErr) {
850 t.Fatalf("Unzip: got error %q; want error containing %q", err, wantUnzipErr)
851 }
852 })
853 }
854 }
855
856 func TestUnzipSizeLimitsSpecial(t *testing.T) {
857 if testing.Short() {
858 t.Skip("skipping test; creating large files takes time")
859 }
860
861 for _, test := range []struct {
862 desc string
863 wantErr1, wantErr2 string
864 m module.Version
865 writeZip func(t *testing.T, zipFile *os.File)
866 }{
867 {
868 desc: "large_zip",
869 m: module.Version{Path: "example.com/m", Version: "v1.0.0"},
870 writeZip: func(t *testing.T, zipFile *os.File) {
871 if err := zipFile.Truncate(modzip.MaxZipFile); err != nil {
872 t.Fatal(err)
873 }
874 },
875
876
877
878 wantErr1: "not a valid zip file",
879 }, {
880 desc: "too_large_zip",
881 m: module.Version{Path: "example.com/m", Version: "v1.0.0"},
882 writeZip: func(t *testing.T, zipFile *os.File) {
883 if err := zipFile.Truncate(modzip.MaxZipFile + 1); err != nil {
884 t.Fatal(err)
885 }
886 },
887 wantErr1: "module zip file is too large",
888 }, {
889 desc: "size_is_a_lie",
890 m: module.Version{Path: "example.com/m", Version: "v1.0.0"},
891 writeZip: func(t *testing.T, zipFile *os.File) {
892
893
894 zipBuf := &bytes.Buffer{}
895 zw := zip.NewWriter(zipBuf)
896 f, err := zw.Create("example.com/m@v1.0.0/go.mod")
897 if err != nil {
898 t.Fatal(err)
899 }
900 realSize := 0x0BAD
901 buf := make([]byte, realSize)
902 if _, err := f.Write(buf); err != nil {
903 t.Fatal(err)
904 }
905 if err := zw.Close(); err != nil {
906 t.Fatal(err)
907 }
908
909
910
911
912
913 zipData := zipBuf.Bytes()
914 realSizeData := []byte{0xAD, 0x0B}
915 fakeSizeData := []byte{0xAC, 0x00}
916 s := zipData
917 n := 0
918 for {
919 if i := bytes.Index(s, realSizeData); i < 0 {
920 break
921 } else {
922 s = s[i:]
923 }
924 copy(s[:len(fakeSizeData)], fakeSizeData)
925 n++
926 }
927 if n != 2 {
928 t.Fatalf("replaced size %d times; expected 2", n)
929 }
930
931
932 if _, err := zipFile.Write(zipData); err != nil {
933 t.Fatal(err)
934 }
935 },
936
937
938 wantErr1: "uncompressed size of file example.com/m@v1.0.0/go.mod is larger than declared size",
939 wantErr2: "not a valid zip file",
940 },
941 } {
942 test := test
943 t.Run(test.desc, func(t *testing.T) {
944 t.Parallel()
945 tmpZipFile, err := os.CreateTemp(t.TempDir(), "TestUnzipSizeLimitsSpecial-*.zip")
946 if err != nil {
947 t.Fatal(err)
948 }
949 tmpZipPath := tmpZipFile.Name()
950 defer tmpZipFile.Close()
951
952 test.writeZip(t, tmpZipFile)
953 if err := tmpZipFile.Close(); err != nil {
954 t.Fatal(err)
955 }
956
957 want := func() string {
958 s := fmt.Sprintf("%q", test.wantErr1)
959 if test.wantErr2 != "" {
960 s = fmt.Sprintf("%q or %q", test.wantErr1, test.wantErr2)
961 }
962 return s
963 }
964
965 if err := modzip.Unzip(t.TempDir(), test.m, tmpZipPath); err == nil && test.wantErr1 != "" {
966 t.Fatalf("unexpected success; want error containing %s", want())
967 } else if err != nil && test.wantErr1 == "" {
968 t.Fatalf("got error %q; want success", err)
969 } else if err != nil && !strings.Contains(err.Error(), test.wantErr1) && (test.wantErr2 == "" || !strings.Contains(err.Error(), test.wantErr2)) {
970 t.Fatalf("got error %q; want error containing %s", err, want())
971 }
972 })
973 }
974 }
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990 func TestVCS(t *testing.T) {
991 if testing.Short() {
992 t.Skip("skipping VCS cloning in -short mode")
993 }
994
995 var downloadErrorCount int32
996 const downloadErrorLimit = 3
997
998 _, gitErr := gitPath()
999 _, hgErr := exec.LookPath("hg")
1000 haveVCS := map[string]bool{
1001 "git": gitErr == nil,
1002 "hg": hgErr == nil,
1003 }
1004
1005 for _, test := range []struct {
1006 m module.Version
1007 vcs, url, subdir, rev string
1008 wantContentHash, wantZipHash string
1009 }{
1010
1011 {
1012 m: module.Version{Path: "rsc.io/quote", Version: "v1.0.0"},
1013 vcs: "git",
1014 url: "https://github.com/rsc/quote",
1015 rev: "v1.0.0",
1016 wantContentHash: "h1:haUSojyo3j2M9g7CEUFG8Na09dtn7QKxvPGaPVQdGwM=",
1017 wantZipHash: "5c08ba2c09a364f93704aaa780e7504346102c6ef4fe1333a11f09904a732078",
1018 },
1019 {
1020 m: module.Version{Path: "rsc.io/quote", Version: "v1.1.0"},
1021 vcs: "git",
1022 url: "https://github.com/rsc/quote",
1023 rev: "v1.1.0",
1024 wantContentHash: "h1:n/ElL9GOlVEwL0mVjzaYj0UxTI/TX9aQ7lR5LHqP/Rw=",
1025 wantZipHash: "730a5ae6e5c4e216e4f84bb93aa9785a85630ad73f96954ebb5f9daa123dcaa9",
1026 },
1027 {
1028 m: module.Version{Path: "rsc.io/quote", Version: "v1.2.0"},
1029 vcs: "git",
1030 url: "https://github.com/rsc/quote",
1031 rev: "v1.2.0",
1032 wantContentHash: "h1:fFMCNi0A97hfNrtUZVQKETbuc3h7bmfFQHnjutpPYCg=",
1033 wantZipHash: "fe1bd62652e9737a30d6b7fd396ea13e54ad13fb05f295669eb63d6d33290b06",
1034 },
1035 {
1036 m: module.Version{Path: "rsc.io/quote", Version: "v1.2.1"},
1037 vcs: "git",
1038 url: "https://github.com/rsc/quote",
1039 rev: "v1.2.1",
1040 wantContentHash: "h1:l+HtgC05eds8qgXNApuv6g1oK1q3B144BM5li1akqXY=",
1041 wantZipHash: "9f0e74de55a6bd20c1567a81e707814dc221f07df176af2a0270392c6faf32fd",
1042 },
1043 {
1044 m: module.Version{Path: "rsc.io/quote", Version: "v1.3.0"},
1045 vcs: "git",
1046 url: "https://github.com/rsc/quote",
1047 rev: "v1.3.0",
1048 wantContentHash: "h1:aPUoHx/0Cd7BTZs4SAaknT4TaKryH766GcFTvJjVbHU=",
1049 wantZipHash: "03872ee7d6747bc2ee0abadbd4eb09e60f6df17d0a6142264abe8a8a00af50e7",
1050 },
1051 {
1052 m: module.Version{Path: "rsc.io/quote", Version: "v1.4.0"},
1053 vcs: "git",
1054 url: "https://github.com/rsc/quote",
1055 rev: "v1.4.0",
1056 wantContentHash: "h1:tYuJspOzwTRMUOX6qmSDRTEKFVV80GM0/l89OLZuVNg=",
1057 wantZipHash: "f60be8193c607bf197da01da4bedb3d683fe84c30de61040eb5d7afaf7869f2e",
1058 },
1059 {
1060 m: module.Version{Path: "rsc.io/quote", Version: "v1.5.0"},
1061 vcs: "git",
1062 url: "https://github.com/rsc/quote",
1063 rev: "v1.5.0",
1064 wantContentHash: "h1:mVjf/WMWxfIw299sOl/O3EXn5qEaaJPMDHMsv7DBDlw=",
1065 wantZipHash: "a2d281834ce159703540da94425fa02c7aec73b88b560081ed0d3681bfe9cd1f",
1066 },
1067 {
1068 m: module.Version{Path: "rsc.io/quote", Version: "v1.5.1"},
1069 vcs: "git",
1070 url: "https://github.com/rsc/quote",
1071 rev: "v1.5.1",
1072 wantContentHash: "h1:ptSemFtffEBvMed43o25vSUpcTVcqxfXU8Jv0sfFVJs=",
1073 wantZipHash: "4ecd78a6d9f571e84ed2baac1688fd150400db2c5b017b496c971af30aaece02",
1074 },
1075 {
1076 m: module.Version{Path: "rsc.io/quote", Version: "v1.5.2"},
1077 vcs: "git",
1078 url: "https://github.com/rsc/quote",
1079 rev: "v1.5.2",
1080 wantContentHash: "h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=",
1081 wantZipHash: "643fcf8ef4e4cbb8f910622c42df3f9a81f3efe8b158a05825a81622c121ca0a",
1082 },
1083 {
1084 m: module.Version{Path: "rsc.io/quote", Version: "v1.5.3-pre1"},
1085 vcs: "git",
1086 url: "https://github.com/rsc/quote",
1087 rev: "v1.5.3-pre1",
1088 wantContentHash: "h1:c3EJ21kn75/hyrOL/Dvj45+ifxGFSY8Wf4WBcoWTxF0=",
1089 wantZipHash: "24106f0f15384949df51fae5d34191bf120c3b80c1c904721ca2872cf83126b2",
1090 },
1091 {
1092 m: module.Version{Path: "rsc.io/quote/v2", Version: "v2.0.1"},
1093 vcs: "git",
1094 url: "https://github.com/rsc/quote",
1095 rev: "v2.0.1",
1096 wantContentHash: "h1:DF8hmGbDhgiIa2tpqLjHLIKkJx6WjCtLEqZBAU+hACI=",
1097 wantZipHash: "009ed42474a59526fe56a14a9dd02bd7f977d1bd3844398bd209d0da0484aade",
1098 },
1099 {
1100 m: module.Version{Path: "rsc.io/quote/v3", Version: "v3.0.0"},
1101 vcs: "git",
1102 url: "https://github.com/rsc/quote",
1103 rev: "v3.0.0",
1104 subdir: "v3",
1105 wantContentHash: "h1:OEIXClZHFMyx5FdatYfxxpNEvxTqHlu5PNdla+vSYGg=",
1106 wantZipHash: "cf3ff89056b785d7b3ef3a10e984efd83b47d9e65eabe8098b927b3370d5c3eb",
1107 },
1108
1109
1110 {
1111 m: module.Version{Path: "vcs-test.golang.org/git/v3pkg.git/v3", Version: "v3.0.0"},
1112 vcs: "git",
1113 url: "https://vcs-test.golang.org/git/v3pkg",
1114 rev: "v3.0.0",
1115 wantContentHash: "h1:mZhljS1BaiW8lODR6wqY5pDxbhXja04rWPFXPwRAtvA=",
1116 wantZipHash: "9c65f0d235e531008dc04e977f6fa5d678febc68679bb63d4148dadb91d3fe57",
1117 },
1118 {
1119 m: module.Version{Path: "vcs-test.golang.org/go/custom-hg-hello", Version: "v0.0.0-20171010233936-a8c8e7a40da9"},
1120 vcs: "hg",
1121 url: "https://vcs-test.golang.org/hg/custom-hg-hello",
1122 rev: "a8c8e7a40da9",
1123 wantContentHash: "h1:LU6jFCbwn5VVgTcj+y4LspOpJHLZvl5TGPE+LwwpMw4=",
1124 wantZipHash: "a1b12047da979d618c639ee98f370767a13d0507bd77785dc2f8dad66b40e2e6",
1125 },
1126
1127
1128 {
1129 m: module.Version{Path: "golang.org/x/arch", Version: "v0.0.0-20190927153633-4e8777c89be4"},
1130 vcs: "git",
1131 url: "https://go.googlesource.com/arch",
1132 rev: "4e8777c89be4d9e61691fbe5d4e6c8838a7806f3",
1133 wantContentHash: "h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI=",
1134 wantZipHash: "d17551a0c4957180ec1507065d13dcdd0f5cd8bfd7dd735fb81f64f3e2b31b68",
1135 },
1136 {
1137 m: module.Version{Path: "golang.org/x/blog", Version: "v0.0.0-20191017104857-0cd0cdff05c2"},
1138 vcs: "git",
1139 url: "https://go.googlesource.com/blog",
1140 rev: "0cd0cdff05c251ad0c796cc94d7059e013311fc6",
1141 wantContentHash: "h1:IKGICrORhR1aH2xG/WqrnpggSNolSj5urQxggCfmj28=",
1142 wantZipHash: "0fed6b400de54da34b52b464ef2cdff45167236aaaf9a99ba8eba8855036faff",
1143 },
1144 {
1145 m: module.Version{Path: "golang.org/x/crypto", Version: "v0.0.0-20191011191535-87dc89f01550"},
1146 vcs: "git",
1147 url: "https://go.googlesource.com/crypto",
1148 rev: "87dc89f01550277dc22b74ffcf4cd89fa2f40f4c",
1149 wantContentHash: "h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=",
1150 wantZipHash: "88e47aa05eb25c6abdad7387ccccfc39e74541896d87b7b1269e9dd2fa00100d",
1151 },
1152 {
1153 m: module.Version{Path: "golang.org/x/net", Version: "v0.0.0-20191014212845-da9a3fd4c582"},
1154 vcs: "git",
1155 url: "https://go.googlesource.com/net",
1156 rev: "da9a3fd4c5820e74b24a6cb7fb438dc9b0dd377c",
1157 wantContentHash: "h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA=",
1158 wantZipHash: "34901a85e6c15475a40457c2393ce66fb0999accaf2d6aa5b64b4863751ddbde",
1159 },
1160 {
1161 m: module.Version{Path: "golang.org/x/sync", Version: "v0.0.0-20190911185100-cd5d95a43a6e"},
1162 vcs: "git",
1163 url: "https://go.googlesource.com/sync",
1164 rev: "cd5d95a43a6e21273425c7ae415d3df9ea832eeb",
1165 wantContentHash: "h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=",
1166 wantZipHash: "9c63fe51b0c533b258d3acc30d9319fe78679ce1a051109c9dea3105b93e2eef",
1167 },
1168 {
1169 m: module.Version{Path: "golang.org/x/sys", Version: "v0.0.0-20191010194322-b09406accb47"},
1170 vcs: "git",
1171 url: "https://go.googlesource.com/sys",
1172 rev: "b09406accb4736d857a32bf9444cd7edae2ffa79",
1173 wantContentHash: "h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=",
1174 wantZipHash: "f26f2993757670b4d1fee3156d331513259757f17133a36966c158642c3f61df",
1175 },
1176 {
1177 m: module.Version{Path: "golang.org/x/talks", Version: "v0.0.0-20191010201600-067e0d331fee"},
1178 vcs: "git",
1179 url: "https://go.googlesource.com/talks",
1180 rev: "067e0d331feee4f8d0fa17d47444db533bd904e7",
1181 wantContentHash: "h1:8fnBMBUwliuiHuzfFw6kSSx79AzQpqkjZi3FSNIoqYs=",
1182 wantZipHash: "fab2129f3005f970dbf2247378edb3220f6bd36726acdc7300ae3bb0f129e2f2",
1183 },
1184 {
1185 m: module.Version{Path: "golang.org/x/tools", Version: "v0.0.0-20191017205301-920acffc3e65"},
1186 vcs: "git",
1187 url: "https://go.googlesource.com/tools",
1188 rev: "920acffc3e65862cb002dae6b227b8d9695e3d29",
1189 wantContentHash: "h1:GwXwgmbrvlcHLDsENMqrQTTIC2C0kIPszsq929NruKI=",
1190 wantZipHash: "7f0ab7466448190f8ad1b8cfb05787c3fb08f4a8f9953cd4b40a51c76ddebb28",
1191 },
1192 {
1193 m: module.Version{Path: "golang.org/x/tour", Version: "v0.0.0-20191002171047-6bb846ce41cd"},
1194 vcs: "git",
1195 url: "https://go.googlesource.com/tour",
1196 rev: "6bb846ce41cdca087b14c8e3560a679691c424b6",
1197 wantContentHash: "h1:EUlK3Rq8iTkQERnCnveD654NvRJ/ZCM9XCDne+S5cJ8=",
1198 wantZipHash: "d6a7e03e02e5f7714bd12653d319a3b0f6e1099c01b1f9a17bc3613fb31c9170",
1199 },
1200 } {
1201 test := test
1202 testName := strings.ReplaceAll(test.m.String(), "/", "_")
1203 t.Run(testName, func(t *testing.T) {
1204 if have, ok := haveVCS[test.vcs]; !ok {
1205 t.Fatalf("unknown vcs: %s", test.vcs)
1206 } else if !have {
1207 t.Skipf("no %s executable in path", test.vcs)
1208 }
1209 t.Parallel()
1210
1211 repo, dl, err := downloadVCSZip(t, test.vcs, test.url, test.rev, test.subdir)
1212 if err != nil {
1213
1214
1215
1216
1217
1218
1219 n := atomic.AddInt32(&downloadErrorCount, 1)
1220 if n < downloadErrorLimit {
1221 t.Skipf("failed to download zip from repository: %v", err)
1222 } else {
1223 t.Fatalf("failed to download zip from repository (repeated failure): %v", err)
1224 }
1225 }
1226
1227
1228
1229 info, err := dl.Stat()
1230 if err != nil {
1231 t.Fatal(err)
1232 }
1233 zr, err := zip.NewReader(dl, info.Size())
1234 if err != nil {
1235 t.Fatal(err)
1236 }
1237
1238 var files []modzip.File
1239 topPrefix := ""
1240 subdir := test.subdir
1241 if subdir != "" && !strings.HasSuffix(subdir, "/") {
1242 subdir += "/"
1243 }
1244 haveLICENSE := false
1245 for _, f := range zr.File {
1246 if !f.FileInfo().Mode().IsRegular() {
1247 continue
1248 }
1249 if topPrefix == "" {
1250 i := strings.Index(f.Name, "/")
1251 if i < 0 {
1252 t.Fatal("missing top-level directory prefix")
1253 }
1254 topPrefix = f.Name[:i+1]
1255 }
1256 if strings.HasSuffix(f.Name, "/") {
1257 continue
1258 }
1259 if !strings.HasPrefix(f.Name, topPrefix) {
1260 t.Fatal("zip file contains more than one top-level directory")
1261 }
1262 name := strings.TrimPrefix(f.Name, topPrefix)
1263 if !strings.HasPrefix(name, subdir) {
1264 continue
1265 }
1266 name = strings.TrimPrefix(name, subdir)
1267 if name == ".hg_archival.txt" {
1268
1269
1270 continue
1271 }
1272 if name == "LICENSE" {
1273 haveLICENSE = true
1274 }
1275 files = append(files, zipFile{name: name, f: f})
1276 }
1277 if !haveLICENSE && subdir != "" {
1278 license, err := downloadVCSFile(t, test.vcs, repo, test.rev, "LICENSE")
1279 if err != nil {
1280 t.Fatal(err)
1281 }
1282 files = append(files, fakeFile{
1283 name: "LICENSE",
1284 size: uint64(len(license)),
1285 data: license,
1286 })
1287 }
1288
1289 tmpModZipFile, err := os.CreateTemp(t.TempDir(), "TestVCS-*.zip")
1290 if err != nil {
1291 t.Fatal(err)
1292 }
1293 tmpModZipPath := tmpModZipFile.Name()
1294 defer tmpModZipFile.Close()
1295 h := sha256.New()
1296 w := io.MultiWriter(tmpModZipFile, h)
1297 if err := modzip.Create(w, test.m, files); err != nil {
1298 t.Fatal(err)
1299 }
1300 if err := tmpModZipFile.Close(); err != nil {
1301 t.Fatal(err)
1302 }
1303
1304 gotZipHash := hex.EncodeToString(h.Sum(nil))
1305 if test.wantZipHash != gotZipHash {
1306
1307
1308
1309
1310
1311
1312 if gotSum, err := dirhash.HashZip(tmpModZipPath, dirhash.Hash1); err == nil && test.wantContentHash != gotSum {
1313 t.Fatalf("zip content hash: got %s, want %s", gotSum, test.wantContentHash)
1314 } else {
1315 t.Fatalf("zip file hash: got %s, want %s", gotZipHash, test.wantZipHash)
1316 }
1317 }
1318 })
1319 }
1320 }
1321
1322 func downloadVCSZip(t testing.TB, vcs, url, rev, subdir string) (repoDir string, dl *os.File, err error) {
1323 repoDir = t.TempDir()
1324
1325 switch vcs {
1326 case "git":
1327
1328 if _, err := run(t, repoDir, "git", "init", "--bare"); err != nil {
1329 return "", nil, err
1330 }
1331 if err := os.MkdirAll(filepath.Join(repoDir, "info"), 0777); err != nil {
1332 return "", nil, err
1333 }
1334 attrFile, err := os.OpenFile(filepath.Join(repoDir, "info", "attributes"), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
1335 if err != nil {
1336 return "", nil, err
1337 }
1338 if _, err := attrFile.Write([]byte("\n* -export-subst -export-ignore\n")); err != nil {
1339 attrFile.Close()
1340 return "", nil, err
1341 }
1342 if err := attrFile.Close(); err != nil {
1343 return "", nil, err
1344 }
1345 if _, err := run(t, repoDir, "git", "remote", "add", "origin", "--", url); err != nil {
1346 return "", nil, err
1347 }
1348 var refSpec string
1349 if strings.HasPrefix(rev, "v") {
1350 refSpec = fmt.Sprintf("refs/tags/%[1]s:refs/tags/%[1]s", rev)
1351 } else {
1352 refSpec = fmt.Sprintf("%s:refs/dummy", rev)
1353 }
1354 if _, err := run(t, repoDir, "git", "fetch", "-f", "--depth=1", "origin", refSpec); err != nil {
1355 return "", nil, err
1356 }
1357
1358
1359 tmpZipFile, err := os.CreateTemp(t.TempDir(), "downloadVCSZip-*.zip")
1360 if err != nil {
1361 return "", nil, err
1362 }
1363 t.Cleanup(func() { tmpZipFile.Close() })
1364 subdirArg := subdir
1365 if subdir == "" {
1366 subdirArg = "."
1367 }
1368
1369 cmd := exec.Command("git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", rev, "--", subdirArg)
1370 cmd.Dir = repoDir
1371 cmd.Stdout = tmpZipFile
1372 stderr := new(strings.Builder)
1373 cmd.Stderr = stderr
1374
1375 err = cmd.Run()
1376 if stderr.Len() > 0 && (err != nil || testing.Verbose()) {
1377 t.Logf("%v: %v\n%s", err, cmd, stderr)
1378 } else if err != nil {
1379 t.Logf("%v: %v", err, cmd)
1380 } else {
1381 t.Logf("%v", cmd)
1382 }
1383 if err != nil {
1384 return "", nil, err
1385 }
1386
1387 if _, err := tmpZipFile.Seek(0, 0); err != nil {
1388 return "", nil, err
1389 }
1390 return repoDir, tmpZipFile, nil
1391
1392 case "hg":
1393
1394 if _, err := run(t, repoDir, "hg", "clone", "-U", "--", url, "."); err != nil {
1395 return "", nil, err
1396 }
1397
1398
1399 tmpZipFile, err := os.CreateTemp(t.TempDir(), "downloadVCSZip-*.zip")
1400 if err != nil {
1401 return "", nil, err
1402 }
1403 tmpZipPath := tmpZipFile.Name()
1404 tmpZipFile.Close()
1405 args := []string{"archive", "-t", "zip", "--no-decode", "-r", rev, "--prefix=prefix/"}
1406 if subdir != "" {
1407 args = append(args, "-I", subdir+"/**")
1408 }
1409 args = append(args, "--", tmpZipPath)
1410 if _, err := run(t, repoDir, "hg", args...); err != nil {
1411 return "", nil, err
1412 }
1413 if tmpZipFile, err = os.Open(tmpZipPath); err != nil {
1414 return "", nil, err
1415 }
1416 t.Cleanup(func() { tmpZipFile.Close() })
1417 return repoDir, tmpZipFile, err
1418
1419 default:
1420 return "", nil, fmt.Errorf("vcs %q not supported", vcs)
1421 }
1422 }
1423
1424 func downloadVCSFile(t testing.TB, vcs, repo, rev, file string) ([]byte, error) {
1425 t.Helper()
1426 switch vcs {
1427 case "git":
1428 return run(t, repo, "git", "cat-file", "blob", rev+":"+file)
1429 default:
1430 return nil, fmt.Errorf("vcs %q not supported", vcs)
1431 }
1432 }
1433
1434 func run(t testing.TB, dir string, name string, args ...string) ([]byte, error) {
1435 t.Helper()
1436
1437 cmd := exec.Command(name, args...)
1438 cmd.Dir = dir
1439 stderr := new(strings.Builder)
1440 cmd.Stderr = stderr
1441
1442 out, err := cmd.Output()
1443 if stderr.Len() > 0 && (err != nil || testing.Verbose()) {
1444 t.Logf("%v: %v\n%s", err, cmd, stderr)
1445 } else if err != nil {
1446 t.Logf("%v: %v", err, cmd)
1447 } else {
1448 t.Logf("%v", cmd)
1449 }
1450 return out, err
1451 }
1452
1453 type zipFile struct {
1454 name string
1455 f *zip.File
1456 }
1457
1458 func (f zipFile) Path() string { return f.name }
1459 func (f zipFile) Lstat() (os.FileInfo, error) { return f.f.FileInfo(), nil }
1460 func (f zipFile) Open() (io.ReadCloser, error) { return f.f.Open() }
1461
1462 func TestCreateFromVCS_basic(t *testing.T) {
1463 mustHaveGit(t)
1464
1465
1466 tmpDir, err := extractTxtarToTempDir(t, txtar.Parse([]byte(`-- go.mod --
1467 module example.com/foo/bar
1468
1469 go 1.12
1470 -- LICENSE --
1471 root license
1472 -- a.go --
1473 package a
1474
1475 var A = 5
1476 -- b.go --
1477 package a
1478
1479 var B = 5
1480 -- c/c.go --
1481 package c
1482
1483 var C = 5
1484 -- d/d.go --
1485 package c
1486
1487 var D = 5
1488 -- e/LICENSE --
1489 e license
1490 -- e/e.go --
1491 package e
1492
1493 var E = 5
1494 -- f/go.mod --
1495 module example.com/foo/bar/f
1496
1497 go 1.12
1498 -- f/f.go --
1499 package f
1500
1501 var F = 5
1502 -- .gitignore --
1503 b.go
1504 c/`)))
1505 if err != nil {
1506 t.Fatal(err)
1507 }
1508
1509 gitInit(t, tmpDir)
1510 gitCommit(t, tmpDir)
1511
1512 for _, tc := range []struct {
1513 desc string
1514 version module.Version
1515 subdir string
1516 wantFiles []string
1517 wantData map[string]string
1518 }{
1519 {
1520 desc: "from root",
1521 version: module.Version{Path: "example.com/foo/bar", Version: "v0.0.1"},
1522 subdir: "",
1523 wantFiles: []string{"go.mod", "LICENSE", "a.go", "d/d.go", "e/LICENSE", "e/e.go", ".gitignore"},
1524 wantData: map[string]string{"LICENSE": "root license\n"},
1525 },
1526 {
1527 desc: "from subdir",
1528 version: module.Version{Path: "example.com/foo/bar", Version: "v0.0.1"},
1529 subdir: "d/",
1530
1531
1532 wantFiles: []string{"d.go", "LICENSE"},
1533 wantData: map[string]string{"LICENSE": "root license\n"},
1534 },
1535 {
1536 desc: "from subdir with license",
1537 version: module.Version{Path: "example.com/foo/bar", Version: "v0.0.1"},
1538 subdir: "e/",
1539
1540
1541 wantFiles: []string{"LICENSE", "e.go"},
1542 wantData: map[string]string{"LICENSE": "e license\n"},
1543 },
1544 {
1545 desc: "from submodule subdir",
1546 version: module.Version{Path: "example.com/foo/bar/f", Version: "v0.0.1"},
1547 subdir: "f/",
1548
1549
1550 wantFiles: []string{"go.mod", "f.go", "LICENSE"},
1551 wantData: map[string]string{"LICENSE": "root license\n"},
1552 },
1553 } {
1554 t.Run(tc.desc, func(t *testing.T) {
1555
1556 tmpZip := &bytes.Buffer{}
1557
1558 if err := modzip.CreateFromVCS(tmpZip, tc.version, tmpDir, "HEAD", tc.subdir); err != nil {
1559 t.Fatal(err)
1560 }
1561
1562 wantData := map[string]string{}
1563 for f, data := range tc.wantData {
1564 p := path.Join(tc.version.String(), f)
1565 wantData[p] = data
1566 }
1567
1568 readerAt := bytes.NewReader(tmpZip.Bytes())
1569 r, err := zip.NewReader(readerAt, int64(tmpZip.Len()))
1570 if err != nil {
1571 t.Fatal(err)
1572 }
1573 var gotFiles []string
1574 gotMap := map[string]bool{}
1575 for _, f := range r.File {
1576 gotMap[f.Name] = true
1577 gotFiles = append(gotFiles, f.Name)
1578
1579 if want, ok := wantData[f.Name]; ok {
1580 r, err := f.Open()
1581 if err != nil {
1582 t.Errorf("CreatedFromVCS: error opening %s: %v", f.Name, err)
1583 continue
1584 }
1585 defer r.Close()
1586 got, err := io.ReadAll(r)
1587 if err != nil {
1588 t.Errorf("CreatedFromVCS: error reading %s: %v", f.Name, err)
1589 continue
1590 }
1591 if want != string(got) {
1592 t.Errorf("CreatedFromVCS: zipped file %s contains %s, expected %s", f.Name, string(got), want)
1593 continue
1594 }
1595 }
1596 }
1597 wantMap := map[string]bool{}
1598 for _, f := range tc.wantFiles {
1599 p := path.Join(tc.version.String(), f)
1600 wantMap[p] = true
1601 }
1602
1603
1604 for f := range gotMap {
1605 if !wantMap[f] {
1606 t.Errorf("CreatedFromVCS: zipped file contains %s, but expected it not to", f)
1607 }
1608 }
1609
1610
1611 for f := range wantMap {
1612 if !gotMap[f] {
1613 t.Errorf("CreatedFromVCS: zipped file doesn't contain %s, but expected it to. all files: %v", f, gotFiles)
1614 }
1615 }
1616 for f := range wantData {
1617 if !gotMap[f] {
1618 t.Errorf("CreatedFromVCS: zipped file doesn't contain %s, but expected it to. all files: %v", f, gotFiles)
1619 }
1620 }
1621 })
1622 }
1623 }
1624
1625
1626 func TestCreateFromVCS_v2(t *testing.T) {
1627 mustHaveGit(t)
1628
1629
1630 tmpDir, err := extractTxtarToTempDir(t, txtar.Parse([]byte(`-- go.mod --
1631 module example.com/foo/bar
1632
1633 go 1.12
1634 -- a.go --
1635 package a
1636
1637 var A = 5
1638 -- b.go --
1639 package a
1640
1641 var B = 5
1642 -- go.mod --
1643 module example.com/foo/bar
1644
1645 go 1.12
1646 -- gaz/v2/a_2.go --
1647 package a
1648
1649 var C = 9
1650 -- gaz/v2/b_2.go --
1651 package a
1652
1653 var B = 11
1654 -- gaz/v2/go.mod --
1655 module example.com/foo/bar/v2
1656
1657 go 1.12
1658 -- .gitignore --
1659 `)))
1660 if err != nil {
1661 t.Fatal(err)
1662 }
1663
1664 gitInit(t, tmpDir)
1665 gitCommit(t, tmpDir)
1666
1667
1668 tmpZip := &bytes.Buffer{}
1669
1670 m := module.Version{Path: "example.com/foo/bar/v2", Version: "v2.0.0"}
1671
1672 if err := modzip.CreateFromVCS(tmpZip, m, tmpDir, "HEAD", "gaz/v2"); err != nil {
1673 t.Fatal(err)
1674 }
1675
1676 readerAt := bytes.NewReader(tmpZip.Bytes())
1677 r, err := zip.NewReader(readerAt, int64(tmpZip.Len()))
1678 if err != nil {
1679 t.Fatal(err)
1680 }
1681 var gotFiles []string
1682 gotMap := map[string]bool{}
1683 for _, f := range r.File {
1684 gotMap[f.Name] = true
1685 gotFiles = append(gotFiles, f.Name)
1686 }
1687 wantMap := map[string]bool{
1688 "example.com/foo/bar/v2@v2.0.0/a_2.go": true,
1689 "example.com/foo/bar/v2@v2.0.0/b_2.go": true,
1690 "example.com/foo/bar/v2@v2.0.0/go.mod": true,
1691 }
1692
1693
1694 for f := range gotMap {
1695 if !wantMap[f] {
1696 t.Errorf("CreatedFromVCS: zipped file contains %s, but expected it not to", f)
1697 }
1698 }
1699
1700
1701 for f := range wantMap {
1702 if !gotMap[f] {
1703 t.Errorf("CreatedFromVCS: zipped file doesn't contain %s, but expected it to. all files: %v", f, gotFiles)
1704 }
1705 }
1706 }
1707
1708 func TestCreateFromVCS_nonGitDir(t *testing.T) {
1709 mustHaveGit(t)
1710
1711
1712 tmpDir, err := extractTxtarToTempDir(t, txtar.Parse([]byte(`-- go.mod --
1713 module example.com/foo/bar
1714
1715 go 1.12
1716 -- a.go --
1717 package a
1718
1719 var A = 5
1720 `)))
1721 if err != nil {
1722 t.Fatal(err)
1723 }
1724
1725
1726 tmpZip, err := os.CreateTemp(t.TempDir(), "TestCreateFromDir-*.zip")
1727 if err != nil {
1728 t.Fatal(err)
1729 }
1730 defer tmpZip.Close()
1731
1732 m := module.Version{Path: "example.com/foo/bar", Version: "v0.0.1"}
1733
1734 err = modzip.CreateFromVCS(tmpZip, m, tmpDir, "HEAD", "")
1735 if err == nil {
1736 t.Fatal("CreateFromVCS: expected error, got nil")
1737 }
1738 var gotErr *modzip.UnrecognizedVCSError
1739 if !errors.As(err, &gotErr) {
1740 t.Errorf("CreateFromVCS: returned error does not unwrap to modzip.UnrecognizedVCSError, but expected it to. returned error: %v", err)
1741 } else if gotErr.RepoRoot != tmpDir {
1742 t.Errorf("CreateFromVCS: returned error has RepoRoot %q, but want %q. returned error: %v", gotErr.RepoRoot, tmpDir, err)
1743 }
1744 }
1745
1746 func TestCreateFromVCS_zeroCommitsGitDir(t *testing.T) {
1747 mustHaveGit(t)
1748
1749
1750 tmpDir, err := extractTxtarToTempDir(t, txtar.Parse([]byte(`-- go.mod --
1751 module example.com/foo/bar
1752
1753 go 1.12
1754 -- a.go --
1755 package a
1756
1757 var A = 5
1758 `)))
1759 if err != nil {
1760 t.Fatal(err)
1761 }
1762
1763 gitInit(t, tmpDir)
1764
1765
1766 tmpZip, err := os.CreateTemp(t.TempDir(), "TestCreateFromDir-*.zip")
1767 if err != nil {
1768 t.Fatal(err)
1769 }
1770 defer tmpZip.Close()
1771
1772 m := module.Version{Path: "example.com/foo/bar", Version: "v0.0.1"}
1773
1774 if err := modzip.CreateFromVCS(tmpZip, m, tmpDir, "HEAD", ""); err == nil {
1775 t.Error("CreateFromVCS: expected error, got nil")
1776 }
1777 }
1778
1779
1780
1781
1782
1783 func gitInit(t testing.TB, dir string) {
1784 t.Helper()
1785 mustHaveGit(t)
1786
1787 if _, err := run(t, dir, "git", "init"); err != nil {
1788 t.Fatal(err)
1789 }
1790 if _, err := run(t, dir, "git", "config", "user.name", "Go Gopher"); err != nil {
1791 t.Fatal(err)
1792 }
1793 if _, err := run(t, dir, "git", "config", "user.email", "gopher@golang.org"); err != nil {
1794 t.Fatal(err)
1795 }
1796 }
1797
1798 func gitCommit(t testing.TB, dir string) {
1799 t.Helper()
1800 mustHaveGit(t)
1801
1802 if _, err := run(t, dir, "git", "add", "-A"); err != nil {
1803 t.Fatal(err)
1804 }
1805 if _, err := run(t, dir, "git", "commit", "-m", "some commit"); err != nil {
1806 t.Fatal(err)
1807 }
1808 }
1809
View as plain text