Source file src/cmd/go/internal/modfetch/coderepo_test.go

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

View as plain text