github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/go/not-internal/modfetch/coderepo_test.go (about)

     1  // Copyright 2018 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 modfetch
     6  
     7  import (
     8  	"archive/zip"
     9  	"crypto/sha256"
    10  	"encoding/hex"
    11  	"hash"
    12  	"github.com/gagliardetto/golang-go/not-internal/testenv"
    13  	"io"
    14  	"io/ioutil"
    15  	"log"
    16  	"os"
    17  	"reflect"
    18  	"strings"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/cfg"
    23  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/modfetch/codehost"
    24  
    25  	"golang.org/x/mod/sumdb/dirhash"
    26  )
    27  
    28  func TestMain(m *testing.M) {
    29  	os.Exit(testMain(m))
    30  }
    31  
    32  func testMain(m *testing.M) int {
    33  	cfg.GOPROXY = "direct"
    34  
    35  	// The sum database is populated using a released version of the go command,
    36  	// but this test may include fixes for additional modules that previously
    37  	// could not be fetched. Since this test isn't executing any of the resolved
    38  	// code, bypass the sum database.
    39  	cfg.GOSUMDB = "off"
    40  
    41  	dir, err := ioutil.TempDir("", "gitrepo-test-")
    42  	if err != nil {
    43  		log.Fatal(err)
    44  	}
    45  	defer os.RemoveAll(dir)
    46  
    47  	codehost.WorkRoot = dir
    48  	return m.Run()
    49  }
    50  
    51  const (
    52  	vgotest1git = "github.com/rsc/vgotest1"
    53  	vgotest1hg  = "vcs-test.golang.org/hg/vgotest1.hg"
    54  )
    55  
    56  var altVgotests = map[string]string{
    57  	"hg": vgotest1hg,
    58  }
    59  
    60  type codeRepoTest struct {
    61  	vcs         string
    62  	path        string
    63  	lookErr     string
    64  	mpath       string
    65  	rev         string
    66  	err         string
    67  	version     string
    68  	name        string
    69  	short       string
    70  	time        time.Time
    71  	gomod       string
    72  	gomodErr    string
    73  	zip         []string
    74  	zipErr      string
    75  	zipSum      string
    76  	zipFileHash string
    77  }
    78  
    79  var codeRepoTests = []codeRepoTest{
    80  	{
    81  		vcs:     "git",
    82  		path:    "github.com/rsc/vgotest1",
    83  		rev:     "v0.0.0",
    84  		version: "v0.0.0",
    85  		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
    86  		short:   "80d85c5d4d17",
    87  		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
    88  		zip: []string{
    89  			"LICENSE",
    90  			"README.md",
    91  			"pkg/p.go",
    92  		},
    93  		zipSum:      "h1:zVEjciLdlk/TPWCOyZo7k24T+tOKRQC+u8MKq/xS80I=",
    94  		zipFileHash: "738a00ddbfe8c329dce6b48e1f23c8e22a92db50f3cfb2653caa0d62676bc09c",
    95  	},
    96  	{
    97  		vcs:     "git",
    98  		path:    "github.com/rsc/vgotest1",
    99  		rev:     "v0.0.0-20180219231006-80d85c5d4d17",
   100  		version: "v0.0.0-20180219231006-80d85c5d4d17",
   101  		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
   102  		short:   "80d85c5d4d17",
   103  		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
   104  		zip: []string{
   105  			"LICENSE",
   106  			"README.md",
   107  			"pkg/p.go",
   108  		},
   109  		zipSum:      "h1:nOznk2xKsLGkTnXe0q9t1Ewt9jxK+oadtafSUqHM3Ec=",
   110  		zipFileHash: "bacb08f391e29d2eaaef8281b5c129ee6d890e608ee65877e0003c0181a766c8",
   111  	},
   112  	{
   113  		vcs:  "git",
   114  		path: "github.com/rsc/vgotest1",
   115  		rev:  "v0.0.1-0.20180219231006-80d85c5d4d17",
   116  		err:  `github.com/rsc/vgotest1@v0.0.1-0.20180219231006-80d85c5d4d17: invalid pseudo-version: tag (v0.0.0) found on revision 80d85c5d4d17 is already canonical, so should not be replaced with a pseudo-version derived from that tag`,
   117  	},
   118  	{
   119  		vcs:     "git",
   120  		path:    "github.com/rsc/vgotest1",
   121  		rev:     "v1.0.0",
   122  		version: "v1.0.0",
   123  		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
   124  		short:   "80d85c5d4d17",
   125  		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
   126  		zip: []string{
   127  			"LICENSE",
   128  			"README.md",
   129  			"pkg/p.go",
   130  		},
   131  		zipSum:      "h1:e040hOoWGeuJLawDjK9DW6med+cz9FxMFYDMOVG8ctQ=",
   132  		zipFileHash: "74caab65cfbea427c341fa815f3bb0378681d8f0e3cf62a7f207014263ec7be3",
   133  	},
   134  	{
   135  		vcs:     "git",
   136  		path:    "github.com/rsc/vgotest1/v2",
   137  		rev:     "v2.0.0",
   138  		version: "v2.0.0",
   139  		name:    "45f53230a74ad275c7127e117ac46914c8126160",
   140  		short:   "45f53230a74a",
   141  		time:    time.Date(2018, 7, 19, 1, 21, 27, 0, time.UTC),
   142  		err:     "missing github.com/rsc/vgotest1/go.mod and .../v2/go.mod at revision v2.0.0",
   143  	},
   144  	{
   145  		vcs:     "git",
   146  		path:    "github.com/rsc/vgotest1",
   147  		rev:     "80d85c5",
   148  		version: "v1.0.0",
   149  		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
   150  		short:   "80d85c5d4d17",
   151  		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
   152  		zip: []string{
   153  			"LICENSE",
   154  			"README.md",
   155  			"pkg/p.go",
   156  		},
   157  		zipSum:      "h1:e040hOoWGeuJLawDjK9DW6med+cz9FxMFYDMOVG8ctQ=",
   158  		zipFileHash: "74caab65cfbea427c341fa815f3bb0378681d8f0e3cf62a7f207014263ec7be3",
   159  	},
   160  	{
   161  		vcs:     "git",
   162  		path:    "github.com/rsc/vgotest1",
   163  		rev:     "mytag",
   164  		version: "v1.0.0",
   165  		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
   166  		short:   "80d85c5d4d17",
   167  		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
   168  		zip: []string{
   169  			"LICENSE",
   170  			"README.md",
   171  			"pkg/p.go",
   172  		},
   173  	},
   174  	{
   175  		vcs:     "git",
   176  		path:    "github.com/rsc/vgotest1/v2",
   177  		rev:     "45f53230a",
   178  		version: "v2.0.0",
   179  		name:    "45f53230a74ad275c7127e117ac46914c8126160",
   180  		short:   "45f53230a74a",
   181  		time:    time.Date(2018, 7, 19, 1, 21, 27, 0, time.UTC),
   182  		err:     "missing github.com/rsc/vgotest1/go.mod and .../v2/go.mod at revision v2.0.0",
   183  	},
   184  	{
   185  		vcs:     "git",
   186  		path:    "github.com/rsc/vgotest1/v54321",
   187  		rev:     "80d85c5",
   188  		version: "v54321.0.0-20180219231006-80d85c5d4d17",
   189  		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
   190  		short:   "80d85c5d4d17",
   191  		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
   192  		err:     "missing github.com/rsc/vgotest1/go.mod and .../v54321/go.mod at revision 80d85c5d4d17",
   193  	},
   194  	{
   195  		vcs:  "git",
   196  		path: "github.com/rsc/vgotest1/submod",
   197  		rev:  "v1.0.0",
   198  		err:  "unknown revision submod/v1.0.0",
   199  	},
   200  	{
   201  		vcs:  "git",
   202  		path: "github.com/rsc/vgotest1/submod",
   203  		rev:  "v1.0.3",
   204  		err:  "unknown revision submod/v1.0.3",
   205  	},
   206  	{
   207  		vcs:     "git",
   208  		path:    "github.com/rsc/vgotest1/submod",
   209  		rev:     "v1.0.4",
   210  		version: "v1.0.4",
   211  		name:    "8afe2b2efed96e0880ecd2a69b98a53b8c2738b6",
   212  		short:   "8afe2b2efed9",
   213  		time:    time.Date(2018, 2, 19, 23, 12, 7, 0, time.UTC),
   214  		gomod:   "module \"github.com/vgotest1/submod\" // submod/go.mod\n",
   215  		zip: []string{
   216  			"go.mod",
   217  			"pkg/p.go",
   218  			"LICENSE",
   219  		},
   220  		zipSum:      "h1:iMsJ/9uQsk6MnZNnJK311f11QiSlmN92Q2aSjCywuJY=",
   221  		zipFileHash: "95801bfa69c5197ae809af512946d22f22850068527cd78100ae3f176bc8043b",
   222  	},
   223  	{
   224  		vcs:     "git",
   225  		path:    "github.com/rsc/vgotest1",
   226  		rev:     "v1.1.0",
   227  		version: "v1.1.0",
   228  		name:    "b769f2de407a4db81af9c5de0a06016d60d2ea09",
   229  		short:   "b769f2de407a",
   230  		time:    time.Date(2018, 2, 19, 23, 13, 36, 0, time.UTC),
   231  		gomod:   "module \"github.com/rsc/vgotest1\" // root go.mod\nrequire \"github.com/rsc/vgotest1/submod\" v1.0.5\n",
   232  		zip: []string{
   233  			"LICENSE",
   234  			"README.md",
   235  			"go.mod",
   236  			"pkg/p.go",
   237  		},
   238  		zipSum:      "h1:M69k7q+8bQ+QUpHov45Z/NoR8rj3DsQJUnXLWvf01+Q=",
   239  		zipFileHash: "58af45fb248d320ea471f568e006379e2b8d71d6d1663f9b19b2e00fd9ac9265",
   240  	},
   241  	{
   242  		vcs:         "git",
   243  		path:        "github.com/rsc/vgotest1/v2",
   244  		rev:         "v2.0.1",
   245  		version:     "v2.0.1",
   246  		name:        "ea65f87c8f52c15ea68f3bdd9925ef17e20d91e9",
   247  		short:       "ea65f87c8f52",
   248  		time:        time.Date(2018, 2, 19, 23, 14, 23, 0, time.UTC),
   249  		gomod:       "module \"github.com/rsc/vgotest1/v2\" // root go.mod\n",
   250  		zipSum:      "h1:QmgYy/zt+uoWhDpcsgrSVzYFvKtBEjl5zT/FRz9GTzA=",
   251  		zipFileHash: "1aedf1546d322a0121879ddfd6d0e8bfbd916d2cafbeb538ddb440e04b04b9ef",
   252  	},
   253  	{
   254  		vcs:     "git",
   255  		path:    "github.com/rsc/vgotest1/v2",
   256  		rev:     "v2.0.3",
   257  		version: "v2.0.3",
   258  		name:    "f18795870fb14388a21ef3ebc1d75911c8694f31",
   259  		short:   "f18795870fb1",
   260  		time:    time.Date(2018, 2, 19, 23, 16, 4, 0, time.UTC),
   261  		err:     "github.com/rsc/vgotest1/v2/go.mod has non-.../v2 module path \"github.com/rsc/vgotest\" at revision v2.0.3",
   262  	},
   263  	{
   264  		vcs:     "git",
   265  		path:    "github.com/rsc/vgotest1/v2",
   266  		rev:     "v2.0.4",
   267  		version: "v2.0.4",
   268  		name:    "1f863feb76bc7029b78b21c5375644838962f88d",
   269  		short:   "1f863feb76bc",
   270  		time:    time.Date(2018, 2, 20, 0, 3, 38, 0, time.UTC),
   271  		err:     "github.com/rsc/vgotest1/go.mod and .../v2/go.mod both have .../v2 module paths at revision v2.0.4",
   272  	},
   273  	{
   274  		vcs:         "git",
   275  		path:        "github.com/rsc/vgotest1/v2",
   276  		rev:         "v2.0.5",
   277  		version:     "v2.0.5",
   278  		name:        "2f615117ce481c8efef46e0cc0b4b4dccfac8fea",
   279  		short:       "2f615117ce48",
   280  		time:        time.Date(2018, 2, 20, 0, 3, 59, 0, time.UTC),
   281  		gomod:       "module \"github.com/rsc/vgotest1/v2\" // v2/go.mod\n",
   282  		zipSum:      "h1:RIEb9q1SUSEQOzMn0zfl/LQxGFWlhWEAdeEguf1MLGU=",
   283  		zipFileHash: "7d92c2c328c5e9b0694101353705d5843746ec1d93a1e986d0da54c8a14dfe6d",
   284  	},
   285  	{
   286  		// redirect to github
   287  		vcs:         "git",
   288  		path:        "rsc.io/quote",
   289  		rev:         "v1.0.0",
   290  		version:     "v1.0.0",
   291  		name:        "f488df80bcdbd3e5bafdc24ad7d1e79e83edd7e6",
   292  		short:       "f488df80bcdb",
   293  		time:        time.Date(2018, 2, 14, 0, 45, 20, 0, time.UTC),
   294  		gomod:       "module \"rsc.io/quote\"\n",
   295  		zipSum:      "h1:haUSojyo3j2M9g7CEUFG8Na09dtn7QKxvPGaPVQdGwM=",
   296  		zipFileHash: "5c08ba2c09a364f93704aaa780e7504346102c6ef4fe1333a11f09904a732078",
   297  	},
   298  	{
   299  		// redirect to static hosting proxy
   300  		vcs:     "mod",
   301  		path:    "swtch.com/testmod",
   302  		rev:     "v1.0.0",
   303  		version: "v1.0.0",
   304  		// NO name or short - we intentionally ignore those in the proxy protocol
   305  		time:  time.Date(1972, 7, 18, 12, 34, 56, 0, time.UTC),
   306  		gomod: "module \"swtch.com/testmod\"\n",
   307  	},
   308  	{
   309  		// redirect to googlesource
   310  		vcs:         "git",
   311  		path:        "golang.org/x/text",
   312  		rev:         "4e4a3210bb",
   313  		version:     "v0.3.1-0.20180208041248-4e4a3210bb54",
   314  		name:        "4e4a3210bb54bb31f6ab2cdca2edcc0b50c420c1",
   315  		short:       "4e4a3210bb54",
   316  		time:        time.Date(2018, 2, 8, 4, 12, 48, 0, time.UTC),
   317  		zipSum:      "h1:Yxu6pHX9X2RECiuw/Q5/4uvajuaowck8zOFKXgbfNBk=",
   318  		zipFileHash: "ac2c165a5c10aa5a7545dea60a08e019270b982fa6c8bdcb5943931de64922fe",
   319  	},
   320  	{
   321  		vcs:         "git",
   322  		path:        "github.com/pkg/errors",
   323  		rev:         "v0.8.0",
   324  		version:     "v0.8.0",
   325  		name:        "645ef00459ed84a119197bfb8d8205042c6df63d",
   326  		short:       "645ef00459ed",
   327  		time:        time.Date(2016, 9, 29, 1, 48, 1, 0, time.UTC),
   328  		zipSum:      "h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=",
   329  		zipFileHash: "e4fa69ba057356614edbc1da881a7d3ebb688505be49f65965686bcb859e2fae",
   330  	},
   331  	{
   332  		// package in subdirectory - custom domain
   333  		// In general we can't reject these definitively in Lookup,
   334  		// but gopkg.in is special.
   335  		vcs:     "git",
   336  		path:    "gopkg.in/yaml.v2/abc",
   337  		lookErr: "invalid module path \"gopkg.in/yaml.v2/abc\"",
   338  	},
   339  	{
   340  		// package in subdirectory - github
   341  		// Because it's a package, Stat should fail entirely.
   342  		vcs:  "git",
   343  		path: "github.com/rsc/quote/buggy",
   344  		rev:  "c4d4236f",
   345  		err:  "missing github.com/rsc/quote/buggy/go.mod at revision c4d4236f9242",
   346  	},
   347  	{
   348  		vcs:         "git",
   349  		path:        "gopkg.in/yaml.v2",
   350  		rev:         "d670f940",
   351  		version:     "v2.0.0",
   352  		name:        "d670f9405373e636a5a2765eea47fac0c9bc91a4",
   353  		short:       "d670f9405373",
   354  		time:        time.Date(2018, 1, 9, 11, 43, 31, 0, time.UTC),
   355  		gomod:       "module gopkg.in/yaml.v2\n",
   356  		zipSum:      "h1:uUkhRGrsEyx/laRdeS6YIQKIys8pg+lRSRdVMTYjivs=",
   357  		zipFileHash: "7b0a141b1b0b49772ab4eecfd11dfd6609a94a5e868cab04a3abb1861ffaa877",
   358  	},
   359  	{
   360  		vcs:         "git",
   361  		path:        "gopkg.in/check.v1",
   362  		rev:         "20d25e280405",
   363  		version:     "v1.0.0-20161208181325-20d25e280405",
   364  		name:        "20d25e2804050c1cd24a7eea1e7a6447dd0e74ec",
   365  		short:       "20d25e280405",
   366  		time:        time.Date(2016, 12, 8, 18, 13, 25, 0, time.UTC),
   367  		gomod:       "module gopkg.in/check.v1\n",
   368  		zipSum:      "h1:829vOVxxusYHC+IqBtkX5mbKtsY9fheQiQn0MZRVLfQ=",
   369  		zipFileHash: "9e7cb3f4f1e66d722306442b0dbe1f6f43d74d1736d54c510537bdfb1d6f432f",
   370  	},
   371  	{
   372  		vcs:         "git",
   373  		path:        "vcs-test.golang.org/go/mod/gitrepo1",
   374  		rev:         "master",
   375  		version:     "v1.2.4-annotated",
   376  		name:        "ede458df7cd0fdca520df19a33158086a8a68e81",
   377  		short:       "ede458df7cd0",
   378  		time:        time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   379  		gomod:       "module vcs-test.golang.org/go/mod/gitrepo1\n",
   380  		zipSum:      "h1:YJYZRsM9BHFTlVr8YADjT0cJH8uFIDtoc5NLiVqZEx8=",
   381  		zipFileHash: "c15e49d58b7a4c37966cbe5bc01a0330cd5f2927e990e1839bda1d407766d9c5",
   382  	},
   383  	{
   384  		vcs:         "git",
   385  		path:        "gopkg.in/natefinch/lumberjack.v2",
   386  		rev:         "latest",
   387  		version:     "v2.0.0-20170531160350-a96e63847dc3",
   388  		name:        "a96e63847dc3c67d17befa69c303767e2f84e54f",
   389  		short:       "a96e63847dc3",
   390  		time:        time.Date(2017, 5, 31, 16, 3, 50, 0, time.UTC),
   391  		gomod:       "module gopkg.in/natefinch/lumberjack.v2\n",
   392  		zipSum:      "h1:AFxeG48hTWHhDTQDk/m2gorfVHUEa9vo3tp3D7TzwjI=",
   393  		zipFileHash: "b5de0da7bbbec76709eef1ac71b6c9ff423b9fbf3bb97b56743450d4937b06d5",
   394  	},
   395  	{
   396  		vcs:  "git",
   397  		path: "gopkg.in/natefinch/lumberjack.v2",
   398  		// This repo has a v2.1 tag.
   399  		// We only allow semver references to tags that are fully qualified, as in v2.1.0.
   400  		// Because we can't record v2.1.0 (the actual tag is v2.1), we record a pseudo-version
   401  		// instead, same as if the tag were any other non-version-looking string.
   402  		// We use a v2 pseudo-version here because of the .v2 in the path, not because
   403  		// of the v2 in the rev.
   404  		rev:     "v2.1", // non-canonical semantic version turns into pseudo-version
   405  		version: "v2.0.0-20170531160350-a96e63847dc3",
   406  		name:    "a96e63847dc3c67d17befa69c303767e2f84e54f",
   407  		short:   "a96e63847dc3",
   408  		time:    time.Date(2017, 5, 31, 16, 3, 50, 0, time.UTC),
   409  		gomod:   "module gopkg.in/natefinch/lumberjack.v2\n",
   410  	},
   411  	{
   412  		vcs:         "git",
   413  		path:        "vcs-test.golang.org/go/v2module/v2",
   414  		rev:         "v2.0.0",
   415  		version:     "v2.0.0",
   416  		name:        "203b91c896acd173aa719e4cdcb7d463c4b090fa",
   417  		short:       "203b91c896ac",
   418  		time:        time.Date(2019, 4, 3, 15, 52, 15, 0, time.UTC),
   419  		gomod:       "module vcs-test.golang.org/go/v2module/v2\n\ngo 1.12\n",
   420  		zipSum:      "h1:JItBZ+gwA5WvtZEGEbuDL4lUttGtLrs53lmdurq3bOg=",
   421  		zipFileHash: "9ea9ae1673cffcc44b7fdd3cc89953d68c102449b46c982dbf085e4f2e394da5",
   422  	},
   423  }
   424  
   425  func TestCodeRepo(t *testing.T) {
   426  	testenv.MustHaveExternalNetwork(t)
   427  
   428  	tmpdir, err := ioutil.TempDir("", "modfetch-test-")
   429  	if err != nil {
   430  		t.Fatal(err)
   431  	}
   432  	defer os.RemoveAll(tmpdir)
   433  
   434  	t.Run("parallel", func(t *testing.T) {
   435  		for _, tt := range codeRepoTests {
   436  			f := func(tt codeRepoTest) func(t *testing.T) {
   437  				return func(t *testing.T) {
   438  					t.Parallel()
   439  					if tt.vcs != "mod" {
   440  						testenv.MustHaveExecPath(t, tt.vcs)
   441  					}
   442  
   443  					repo, err := Lookup("direct", tt.path)
   444  					if tt.lookErr != "" {
   445  						if err != nil && err.Error() == tt.lookErr {
   446  							return
   447  						}
   448  						t.Errorf("Lookup(%q): %v, want error %q", tt.path, err, tt.lookErr)
   449  					}
   450  					if err != nil {
   451  						t.Fatalf("Lookup(%q): %v", tt.path, err)
   452  					}
   453  
   454  					if tt.mpath == "" {
   455  						tt.mpath = tt.path
   456  					}
   457  					if mpath := repo.ModulePath(); mpath != tt.mpath {
   458  						t.Errorf("repo.ModulePath() = %q, want %q", mpath, tt.mpath)
   459  					}
   460  
   461  					info, err := repo.Stat(tt.rev)
   462  					if err != nil {
   463  						if tt.err != "" {
   464  							if !strings.Contains(err.Error(), tt.err) {
   465  								t.Fatalf("repoStat(%q): %v, wanted %q", tt.rev, err, tt.err)
   466  							}
   467  							return
   468  						}
   469  						t.Fatalf("repo.Stat(%q): %v", tt.rev, err)
   470  					}
   471  					if tt.err != "" {
   472  						t.Errorf("repo.Stat(%q): success, wanted error", tt.rev)
   473  					}
   474  					if info.Version != tt.version {
   475  						t.Errorf("info.Version = %q, want %q", info.Version, tt.version)
   476  					}
   477  					if info.Name != tt.name {
   478  						t.Errorf("info.Name = %q, want %q", info.Name, tt.name)
   479  					}
   480  					if info.Short != tt.short {
   481  						t.Errorf("info.Short = %q, want %q", info.Short, tt.short)
   482  					}
   483  					if !info.Time.Equal(tt.time) {
   484  						t.Errorf("info.Time = %v, want %v", info.Time, tt.time)
   485  					}
   486  
   487  					if tt.gomod != "" || tt.gomodErr != "" {
   488  						data, err := repo.GoMod(tt.version)
   489  						if err != nil && tt.gomodErr == "" {
   490  							t.Errorf("repo.GoMod(%q): %v", tt.version, err)
   491  						} else if err != nil && tt.gomodErr != "" {
   492  							if err.Error() != tt.gomodErr {
   493  								t.Errorf("repo.GoMod(%q): %v, want %q", tt.version, err, tt.gomodErr)
   494  							}
   495  						} else if tt.gomodErr != "" {
   496  							t.Errorf("repo.GoMod(%q) = %q, want error %q", tt.version, data, tt.gomodErr)
   497  						} else if string(data) != tt.gomod {
   498  							t.Errorf("repo.GoMod(%q) = %q, want %q", tt.version, data, tt.gomod)
   499  						}
   500  					}
   501  
   502  					needHash := !testing.Short() && (tt.zipFileHash != "" || tt.zipSum != "")
   503  					if tt.zip != nil || tt.zipErr != "" || needHash {
   504  						f, err := ioutil.TempFile(tmpdir, tt.version+".zip.")
   505  						if err != nil {
   506  							t.Fatalf("ioutil.TempFile: %v", err)
   507  						}
   508  						zipfile := f.Name()
   509  						defer func() {
   510  							f.Close()
   511  							os.Remove(zipfile)
   512  						}()
   513  
   514  						var w io.Writer
   515  						var h hash.Hash
   516  						if needHash {
   517  							h = sha256.New()
   518  							w = io.MultiWriter(f, h)
   519  						} else {
   520  							w = f
   521  						}
   522  						err = repo.Zip(w, tt.version)
   523  						f.Close()
   524  						if err != nil {
   525  							if tt.zipErr != "" {
   526  								if err.Error() == tt.zipErr {
   527  									return
   528  								}
   529  								t.Fatalf("repo.Zip(%q): %v, want error %q", tt.version, err, tt.zipErr)
   530  							}
   531  							t.Fatalf("repo.Zip(%q): %v", tt.version, err)
   532  						}
   533  						if tt.zipErr != "" {
   534  							t.Errorf("repo.Zip(%q): success, want error %q", tt.version, tt.zipErr)
   535  						}
   536  
   537  						if tt.zip != nil {
   538  							prefix := tt.path + "@" + tt.version + "/"
   539  							z, err := zip.OpenReader(zipfile)
   540  							if err != nil {
   541  								t.Fatalf("open zip %s: %v", zipfile, err)
   542  							}
   543  							var names []string
   544  							for _, file := range z.File {
   545  								if !strings.HasPrefix(file.Name, prefix) {
   546  									t.Errorf("zip entry %v does not start with prefix %v", file.Name, prefix)
   547  									continue
   548  								}
   549  								names = append(names, file.Name[len(prefix):])
   550  							}
   551  							z.Close()
   552  							if !reflect.DeepEqual(names, tt.zip) {
   553  								t.Fatalf("zip = %v\nwant %v\n", names, tt.zip)
   554  							}
   555  						}
   556  
   557  						if needHash {
   558  							sum, err := dirhash.HashZip(zipfile, dirhash.Hash1)
   559  							if err != nil {
   560  								t.Errorf("repo.Zip(%q): %v", tt.version, err)
   561  							} else if sum != tt.zipSum {
   562  								t.Errorf("repo.Zip(%q): got file with sum %q, want %q", tt.version, sum, tt.zipSum)
   563  							} else if zipFileHash := hex.EncodeToString(h.Sum(nil)); zipFileHash != tt.zipFileHash {
   564  								t.Errorf("repo.Zip(%q): got file with hash %q, want %q (but content has correct sum)", tt.version, zipFileHash, tt.zipFileHash)
   565  							}
   566  						}
   567  					}
   568  				}
   569  			}
   570  			t.Run(strings.ReplaceAll(tt.path, "/", "_")+"/"+tt.rev, f(tt))
   571  			if strings.HasPrefix(tt.path, vgotest1git) {
   572  				for vcs, alt := range altVgotests {
   573  					altTest := tt
   574  					altTest.vcs = vcs
   575  					altTest.path = alt + strings.TrimPrefix(altTest.path, vgotest1git)
   576  					if strings.HasPrefix(altTest.mpath, vgotest1git) {
   577  						altTest.mpath = alt + strings.TrimPrefix(altTest.mpath, vgotest1git)
   578  					}
   579  					var m map[string]string
   580  					if alt == vgotest1hg {
   581  						m = hgmap
   582  					}
   583  					altTest.version = remap(altTest.version, m)
   584  					altTest.name = remap(altTest.name, m)
   585  					altTest.short = remap(altTest.short, m)
   586  					altTest.rev = remap(altTest.rev, m)
   587  					altTest.err = remap(altTest.err, m)
   588  					altTest.gomodErr = remap(altTest.gomodErr, m)
   589  					altTest.zipErr = remap(altTest.zipErr, m)
   590  					altTest.zipSum = ""
   591  					altTest.zipFileHash = ""
   592  					t.Run(strings.ReplaceAll(altTest.path, "/", "_")+"/"+altTest.rev, f(altTest))
   593  				}
   594  			}
   595  		}
   596  	})
   597  }
   598  
   599  var hgmap = map[string]string{
   600  	"github.com/rsc/vgotest1":                  "vcs-test.golang.org/hg/vgotest1.hg",
   601  	"f18795870fb14388a21ef3ebc1d75911c8694f31": "a9ad6d1d14eb544f459f446210c7eb3b009807c6",
   602  	"ea65f87c8f52c15ea68f3bdd9925ef17e20d91e9": "f1fc0f22021b638d073d31c752847e7bf385def7",
   603  	"b769f2de407a4db81af9c5de0a06016d60d2ea09": "92c7eb888b4fac17f1c6bd2e1060a1b881a3b832",
   604  	"8afe2b2efed96e0880ecd2a69b98a53b8c2738b6": "4e58084d459ae7e79c8c2264d0e8e9a92eb5cd44",
   605  	"2f615117ce481c8efef46e0cc0b4b4dccfac8fea": "879ea98f7743c8eff54f59a918f3a24123d1cf46",
   606  	"80d85c5d4d17598a0e9055e7c175a32b415d6128": "e125018e286a4b09061079a81e7b537070b7ff71",
   607  	"1f863feb76bc7029b78b21c5375644838962f88d": "bf63880162304a9337477f3858f5b7e255c75459",
   608  	"45f53230a74ad275c7127e117ac46914c8126160": "814fce58e83abd5bf2a13892e0b0e1198abefcd4",
   609  }
   610  
   611  func remap(name string, m map[string]string) string {
   612  	if m[name] != "" {
   613  		return m[name]
   614  	}
   615  	if codehost.AllHex(name) {
   616  		for k, v := range m {
   617  			if strings.HasPrefix(k, name) {
   618  				return v[:len(name)]
   619  			}
   620  		}
   621  	}
   622  	for k, v := range m {
   623  		name = strings.ReplaceAll(name, k, v)
   624  		if codehost.AllHex(k) {
   625  			name = strings.ReplaceAll(name, k[:12], v[:12])
   626  		}
   627  	}
   628  	return name
   629  }
   630  
   631  var codeRepoVersionsTests = []struct {
   632  	vcs      string
   633  	path     string
   634  	prefix   string
   635  	versions []string
   636  }{
   637  	{
   638  		vcs:      "git",
   639  		path:     "github.com/rsc/vgotest1",
   640  		versions: []string{"v0.0.0", "v0.0.1", "v1.0.0", "v1.0.1", "v1.0.2", "v1.0.3", "v1.1.0"},
   641  	},
   642  	{
   643  		vcs:      "git",
   644  		path:     "github.com/rsc/vgotest1",
   645  		prefix:   "v1.0",
   646  		versions: []string{"v1.0.0", "v1.0.1", "v1.0.2", "v1.0.3"},
   647  	},
   648  	{
   649  		vcs:      "git",
   650  		path:     "github.com/rsc/vgotest1/v2",
   651  		versions: []string{"v2.0.0", "v2.0.1", "v2.0.2", "v2.0.3", "v2.0.4", "v2.0.5", "v2.0.6"},
   652  	},
   653  	{
   654  		vcs:      "mod",
   655  		path:     "swtch.com/testmod",
   656  		versions: []string{"v1.0.0", "v1.1.1"},
   657  	},
   658  	{
   659  		vcs:      "git",
   660  		path:     "gopkg.in/russross/blackfriday.v2",
   661  		versions: []string{"v2.0.0", "v2.0.1"},
   662  	},
   663  	{
   664  		vcs:      "git",
   665  		path:     "gopkg.in/natefinch/lumberjack.v2",
   666  		versions: []string{"v2.0.0"},
   667  	},
   668  }
   669  
   670  func TestCodeRepoVersions(t *testing.T) {
   671  	testenv.MustHaveExternalNetwork(t)
   672  
   673  	tmpdir, err := ioutil.TempDir("", "vgo-modfetch-test-")
   674  	if err != nil {
   675  		t.Fatal(err)
   676  	}
   677  	defer os.RemoveAll(tmpdir)
   678  
   679  	t.Run("parallel", func(t *testing.T) {
   680  		for _, tt := range codeRepoVersionsTests {
   681  			t.Run(strings.ReplaceAll(tt.path, "/", "_"), func(t *testing.T) {
   682  				tt := tt
   683  				t.Parallel()
   684  				if tt.vcs != "mod" {
   685  					testenv.MustHaveExecPath(t, tt.vcs)
   686  				}
   687  
   688  				repo, err := Lookup("direct", tt.path)
   689  				if err != nil {
   690  					t.Fatalf("Lookup(%q): %v", tt.path, err)
   691  				}
   692  				list, err := repo.Versions(tt.prefix)
   693  				if err != nil {
   694  					t.Fatalf("Versions(%q): %v", tt.prefix, err)
   695  				}
   696  				if !reflect.DeepEqual(list, tt.versions) {
   697  					t.Fatalf("Versions(%q):\nhave %v\nwant %v", tt.prefix, list, tt.versions)
   698  				}
   699  			})
   700  		}
   701  	})
   702  }
   703  
   704  var latestTests = []struct {
   705  	vcs     string
   706  	path    string
   707  	version string
   708  	err     string
   709  }{
   710  	{
   711  		vcs:  "git",
   712  		path: "github.com/rsc/empty",
   713  		err:  "no commits",
   714  	},
   715  	{
   716  		vcs:  "git",
   717  		path: "github.com/rsc/vgotest1",
   718  		err:  `github.com/rsc/vgotest1@v0.0.0-20180219223237-a08abb797a67: invalid version: go.mod has post-v0 module path "github.com/vgotest1/v2" at revision a08abb797a67`,
   719  	},
   720  	{
   721  		vcs:  "git",
   722  		path: "github.com/rsc/vgotest1/v2",
   723  		err:  `github.com/rsc/vgotest1/v2@v2.0.0-20180219223237-a08abb797a67: invalid version: github.com/rsc/vgotest1/go.mod and .../v2/go.mod both have .../v2 module paths at revision a08abb797a67`,
   724  	},
   725  	{
   726  		vcs:  "git",
   727  		path: "github.com/rsc/vgotest1/subdir",
   728  		err:  "github.com/rsc/vgotest1/subdir@v0.0.0-20180219223237-a08abb797a67: invalid version: missing github.com/rsc/vgotest1/subdir/go.mod at revision a08abb797a67",
   729  	},
   730  	{
   731  		vcs:     "git",
   732  		path:    "vcs-test.golang.org/git/commit-after-tag.git",
   733  		version: "v1.0.1-0.20190715211727-b325d8217783",
   734  	},
   735  	{
   736  		vcs:     "git",
   737  		path:    "vcs-test.golang.org/git/no-tags.git",
   738  		version: "v0.0.0-20190715212047-e706ba1d9f6d",
   739  	},
   740  	{
   741  		vcs:     "mod",
   742  		path:    "swtch.com/testmod",
   743  		version: "v1.1.1",
   744  	},
   745  }
   746  
   747  func TestLatest(t *testing.T) {
   748  	testenv.MustHaveExternalNetwork(t)
   749  
   750  	tmpdir, err := ioutil.TempDir("", "vgo-modfetch-test-")
   751  	if err != nil {
   752  		t.Fatal(err)
   753  	}
   754  	defer os.RemoveAll(tmpdir)
   755  
   756  	t.Run("parallel", func(t *testing.T) {
   757  		for _, tt := range latestTests {
   758  			name := strings.ReplaceAll(tt.path, "/", "_")
   759  			t.Run(name, func(t *testing.T) {
   760  				tt := tt
   761  				t.Parallel()
   762  				if tt.vcs != "mod" {
   763  					testenv.MustHaveExecPath(t, tt.vcs)
   764  				}
   765  
   766  				repo, err := Lookup("direct", tt.path)
   767  				if err != nil {
   768  					t.Fatalf("Lookup(%q): %v", tt.path, err)
   769  				}
   770  				info, err := repo.Latest()
   771  				if err != nil {
   772  					if tt.err != "" {
   773  						if err.Error() == tt.err {
   774  							return
   775  						}
   776  						t.Fatalf("Latest(): %v, want %q", err, tt.err)
   777  					}
   778  					t.Fatalf("Latest(): %v", err)
   779  				}
   780  				if tt.err != "" {
   781  					t.Fatalf("Latest() = %v, want error %q", info.Version, tt.err)
   782  				}
   783  				if info.Version != tt.version {
   784  					t.Fatalf("Latest() = %v, want %v", info.Version, tt.version)
   785  				}
   786  			})
   787  		}
   788  	})
   789  }
   790  
   791  // fixedTagsRepo is a fake codehost.Repo that returns a fixed list of tags
   792  type fixedTagsRepo struct {
   793  	tags []string
   794  	codehost.Repo
   795  }
   796  
   797  func (ch *fixedTagsRepo) Tags(string) ([]string, error) { return ch.tags, nil }
   798  
   799  func TestNonCanonicalSemver(t *testing.T) {
   800  	root := "golang.org/x/issue24476"
   801  	ch := &fixedTagsRepo{
   802  		tags: []string{
   803  			"", "huh?", "1.0.1",
   804  			// what about "version 1 dot dogcow"?
   805  			"v1.🐕.🐄",
   806  			"v1", "v0.1",
   807  			// and one normal one that should pass through
   808  			"v1.0.1",
   809  		},
   810  	}
   811  
   812  	cr, err := newCodeRepo(ch, root, root)
   813  	if err != nil {
   814  		t.Fatal(err)
   815  	}
   816  
   817  	v, err := cr.Versions("")
   818  	if err != nil {
   819  		t.Fatal(err)
   820  	}
   821  	if len(v) != 1 || v[0] != "v1.0.1" {
   822  		t.Fatal("unexpected versions returned:", v)
   823  	}
   824  }