...

Source file src/golang.org/x/mod/zip/zip_test.go

Documentation: golang.org/x/mod/zip

     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.
     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  		// Enable extra Git logging to diagnose networking issues.
    43  		// (These environment variables will be inherited by subprocesses.)
    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  // gitPath returns the path to a usable "git" command,
    51  // or a non-nil error.
    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  // readTest loads a test from a txtar file. The comment section of the file
    81  // should contain lines with key=value pairs. Valid keys are the field names
    82  // from testParams.
    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++ // report line numbers starting with 1
    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 // if nil, Open will access a sequence of 0-bytes
   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  // TestCheckFiles verifies behavior of CheckFiles. Note that CheckFiles is also
   222  // covered by TestCreate, TestCreateDir, and TestCreateSizeLimits, so this test
   223  // focuses on how multiple errors and omissions are reported, rather than trying
   224  // to cover every case.
   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  			// Load the test.
   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  			// Check the files.
   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  			// Check that the error (if any) is just a list of invalid files.
   263  			// SizeError is not covered in this test.
   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  // TestCheckDir verifies behavior of the CheckDir function. Note that CheckDir
   279  // relies on CheckFiles and listFilesInDir (called by CreateFromDir), so this
   280  // test focuses on how multiple errors and omissions are reported, rather than
   281  // trying to cover every case.
   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  			// Load the test and extract the files to a temporary directory.
   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  			// Check the directory.
   312  			cf, err := modzip.CheckDir(tmpDir)
   313  			if err != nil && err.Error() != cf.Err().Error() {
   314  				// I/O error
   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  			// Check that the error (if any) is just a list of invalid files.
   324  			// SizeError is not covered in this test.
   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  // TestCheckZip verifies behavior of CheckZip. Note that CheckZip is also
   340  // covered by TestUnzip, so this test focuses on how multiple errors are
   341  // reported, rather than trying to cover every case.
   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  			// Load the test and extract the files to a temporary zip file.
   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  			// Check the zip.
   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  				// I/O error
   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  			// Check that the error (if any) is just a list of invalid files.
   384  			// SizeError is not covered in this test.
   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  			// Load the test.
   415  			testPath := filepath.Join(testDir, testEntry.Name())
   416  			test, err := readTest(testPath)
   417  			if err != nil {
   418  				t.Fatal(err)
   419  			}
   420  
   421  			// Write zip to temporary file.
   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  			// Hash zip file, compare with known value.
   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  			// Load the test.
   478  			testPath := filepath.Join(testDir, testEntry.Name())
   479  			test, err := readTest(testPath)
   480  			if err != nil {
   481  				t.Fatal(err)
   482  			}
   483  
   484  			// Write files to a temporary directory.
   485  			tmpDir, err := extractTxtarToTempDir(t, test.archive)
   486  			if err != nil {
   487  				t.Fatal(err)
   488  			}
   489  
   490  			// Create zip from the directory.
   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  			// Hash zip file, compare with known value.
   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  						// Symlinks in tmpDir are always expected to work on these platforms.
   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  			// Load the test.
   606  			testPath := filepath.Join(testDir, testEntry.Name())
   607  			test, err := readTest(testPath)
   608  			if err != nil {
   609  				t.Fatal(err)
   610  			}
   611  
   612  			// Convert txtar to temporary zip file.
   613  			tmpZipPath, err := extractTxtarToTempZip(t, test.archive)
   614  			if err != nil {
   615  				t.Fatal(err)
   616  			}
   617  
   618  			// Extract to a temporary directory.
   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  			// Hash the directory, compare to known value.
   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  // sizeLimitTests is shared by TestCreateSizeLimits and TestUnzipSizeLimits.
   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  		// negative file size may happen when size is represented as uint64
   737  		// but is cast to int64, as is the case in zip files.
   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  			// this is not an error we care about; we're just testing whether
   876  			// Unzip checks the size of the file before opening.
   877  			// It's harder to create a valid zip file of exactly the right size.
   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  				// Create a normal zip file in memory containing one file full of zero
   893  				// bytes. Use a distinctive size so we can find it later.
   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  				// Replace the uncompressed size of the file. As a shortcut, we just
   910  				// search-and-replace the byte sequence. It should occur twice because
   911  				// the 32- and 64-byte sizes are stored separately. All multi-byte
   912  				// values are little-endian.
   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  				// Write the modified zip to the actual file.
   932  				if _, err := zipFile.Write(zipData); err != nil {
   933  					t.Fatal(err)
   934  				}
   935  			},
   936  			// wantErr1 is for 1.18 and earlier,
   937  			// wantErr2 is for 1.19 and later.
   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  // TestVCS clones a repository, creates a zip for a known version,
   977  // and verifies the zip file itself has the same SHA-256 hash as the one
   978  // 'go mod download' produces.
   979  //
   980  // This test is intended to build confidence that this implementation produces
   981  // the same output as the go command, given the same VCS zip input. This is
   982  // not intended to be a complete conformance test. The code that produces zip
   983  // archives from VCS repos is based on the go command, but it's for testing
   984  // only, and we don't export it.
   985  //
   986  // Note that we test the hash of the zip file itself. This is stricter than
   987  // testing the hash of the content, which is what we've promised users.
   988  // It's okay if the zip hash changes without changing the content hash, but
   989  // we should not let that happen accidentally.
   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  		// Simple tests: all versions of rsc.io/quote + newer major versions
  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  		// Test cases from vcs-test.golang.org
  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  		// Latest versions of selected golang.org/x repos
  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  				// This may fail if there's a problem with the network or upstream
  1214  				// repository. The package being tested doesn't directly interact with
  1215  				// VCS tools; the test just does this to simulate what the go command
  1216  				// does. So an error should cause a skip instead of a failure. But we
  1217  				// should fail after too many errors so we don't lose test coverage
  1218  				// when something changes permanently.
  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  			// Create a module zip from that archive.
  1228  			// (adapted from cmd/go/internal/modfetch.codeRepo.Zip)
  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, "/") { // drop directory dummy entries
  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  					// Inserted by hg archive.
  1269  					// Not correct to drop from other version control systems, but too bad.
  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  				// If the test fails because the hash of the zip file itself differs,
  1307  				// that may be okay as long as the hash of the data within the zip file
  1308  				// does not change. For example, we might change the compression,
  1309  				// order, or alignment of files without affecting the extracted output.
  1310  				// We shouldn't make such a change unintentionally though, so this
  1311  				// test will fail either way.
  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  		// Create a repository and download the revision we want.
  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  		// Create an archive.
  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  		// Clone the whole repository.
  1394  		if _, err := run(t, repoDir, "hg", "clone", "-U", "--", url, "."); err != nil {
  1395  			return "", nil, err
  1396  		}
  1397  
  1398  		// Create an archive.
  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  	// Write files to a temporary directory.
  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  			// Note: File paths are zipped as if the subdir were the root. ie d.go instead of d/d.go.
  1531  			// subdirs without a license hoist the license from the root
  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  			// Note: File paths are zipped as if the subdir were the root. ie e.go instead of e/e.go.
  1540  			// subdirs with a license use their own
  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  			// Note: File paths are zipped as if the subdir were the root. ie f.go instead of f/f.go.
  1549  			// subdirs without a license hoist the license from the root
  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  			// Create zip from the directory.
  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  			// The things that should be there.
  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  			// The things that are missing.
  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  // Test what the experience of creating a zip from a v2 module is like.
  1626  func TestCreateFromVCS_v2(t *testing.T) {
  1627  	mustHaveGit(t)
  1628  
  1629  	// Write files to a temporary directory.
  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  	// Create zip from the directory.
  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  	// The things that should be there.
  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  	// The things that are missing.
  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  	// Write files to a temporary directory.
  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  	// Create zip from the directory.
  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  	// Write files to a temporary directory.
  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  	// Create zip from the directory.
  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  // gitInit runs "git init" at the specified dir.
  1780  //
  1781  // Note: some environments - and trybots - don't have git installed. This
  1782  // function will cause the calling test to be skipped if that's the case.
  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