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

View as plain text