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