1 // Copyright 2019 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
25 "golang.org/x/mod/module"
26 "golang.org/x/mod/sumdb/dirhash"
27 modzip "golang.org/x/mod/zip"
28 "golang.org/x/tools/txtar"
31 const emptyHash = "h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
33 type testParams struct {
34 path, version, wantErr, hash string
35 archive *txtar.Archive
38 // readTest loads a test from a txtar file. The comment section of the file
39 // should contain lines with key=value pairs. Valid keys are the field names
41 func readTest(file string) (testParams, error) {
44 test.archive, err = txtar.ParseFile(file)
46 return testParams{}, err
49 lines := strings.Split(string(test.archive.Comment), "\n")
50 for n, line := range lines {
51 n++ // report line numbers starting with 1
52 if i := strings.IndexByte(line, '#'); i >= 0 {
55 line = strings.TrimSpace(line)
59 eq := strings.IndexByte(line, '=')
61 return testParams{}, fmt.Errorf("%s:%d: missing = separator", file, n)
63 key, value := strings.TrimSpace(line[:eq]), strings.TrimSpace(line[eq+1:])
74 return testParams{}, fmt.Errorf("%s:%d: unknown key %q", file, n, key)
81 func extractTxtarToTempDir(arc *txtar.Archive) (dir string, err error) {
82 dir, err = ioutil.TempDir("", "zip_test-*")
91 for _, f := range arc.Files {
92 filePath := filepath.Join(dir, f.Name)
93 if err := os.MkdirAll(filepath.Dir(filePath), 0777); err != nil {
96 if err := ioutil.WriteFile(filePath, f.Data, 0666); err != nil {
103 func extractTxtarToTempZip(arc *txtar.Archive) (zipPath string, err error) {
104 zipFile, err := ioutil.TempFile("", "zip_test-*.zip")
109 if cerr := zipFile.Close(); err == nil && cerr != nil {
113 os.Remove(zipFile.Name())
116 zw := zip.NewWriter(zipFile)
117 for _, f := range arc.Files {
118 zf, err := zw.Create(f.Name)
122 if _, err := zf.Write(f.Data); err != nil {
126 if err := zw.Close(); err != nil {
129 return zipFile.Name(), nil
132 type fakeFile struct {
135 data []byte // if nil, Open will access a sequence of 0-bytes
138 func (f fakeFile) Path() string { return f.name }
139 func (f fakeFile) Lstat() (os.FileInfo, error) { return fakeFileInfo{f}, nil }
140 func (f fakeFile) Open() (io.ReadCloser, error) {
142 return ioutil.NopCloser(bytes.NewReader(f.data)), nil
144 if f.size >= uint64(modzip.MaxZipFile<<1) {
145 return nil, fmt.Errorf("cannot open fakeFile of size %d", f.size)
147 return ioutil.NopCloser(io.LimitReader(zeroReader{}, int64(f.size))), nil
150 type fakeFileInfo struct {
154 func (fi fakeFileInfo) Name() string { return path.Base(fi.f.name) }
155 func (fi fakeFileInfo) Size() int64 { return int64(fi.f.size) }
156 func (fi fakeFileInfo) Mode() os.FileMode { return 0644 }
157 func (fi fakeFileInfo) ModTime() time.Time { return time.Time{} }
158 func (fi fakeFileInfo) IsDir() bool { return false }
159 func (fi fakeFileInfo) Sys() interface{} { return nil }
161 type zeroReader struct{}
163 func (r zeroReader) Read(b []byte) (int, error) {
170 func formatCheckedFiles(cf modzip.CheckedFiles) string {
171 buf := &bytes.Buffer{}
172 fmt.Fprintf(buf, "valid:\n")
173 for _, f := range cf.Valid {
176 fmt.Fprintf(buf, "\nomitted:\n")
177 for _, f := range cf.Omitted {
178 fmt.Fprintf(buf, "%s: %v\n", f.Path, f.Err)
180 fmt.Fprintf(buf, "\ninvalid:\n")
181 for _, f := range cf.Invalid {
182 fmt.Fprintf(buf, "%s: %v\n", f.Path, f.Err)
187 // TestCheckFiles verifies behavior of CheckFiles. Note that CheckFiles is also
188 // covered by TestCreate, TestCreateDir, and TestCreateSizeLimits, so this test
189 // focuses on how multiple errors and omissions are reported, rather than trying
190 // to cover every case.
191 func TestCheckFiles(t *testing.T) {
192 testPaths, err := filepath.Glob(filepath.FromSlash("testdata/check_files/*.txt"))
196 for _, testPath := range testPaths {
198 name := strings.TrimSuffix(filepath.Base(testPath), ".txt")
199 t.Run(name, func(t *testing.T) {
203 test, err := readTest(testPath)
207 files := make([]modzip.File, 0, len(test.archive.Files))
209 for _, tf := range test.archive.Files {
210 if tf.Name == "want" {
211 want = string(tf.Data)
214 files = append(files, fakeFile{
216 size: uint64(len(tf.Data)),
222 cf, _ := modzip.CheckFiles(files)
223 got := formatCheckedFiles(cf)
225 t.Errorf("got:\n%s\n\nwant:\n%s", got, want)
228 // Check that the error (if any) is just a list of invalid files.
229 // SizeError is not covered in this test.
230 var gotErr, wantErr string
231 if len(cf.Invalid) > 0 {
232 wantErr = modzip.FileErrorList(cf.Invalid).Error()
234 if err := cf.Err(); err != nil {
237 if gotErr != wantErr {
238 t.Errorf("got error:\n%s\n\nwant error:\n%s", gotErr, wantErr)
244 // TestCheckDir verifies behavior of the CheckDir function. Note that CheckDir
245 // relies on CheckFiles and listFilesInDir (called by CreateFromDir), so this
246 // test focuses on how multiple errors and omissions are reported, rather than
247 // trying to cover every case.
248 func TestCheckDir(t *testing.T) {
249 testPaths, err := filepath.Glob(filepath.FromSlash("testdata/check_dir/*.txt"))
253 for _, testPath := range testPaths {
255 name := strings.TrimSuffix(filepath.Base(testPath), ".txt")
256 t.Run(name, func(t *testing.T) {
259 // Load the test and extract the files to a temporary directory.
260 test, err := readTest(testPath)
265 for i, f := range test.archive.Files {
266 if f.Name == "want" {
267 want = string(f.Data)
268 test.archive.Files = append(test.archive.Files[:i], test.archive.Files[i+1:]...)
272 tmpDir, err := extractTxtarToTempDir(test.archive)
277 if err := os.RemoveAll(tmpDir); err != nil {
278 t.Errorf("removing temp directory: %v", err)
282 // Check the directory.
283 cf, err := modzip.CheckDir(tmpDir)
284 if err != nil && err.Error() != cf.Err().Error() {
288 rep := strings.NewReplacer(tmpDir, "$work", `'\''`, `'\''`, string(os.PathSeparator), "/")
289 got := rep.Replace(formatCheckedFiles(cf))
291 t.Errorf("got:\n%s\n\nwant:\n%s", got, want)
294 // Check that the error (if any) is just a list of invalid files.
295 // SizeError is not covered in this test.
296 var gotErr, wantErr string
297 if len(cf.Invalid) > 0 {
298 wantErr = modzip.FileErrorList(cf.Invalid).Error()
300 if err := cf.Err(); err != nil {
303 if gotErr != wantErr {
304 t.Errorf("got error:\n%s\n\nwant error:\n%s", gotErr, wantErr)
310 // TestCheckZip verifies behavior of CheckZip. Note that CheckZip is also
311 // covered by TestUnzip, so this test focuses on how multiple errors are
312 // reported, rather than trying to cover every case.
313 func TestCheckZip(t *testing.T) {
314 testPaths, err := filepath.Glob(filepath.FromSlash("testdata/check_zip/*.txt"))
318 for _, testPath := range testPaths {
320 name := strings.TrimSuffix(filepath.Base(testPath), ".txt")
321 t.Run(name, func(t *testing.T) {
324 // Load the test and extract the files to a temporary zip file.
325 test, err := readTest(testPath)
330 for i, f := range test.archive.Files {
331 if f.Name == "want" {
332 want = string(f.Data)
333 test.archive.Files = append(test.archive.Files[:i], test.archive.Files[i+1:]...)
337 tmpZipPath, err := extractTxtarToTempZip(test.archive)
342 if err := os.Remove(tmpZipPath); err != nil {
343 t.Errorf("removing temp zip file: %v", err)
348 m := module.Version{Path: test.path, Version: test.version}
349 cf, err := modzip.CheckZip(m, tmpZipPath)
350 if err != nil && err.Error() != cf.Err().Error() {
354 got := formatCheckedFiles(cf)
356 t.Errorf("got:\n%s\n\nwant:\n%s", got, want)
359 // Check that the error (if any) is just a list of invalid files.
360 // SizeError is not covered in this test.
361 var gotErr, wantErr string
362 if len(cf.Invalid) > 0 {
363 wantErr = modzip.FileErrorList(cf.Invalid).Error()
365 if err := cf.Err(); err != nil {
368 if gotErr != wantErr {
369 t.Errorf("got error:\n%s\n\nwant error:\n%s", gotErr, wantErr)
375 func TestCreate(t *testing.T) {
376 testDir := filepath.FromSlash("testdata/create")
377 testInfos, err := ioutil.ReadDir(testDir)
381 for _, testInfo := range testInfos {
383 base := filepath.Base(testInfo.Name())
384 if filepath.Ext(base) != ".txt" {
387 t.Run(base[:len(base)-len(".txt")], func(t *testing.T) {
391 testPath := filepath.Join(testDir, testInfo.Name())
392 test, err := readTest(testPath)
397 // Write zip to temporary file.
398 tmpZip, err := ioutil.TempFile("", "TestCreate-*.zip")
402 tmpZipPath := tmpZip.Name()
405 os.Remove(tmpZipPath)
407 m := module.Version{Path: test.path, Version: test.version}
408 files := make([]modzip.File, len(test.archive.Files))
409 for i, tf := range test.archive.Files {
412 size: uint64(len(tf.Data)),
416 if err := modzip.Create(tmpZip, m, files); err != nil {
417 if test.wantErr == "" {
418 t.Fatalf("unexpected error: %v", err)
419 } else if !strings.Contains(err.Error(), test.wantErr) {
420 t.Fatalf("got error %q; want error containing %q", err.Error(), test.wantErr)
424 } else if test.wantErr != "" {
425 t.Fatalf("unexpected success; wanted error containing %q", test.wantErr)
427 if err := tmpZip.Close(); err != nil {
431 // Hash zip file, compare with known value.
432 if hash, err := dirhash.HashZip(tmpZipPath, dirhash.Hash1); err != nil {
434 } else if hash != test.hash {
435 t.Fatalf("got hash: %q\nwant: %q", hash, test.hash)
441 func TestCreateFromDir(t *testing.T) {
442 testDir := filepath.FromSlash("testdata/create_from_dir")
443 testInfos, err := ioutil.ReadDir(testDir)
447 for _, testInfo := range testInfos {
449 base := filepath.Base(testInfo.Name())
450 if filepath.Ext(base) != ".txt" {
453 t.Run(base[:len(base)-len(".txt")], func(t *testing.T) {
457 testPath := filepath.Join(testDir, testInfo.Name())
458 test, err := readTest(testPath)
463 // Write files to a temporary directory.
464 tmpDir, err := extractTxtarToTempDir(test.archive)
469 if err := os.RemoveAll(tmpDir); err != nil {
470 t.Errorf("removing temp directory: %v", err)
474 // Create zip from the directory.
475 tmpZip, err := ioutil.TempFile("", "TestCreateFromDir-*.zip")
479 tmpZipPath := tmpZip.Name()
482 os.Remove(tmpZipPath)
484 m := module.Version{Path: test.path, Version: test.version}
485 if err := modzip.CreateFromDir(tmpZip, m, tmpDir); err != nil {
486 if test.wantErr == "" {
487 t.Fatalf("unexpected error: %v", err)
488 } else if !strings.Contains(err.Error(), test.wantErr) {
489 t.Fatalf("got error %q; want error containing %q", err, test.wantErr)
493 } else if test.wantErr != "" {
494 t.Fatalf("unexpected success; want error containing %q", test.wantErr)
497 // Hash zip file, compare with known value.
498 if hash, err := dirhash.HashZip(tmpZipPath, dirhash.Hash1); err != nil {
500 } else if hash != test.hash {
501 t.Fatalf("got hash: %q\nwant: %q", hash, test.hash)
507 func TestCreateFromDirSpecial(t *testing.T) {
508 for _, test := range []struct {
510 setup func(t *testing.T, tmpDir string) string
514 desc: "ignore_empty_dir",
515 setup: func(t *testing.T, tmpDir string) string {
516 if err := os.Mkdir(filepath.Join(tmpDir, "empty"), 0777); err != nil {
523 desc: "ignore_symlink",
524 setup: func(t *testing.T, tmpDir string) string {
525 if err := os.Symlink(tmpDir, filepath.Join(tmpDir, "link")); err != nil {
526 switch runtime.GOOS {
527 case "plan9", "windows":
528 t.Skipf("could not create symlink: %v", err)
537 desc: "dir_is_vendor",
538 setup: func(t *testing.T, tmpDir string) string {
539 vendorDir := filepath.Join(tmpDir, "vendor")
540 if err := os.Mkdir(vendorDir, 0777); err != nil {
543 goModData := []byte("module example.com/m\n\ngo 1.13\n")
544 if err := ioutil.WriteFile(filepath.Join(vendorDir, "go.mod"), goModData, 0666); err != nil {
549 wantHash: "h1:XduFAgX/GaspZa8Jv4pfzoGEzNaU/r88PiCunijw5ok=",
552 t.Run(test.desc, func(t *testing.T) {
553 tmpDir, err := ioutil.TempDir("", "TestCreateFromDirSpecial-"+test.desc)
557 defer os.RemoveAll(tmpDir)
558 dir := test.setup(t, tmpDir)
560 tmpZipFile, err := ioutil.TempFile("", "TestCreateFromDir-*.zip")
564 tmpZipPath := tmpZipFile.Name()
567 os.Remove(tmpZipPath)
570 m := module.Version{Path: "example.com/m", Version: "v1.0.0"}
571 if err := modzip.CreateFromDir(tmpZipFile, m, dir); err != nil {
574 if err := tmpZipFile.Close(); err != nil {
578 if hash, err := dirhash.HashZip(tmpZipPath, dirhash.Hash1); err != nil {
580 } else if hash != test.wantHash {
581 t.Fatalf("got hash %q; want %q", hash, emptyHash)
587 func TestUnzip(t *testing.T) {
588 testDir := filepath.FromSlash("testdata/unzip")
589 testInfos, err := ioutil.ReadDir(testDir)
593 for _, testInfo := range testInfos {
594 base := filepath.Base(testInfo.Name())
595 if filepath.Ext(base) != ".txt" {
598 t.Run(base[:len(base)-len(".txt")], func(t *testing.T) {
600 testPath := filepath.Join(testDir, testInfo.Name())
601 test, err := readTest(testPath)
606 // Convert txtar to temporary zip file.
607 tmpZipPath, err := extractTxtarToTempZip(test.archive)
612 if err := os.Remove(tmpZipPath); err != nil {
613 t.Errorf("removing temp zip file: %v", err)
617 // Extract to a temporary directory.
618 tmpDir, err := ioutil.TempDir("", "TestUnzip")
622 defer os.RemoveAll(tmpDir)
623 m := module.Version{Path: test.path, Version: test.version}
624 if err := modzip.Unzip(tmpDir, m, tmpZipPath); err != nil {
625 if test.wantErr == "" {
626 t.Fatalf("unexpected error: %v", err)
627 } else if !strings.Contains(err.Error(), test.wantErr) {
628 t.Fatalf("got error %q; want error containing %q", err.Error(), test.wantErr)
632 } else if test.wantErr != "" {
633 t.Fatalf("unexpected success; wanted error containing %q", test.wantErr)
636 // Hash the directory, compare to known value.
637 prefix := fmt.Sprintf("%s@%s/", test.path, test.version)
638 if hash, err := dirhash.HashDir(tmpDir, prefix, dirhash.Hash1); err != nil {
640 } else if hash != test.hash {
641 t.Fatalf("got hash %q\nwant: %q", hash, test.hash)
647 type sizeLimitTest struct {
651 wantCheckFilesErr string
653 wantCheckZipErr string
657 // sizeLimitTests is shared by TestCreateSizeLimits and TestUnzipSizeLimits.
658 var sizeLimitTests = [...]sizeLimitTest{
661 files: []modzip.File{fakeFile{
663 size: modzip.MaxZipFile,
666 desc: "one_too_large",
667 files: []modzip.File{fakeFile{
669 size: modzip.MaxZipFile + 1,
671 wantCheckFilesErr: "module source tree too large",
672 wantCreateErr: "module source tree too large",
673 wantCheckZipErr: "total uncompressed size of module contents too large",
674 wantUnzipErr: "total uncompressed size of module contents too large",
677 files: []modzip.File{
684 size: modzip.MaxZipFile - 10,
688 desc: "total_too_large",
689 files: []modzip.File{
696 size: modzip.MaxZipFile - 9,
699 wantCheckFilesErr: "module source tree too large",
700 wantCreateErr: "module source tree too large",
701 wantCheckZipErr: "total uncompressed size of module contents too large",
702 wantUnzipErr: "total uncompressed size of module contents too large",
705 files: []modzip.File{fakeFile{
707 size: modzip.MaxGoMod,
710 desc: "too_large_gomod",
711 files: []modzip.File{fakeFile{
713 size: modzip.MaxGoMod + 1,
715 wantErr: "go.mod file too large",
717 desc: "large_license",
718 files: []modzip.File{fakeFile{
720 size: modzip.MaxLICENSE,
723 desc: "too_large_license",
724 files: []modzip.File{fakeFile{
726 size: modzip.MaxLICENSE + 1,
728 wantErr: "LICENSE file too large",
732 var sizeLimitVersion = module.Version{Path: "example.com/large", Version: "v1.0.0"}
734 func TestCreateSizeLimits(t *testing.T) {
736 t.Skip("creating large files takes time")
738 tests := append(sizeLimitTests[:], sizeLimitTest{
739 // negative file size may happen when size is represented as uint64
740 // but is cast to int64, as is the case in zip files.
742 files: []modzip.File{fakeFile{
744 size: 0x8000000000000000,
746 wantErr: "module source tree too large",
748 desc: "size_is_a_lie",
749 files: []modzip.File{fakeFile{
752 data: []byte(`package large`),
754 wantCreateErr: "larger than declared size",
757 for _, test := range tests {
759 t.Run(test.desc, func(t *testing.T) {
762 wantCheckFilesErr := test.wantCheckFilesErr
763 if wantCheckFilesErr == "" {
764 wantCheckFilesErr = test.wantErr
766 if _, err := modzip.CheckFiles(test.files); err == nil && wantCheckFilesErr != "" {
767 t.Fatalf("CheckFiles: unexpected success; want error containing %q", wantCheckFilesErr)
768 } else if err != nil && wantCheckFilesErr == "" {
769 t.Fatalf("CheckFiles: got error %q; want success", err)
770 } else if err != nil && !strings.Contains(err.Error(), wantCheckFilesErr) {
771 t.Fatalf("CheckFiles: got error %q; want error containing %q", err, wantCheckFilesErr)
774 wantCreateErr := test.wantCreateErr
775 if wantCreateErr == "" {
776 wantCreateErr = test.wantErr
778 if err := modzip.Create(ioutil.Discard, sizeLimitVersion, test.files); err == nil && wantCreateErr != "" {
779 t.Fatalf("Create: unexpected success; want error containing %q", wantCreateErr)
780 } else if err != nil && wantCreateErr == "" {
781 t.Fatalf("Create: got error %q; want success", err)
782 } else if err != nil && !strings.Contains(err.Error(), wantCreateErr) {
783 t.Fatalf("Create: got error %q; want error containing %q", err, wantCreateErr)
789 func TestUnzipSizeLimits(t *testing.T) {
791 t.Skip("creating large files takes time")
793 for _, test := range sizeLimitTests {
795 t.Run(test.desc, func(t *testing.T) {
797 tmpZipFile, err := ioutil.TempFile("", "TestUnzipSizeLimits-*.zip")
801 tmpZipPath := tmpZipFile.Name()
804 if err := os.Remove(tmpZipPath); err != nil {
805 t.Errorf("removing temp zip file: %v", err)
809 zw := zip.NewWriter(tmpZipFile)
810 prefix := fmt.Sprintf("%s@%s/", sizeLimitVersion.Path, sizeLimitVersion.Version)
811 for _, tf := range test.files {
812 zf, err := zw.Create(prefix + tf.Path())
820 _, err = io.Copy(zf, rc)
826 if err := zw.Close(); err != nil {
829 if err := tmpZipFile.Close(); err != nil {
833 tmpDir, err := ioutil.TempDir("", "TestUnzipSizeLimits")
838 if err := os.RemoveAll(tmpDir); err != nil {
839 t.Errorf("removing temp dir: %v", err)
843 wantCheckZipErr := test.wantCheckZipErr
844 if wantCheckZipErr == "" {
845 wantCheckZipErr = test.wantErr
847 cf, err := modzip.CheckZip(sizeLimitVersion, tmpZipPath)
851 if err == nil && wantCheckZipErr != "" {
852 t.Fatalf("CheckZip: unexpected success; want error containing %q", wantCheckZipErr)
853 } else if err != nil && wantCheckZipErr == "" {
854 t.Fatalf("CheckZip: got error %q; want success", err)
855 } else if err != nil && !strings.Contains(err.Error(), wantCheckZipErr) {
856 t.Fatalf("CheckZip: got error %q; want error containing %q", err, wantCheckZipErr)
859 wantUnzipErr := test.wantUnzipErr
860 if wantUnzipErr == "" {
861 wantUnzipErr = test.wantErr
863 if err := modzip.Unzip(tmpDir, sizeLimitVersion, tmpZipPath); err == nil && wantUnzipErr != "" {
864 t.Fatalf("Unzip: unexpected success; want error containing %q", wantUnzipErr)
865 } else if err != nil && wantUnzipErr == "" {
866 t.Fatalf("Unzip: got error %q; want success", err)
867 } else if err != nil && !strings.Contains(err.Error(), wantUnzipErr) {
868 t.Fatalf("Unzip: got error %q; want error containing %q", err, wantUnzipErr)
874 func TestUnzipSizeLimitsSpecial(t *testing.T) {
876 t.Skip("skipping test; creating large files takes time")
879 for _, test := range []struct {
882 writeZip func(t *testing.T, zipFile *os.File)
886 m: module.Version{Path: "example.com/m", Version: "v1.0.0"},
887 writeZip: func(t *testing.T, zipFile *os.File) {
888 if err := zipFile.Truncate(modzip.MaxZipFile); err != nil {
892 // this is not an error we care about; we're just testing whether
893 // Unzip checks the size of the file before opening.
894 // It's harder to create a valid zip file of exactly the right size.
895 wantErr: "not a valid zip file",
897 desc: "too_large_zip",
898 m: module.Version{Path: "example.com/m", Version: "v1.0.0"},
899 writeZip: func(t *testing.T, zipFile *os.File) {
900 if err := zipFile.Truncate(modzip.MaxZipFile + 1); err != nil {
904 wantErr: "module zip file is too large",
906 desc: "size_is_a_lie",
907 m: module.Version{Path: "example.com/m", Version: "v1.0.0"},
908 writeZip: func(t *testing.T, zipFile *os.File) {
909 // Create a normal zip file in memory containing one file full of zero
910 // bytes. Use a distinctive size so we can find it later.
911 zipBuf := &bytes.Buffer{}
912 zw := zip.NewWriter(zipBuf)
913 f, err := zw.Create("example.com/m@v1.0.0/go.mod")
918 buf := make([]byte, realSize)
919 if _, err := f.Write(buf); err != nil {
922 if err := zw.Close(); err != nil {
926 // Replace the uncompressed size of the file. As a shortcut, we just
927 // search-and-replace the byte sequence. It should occur twice because
928 // the 32- and 64-byte sizes are stored separately. All multi-byte
929 // values are little-endian.
930 zipData := zipBuf.Bytes()
931 realSizeData := []byte{0xAD, 0x0B}
932 fakeSizeData := []byte{0xAC, 0x00}
936 if i := bytes.Index(s, realSizeData); i < 0 {
941 copy(s[:len(fakeSizeData)], fakeSizeData)
945 t.Fatalf("replaced size %d times; expected 2", n)
948 // Write the modified zip to the actual file.
949 if _, err := zipFile.Write(zipData); err != nil {
953 wantErr: "uncompressed size of file example.com/m@v1.0.0/go.mod is larger than declared size",
957 t.Run(test.desc, func(t *testing.T) {
959 tmpZipFile, err := ioutil.TempFile("", "TestUnzipSizeLimitsSpecial-*.zip")
963 tmpZipPath := tmpZipFile.Name()
966 os.Remove(tmpZipPath)
969 test.writeZip(t, tmpZipFile)
970 if err := tmpZipFile.Close(); err != nil {
974 tmpDir, err := ioutil.TempDir("", "TestUnzipSizeLimitsSpecial")
978 defer os.RemoveAll(tmpDir)
979 if err := modzip.Unzip(tmpDir, test.m, tmpZipPath); err == nil && test.wantErr != "" {
980 t.Fatalf("unexpected success; want error containing %q", test.wantErr)
981 } else if err != nil && test.wantErr == "" {
982 t.Fatalf("got error %q; want success", err)
983 } else if err != nil && !strings.Contains(err.Error(), test.wantErr) {
984 t.Fatalf("got error %q; want error containing %q", err, test.wantErr)
990 // TestVCS clones a repository, creates a zip for a known version,
991 // and verifies the zip file itself has the same SHA-256 hash as the one
992 // 'go mod download' produces.
994 // This test is intended to build confidence that this implementation produces
995 // the same output as the go command, given the same VCS zip input. This is
996 // not intended to be a complete conformance test. The code that produces zip
997 // archives from VCS repos is based on the go command, but it's for testing
998 // only, and we don't export it.
1000 // Note that we test the hash of the zip file itself. This is stricter than
1001 // testing the hash of the content, which is what we've promised users.
1002 // It's okay if the zip hash changes without changing the content hash, but
1003 // we should not let that happen accidentally.
1004 func TestVCS(t *testing.T) {
1005 if testing.Short() {
1009 var downloadErrorCount int32
1010 const downloadErrorLimit = 3
1012 haveVCS := make(map[string]bool)
1013 for _, vcs := range []string{"git", "hg"} {
1014 _, err := exec.LookPath(vcs)
1015 haveVCS[vcs] = err == nil
1018 for _, test := range []struct {
1020 vcs, url, subdir, rev string
1021 wantContentHash, wantZipHash string
1023 // Simple tests: all versions of rsc.io/quote + newer major versions
1025 m: module.Version{Path: "rsc.io/quote", Version: "v1.0.0"},
1027 url: "https://github.com/rsc/quote",
1029 wantContentHash: "h1:haUSojyo3j2M9g7CEUFG8Na09dtn7QKxvPGaPVQdGwM=",
1030 wantZipHash: "5c08ba2c09a364f93704aaa780e7504346102c6ef4fe1333a11f09904a732078",
1033 m: module.Version{Path: "rsc.io/quote", Version: "v1.1.0"},
1035 url: "https://github.com/rsc/quote",
1037 wantContentHash: "h1:n/ElL9GOlVEwL0mVjzaYj0UxTI/TX9aQ7lR5LHqP/Rw=",
1038 wantZipHash: "730a5ae6e5c4e216e4f84bb93aa9785a85630ad73f96954ebb5f9daa123dcaa9",
1041 m: module.Version{Path: "rsc.io/quote", Version: "v1.2.0"},
1043 url: "https://github.com/rsc/quote",
1045 wantContentHash: "h1:fFMCNi0A97hfNrtUZVQKETbuc3h7bmfFQHnjutpPYCg=",
1046 wantZipHash: "fe1bd62652e9737a30d6b7fd396ea13e54ad13fb05f295669eb63d6d33290b06",
1049 m: module.Version{Path: "rsc.io/quote", Version: "v1.2.1"},
1051 url: "https://github.com/rsc/quote",
1053 wantContentHash: "h1:l+HtgC05eds8qgXNApuv6g1oK1q3B144BM5li1akqXY=",
1054 wantZipHash: "9f0e74de55a6bd20c1567a81e707814dc221f07df176af2a0270392c6faf32fd",
1057 m: module.Version{Path: "rsc.io/quote", Version: "v1.3.0"},
1059 url: "https://github.com/rsc/quote",
1061 wantContentHash: "h1:aPUoHx/0Cd7BTZs4SAaknT4TaKryH766GcFTvJjVbHU=",
1062 wantZipHash: "03872ee7d6747bc2ee0abadbd4eb09e60f6df17d0a6142264abe8a8a00af50e7",
1065 m: module.Version{Path: "rsc.io/quote", Version: "v1.4.0"},
1067 url: "https://github.com/rsc/quote",
1069 wantContentHash: "h1:tYuJspOzwTRMUOX6qmSDRTEKFVV80GM0/l89OLZuVNg=",
1070 wantZipHash: "f60be8193c607bf197da01da4bedb3d683fe84c30de61040eb5d7afaf7869f2e",
1073 m: module.Version{Path: "rsc.io/quote", Version: "v1.5.0"},
1075 url: "https://github.com/rsc/quote",
1077 wantContentHash: "h1:mVjf/WMWxfIw299sOl/O3EXn5qEaaJPMDHMsv7DBDlw=",
1078 wantZipHash: "a2d281834ce159703540da94425fa02c7aec73b88b560081ed0d3681bfe9cd1f",
1081 m: module.Version{Path: "rsc.io/quote", Version: "v1.5.1"},
1083 url: "https://github.com/rsc/quote",
1085 wantContentHash: "h1:ptSemFtffEBvMed43o25vSUpcTVcqxfXU8Jv0sfFVJs=",
1086 wantZipHash: "4ecd78a6d9f571e84ed2baac1688fd150400db2c5b017b496c971af30aaece02",
1089 m: module.Version{Path: "rsc.io/quote", Version: "v1.5.2"},
1091 url: "https://github.com/rsc/quote",
1093 wantContentHash: "h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=",
1094 wantZipHash: "643fcf8ef4e4cbb8f910622c42df3f9a81f3efe8b158a05825a81622c121ca0a",
1097 m: module.Version{Path: "rsc.io/quote", Version: "v1.5.3-pre1"},
1099 url: "https://github.com/rsc/quote",
1101 wantContentHash: "h1:c3EJ21kn75/hyrOL/Dvj45+ifxGFSY8Wf4WBcoWTxF0=",
1102 wantZipHash: "24106f0f15384949df51fae5d34191bf120c3b80c1c904721ca2872cf83126b2",
1105 m: module.Version{Path: "rsc.io/quote/v2", Version: "v2.0.1"},
1107 url: "https://github.com/rsc/quote",
1109 wantContentHash: "h1:DF8hmGbDhgiIa2tpqLjHLIKkJx6WjCtLEqZBAU+hACI=",
1110 wantZipHash: "009ed42474a59526fe56a14a9dd02bd7f977d1bd3844398bd209d0da0484aade",
1113 m: module.Version{Path: "rsc.io/quote/v3", Version: "v3.0.0"},
1115 url: "https://github.com/rsc/quote",
1118 wantContentHash: "h1:OEIXClZHFMyx5FdatYfxxpNEvxTqHlu5PNdla+vSYGg=",
1119 wantZipHash: "cf3ff89056b785d7b3ef3a10e984efd83b47d9e65eabe8098b927b3370d5c3eb",
1122 // Test cases from vcs-test.golang.org
1124 m: module.Version{Path: "vcs-test.golang.org/git/v3pkg.git/v3", Version: "v3.0.0"},
1126 url: "https://vcs-test.golang.org/git/v3pkg",
1128 wantContentHash: "h1:mZhljS1BaiW8lODR6wqY5pDxbhXja04rWPFXPwRAtvA=",
1129 wantZipHash: "9c65f0d235e531008dc04e977f6fa5d678febc68679bb63d4148dadb91d3fe57",
1132 m: module.Version{Path: "vcs-test.golang.org/go/custom-hg-hello", Version: "v0.0.0-20171010233936-a8c8e7a40da9"},
1134 url: "https://vcs-test.golang.org/hg/custom-hg-hello",
1135 rev: "a8c8e7a40da9",
1136 wantContentHash: "h1:LU6jFCbwn5VVgTcj+y4LspOpJHLZvl5TGPE+LwwpMw4=",
1137 wantZipHash: "a1b12047da979d618c639ee98f370767a13d0507bd77785dc2f8dad66b40e2e6",
1140 // Latest versions of selected golang.org/x repos
1142 m: module.Version{Path: "golang.org/x/arch", Version: "v0.0.0-20190927153633-4e8777c89be4"},
1144 url: "https://go.googlesource.com/arch",
1145 rev: "4e8777c89be4d9e61691fbe5d4e6c8838a7806f3",
1146 wantContentHash: "h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI=",
1147 wantZipHash: "d17551a0c4957180ec1507065d13dcdd0f5cd8bfd7dd735fb81f64f3e2b31b68",
1150 m: module.Version{Path: "golang.org/x/blog", Version: "v0.0.0-20191017104857-0cd0cdff05c2"},
1152 url: "https://go.googlesource.com/blog",
1153 rev: "0cd0cdff05c251ad0c796cc94d7059e013311fc6",
1154 wantContentHash: "h1:IKGICrORhR1aH2xG/WqrnpggSNolSj5urQxggCfmj28=",
1155 wantZipHash: "0fed6b400de54da34b52b464ef2cdff45167236aaaf9a99ba8eba8855036faff",
1158 m: module.Version{Path: "golang.org/x/crypto", Version: "v0.0.0-20191011191535-87dc89f01550"},
1160 url: "https://go.googlesource.com/crypto",
1161 rev: "87dc89f01550277dc22b74ffcf4cd89fa2f40f4c",
1162 wantContentHash: "h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=",
1163 wantZipHash: "88e47aa05eb25c6abdad7387ccccfc39e74541896d87b7b1269e9dd2fa00100d",
1166 m: module.Version{Path: "golang.org/x/net", Version: "v0.0.0-20191014212845-da9a3fd4c582"},
1168 url: "https://go.googlesource.com/net",
1169 rev: "da9a3fd4c5820e74b24a6cb7fb438dc9b0dd377c",
1170 wantContentHash: "h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA=",
1171 wantZipHash: "34901a85e6c15475a40457c2393ce66fb0999accaf2d6aa5b64b4863751ddbde",
1174 m: module.Version{Path: "golang.org/x/sync", Version: "v0.0.0-20190911185100-cd5d95a43a6e"},
1176 url: "https://go.googlesource.com/sync",
1177 rev: "cd5d95a43a6e21273425c7ae415d3df9ea832eeb",
1178 wantContentHash: "h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=",
1179 wantZipHash: "9c63fe51b0c533b258d3acc30d9319fe78679ce1a051109c9dea3105b93e2eef",
1182 m: module.Version{Path: "golang.org/x/sys", Version: "v0.0.0-20191010194322-b09406accb47"},
1184 url: "https://go.googlesource.com/sys",
1185 rev: "b09406accb4736d857a32bf9444cd7edae2ffa79",
1186 wantContentHash: "h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=",
1187 wantZipHash: "f26f2993757670b4d1fee3156d331513259757f17133a36966c158642c3f61df",
1190 m: module.Version{Path: "golang.org/x/talks", Version: "v0.0.0-20191010201600-067e0d331fee"},
1192 url: "https://go.googlesource.com/talks",
1193 rev: "067e0d331feee4f8d0fa17d47444db533bd904e7",
1194 wantContentHash: "h1:8fnBMBUwliuiHuzfFw6kSSx79AzQpqkjZi3FSNIoqYs=",
1195 wantZipHash: "fab2129f3005f970dbf2247378edb3220f6bd36726acdc7300ae3bb0f129e2f2",
1198 m: module.Version{Path: "golang.org/x/tools", Version: "v0.0.0-20191017205301-920acffc3e65"},
1200 url: "https://go.googlesource.com/tools",
1201 rev: "920acffc3e65862cb002dae6b227b8d9695e3d29",
1202 wantContentHash: "h1:GwXwgmbrvlcHLDsENMqrQTTIC2C0kIPszsq929NruKI=",
1203 wantZipHash: "7f0ab7466448190f8ad1b8cfb05787c3fb08f4a8f9953cd4b40a51c76ddebb28",
1206 m: module.Version{Path: "golang.org/x/tour", Version: "v0.0.0-20191002171047-6bb846ce41cd"},
1208 url: "https://go.googlesource.com/tour",
1209 rev: "6bb846ce41cdca087b14c8e3560a679691c424b6",
1210 wantContentHash: "h1:EUlK3Rq8iTkQERnCnveD654NvRJ/ZCM9XCDne+S5cJ8=",
1211 wantZipHash: "d6a7e03e02e5f7714bd12653d319a3b0f6e1099c01b1f9a17bc3613fb31c9170",
1215 testName := strings.ReplaceAll(test.m.String(), "/", "_")
1216 t.Run(testName, func(t *testing.T) {
1217 if have, ok := haveVCS[test.vcs]; !ok {
1218 t.Fatalf("unknown vcs: %s", test.vcs)
1224 repo, dl, cleanup, err := downloadVCSZip(test.vcs, test.url, test.rev, test.subdir)
1227 // This may fail if there's a problem with the network or upstream
1228 // repository. The package being tested doesn't directly interact with
1229 // VCS tools; the test just does this to simulate what the go command
1230 // does. So an error should cause a skip instead of a failure. But we
1231 // should fail after too many errors so we don't lose test coverage
1232 // when something changes permanently.
1233 n := atomic.AddInt32(&downloadErrorCount, 1)
1234 if n < downloadErrorLimit {
1235 t.Skipf("failed to download zip from repository: %v", err)
1237 t.Fatalf("failed to download zip from repository (repeated failure): %v", err)
1241 // Create a module zip from that archive.
1242 // (adapted from cmd/go/internal/modfetch.codeRepo.Zip)
1243 info, err := dl.Stat()
1247 zr, err := zip.NewReader(dl, info.Size())
1252 var files []modzip.File
1254 subdir := test.subdir
1255 if subdir != "" && !strings.HasSuffix(subdir, "/") {
1258 haveLICENSE := false
1259 for _, f := range zr.File {
1260 if !f.FileInfo().Mode().IsRegular() {
1263 if topPrefix == "" {
1264 i := strings.Index(f.Name, "/")
1266 t.Fatal("missing top-level directory prefix")
1268 topPrefix = f.Name[:i+1]
1270 if strings.HasSuffix(f.Name, "/") { // drop directory dummy entries
1273 if !strings.HasPrefix(f.Name, topPrefix) {
1274 t.Fatal("zip file contains more than one top-level directory")
1276 name := strings.TrimPrefix(f.Name, topPrefix)
1277 if !strings.HasPrefix(name, subdir) {
1280 name = strings.TrimPrefix(name, subdir)
1281 if name == ".hg_archival.txt" {
1282 // Inserted by hg archive.
1283 // Not correct to drop from other version control systems, but too bad.
1286 if name == "LICENSE" {
1289 files = append(files, zipFile{name: name, f: f})
1291 if !haveLICENSE && subdir != "" {
1292 license, err := downloadVCSFile(test.vcs, repo, test.rev, "LICENSE")
1296 files = append(files, fakeFile{
1298 size: uint64(len(license)),
1303 tmpModZipFile, err := ioutil.TempFile("", "TestVCS-*.zip")
1307 tmpModZipPath := tmpModZipFile.Name()
1309 tmpModZipFile.Close()
1310 os.Remove(tmpModZipPath)
1313 w := io.MultiWriter(tmpModZipFile, h)
1314 if err := modzip.Create(w, test.m, files); err != nil {
1317 if err := tmpModZipFile.Close(); err != nil {
1321 gotZipHash := hex.EncodeToString(h.Sum(nil))
1322 if test.wantZipHash != gotZipHash {
1323 // If the test fails because the hash of the zip file itself differs,
1324 // that may be okay as long as the hash of the data within the zip file
1325 // does not change. For example, we might change the compression,
1326 // order, or alignment of files without affecting the extracted output.
1327 // We shouldn't make such a change unintentionally though, so this
1328 // test will fail either way.
1329 if gotSum, err := dirhash.HashZip(tmpModZipPath, dirhash.Hash1); err == nil && test.wantContentHash != gotSum {
1330 t.Fatalf("zip content hash: got %s, want %s", gotSum, test.wantContentHash)
1332 t.Fatalf("zip file hash: got %s, want %s", gotZipHash, test.wantZipHash)
1339 func downloadVCSZip(vcs, url, rev, subdir string) (repoDir string, dl *os.File, cleanup func(), err error) {
1340 var cleanups []func()
1342 for i := len(cleanups) - 1; i >= 0; i-- {
1346 repoDir, err = ioutil.TempDir("", "downloadVCSZip")
1348 return "", nil, cleanup, err
1350 cleanups = append(cleanups, func() { os.RemoveAll(repoDir) })
1354 // Create a repository and download the revision we want.
1355 if err := run(repoDir, "git", "init", "--bare"); err != nil {
1356 return "", nil, cleanup, err
1358 if err := os.MkdirAll(filepath.Join(repoDir, "info"), 0777); err != nil {
1359 return "", nil, cleanup, err
1361 attrFile, err := os.OpenFile(filepath.Join(repoDir, "info", "attributes"), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
1363 return "", nil, cleanup, err
1365 if _, err := attrFile.Write([]byte("\n* -export-subst -export-ignore\n")); err != nil {
1367 return "", nil, cleanup, err
1369 if err := attrFile.Close(); err != nil {
1370 return "", nil, cleanup, err
1372 if err := run(repoDir, "git", "remote", "add", "origin", "--", url); err != nil {
1373 return "", nil, cleanup, err
1376 if strings.HasPrefix(rev, "v") {
1377 refSpec = fmt.Sprintf("refs/tags/%[1]s:refs/tags/%[1]s", rev)
1379 refSpec = fmt.Sprintf("%s:refs/dummy", rev)
1381 if err := run(repoDir, "git", "fetch", "-f", "--depth=1", "origin", refSpec); err != nil {
1382 return "", nil, cleanup, err
1385 // Create an archive.
1386 tmpZipFile, err := ioutil.TempFile("", "downloadVCSZip-*.zip")
1388 return "", nil, cleanup, err
1390 cleanups = append(cleanups, func() {
1391 name := tmpZipFile.Name()
1399 cmd := exec.Command("git", "-c", "core.autocrlf=input", "-c", "core.eol=lf", "archive", "--format=zip", "--prefix=prefix/", rev, "--", subdirArg)
1401 cmd.Stdout = tmpZipFile
1402 if err := cmd.Run(); err != nil {
1403 return "", nil, cleanup, err
1405 if _, err := tmpZipFile.Seek(0, 0); err != nil {
1406 return "", nil, cleanup, err
1408 return repoDir, tmpZipFile, cleanup, nil
1411 // Clone the whole repository.
1412 if err := run(repoDir, "hg", "clone", "-U", "--", url, "."); err != nil {
1413 return "", nil, cleanup, err
1416 // Create an archive.
1417 tmpZipFile, err := ioutil.TempFile("", "downloadVCSZip-*.zip")
1419 return "", nil, cleanup, err
1421 tmpZipPath := tmpZipFile.Name()
1423 cleanups = append(cleanups, func() { os.Remove(tmpZipPath) })
1424 args := []string{"archive", "-t", "zip", "--no-decode", "-r", rev, "--prefix=prefix/"}
1426 args = append(args, "-I", subdir+"/**")
1428 args = append(args, "--", tmpZipPath)
1429 if err := run(repoDir, "hg", args...); err != nil {
1430 return "", nil, cleanup, err
1432 if tmpZipFile, err = os.Open(tmpZipPath); err != nil {
1433 return "", nil, cleanup, err
1435 cleanups = append(cleanups, func() { tmpZipFile.Close() })
1436 return repoDir, tmpZipFile, cleanup, err
1439 return "", nil, cleanup, fmt.Errorf("vcs %q not supported", vcs)
1443 func downloadVCSFile(vcs, repo, rev, file string) ([]byte, error) {
1446 cmd := exec.Command("git", "cat-file", "blob", rev+":"+file)
1450 return nil, fmt.Errorf("vcs %q not supported", vcs)
1454 func run(dir string, name string, args ...string) error {
1455 cmd := exec.Command(name, args...)
1457 if err := cmd.Run(); err != nil {
1458 return fmt.Errorf("%s: %v", strings.Join(args, " "), err)
1463 type zipFile struct {
1468 func (f zipFile) Path() string { return f.name }
1469 func (f zipFile) Lstat() (os.FileInfo, error) { return f.f.FileInfo(), nil }
1470 func (f zipFile) Open() (io.ReadCloser, error) { return f.f.Open() }