github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/cmd/go/internal/modfetch/coderepo_test.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package modfetch
     6  
     7  import (
     8  	"archive/zip"
     9  	"internal/testenv"
    10  	"io"
    11  	"io/ioutil"
    12  	"log"
    13  	"os"
    14  	"reflect"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	"cmd/go/internal/modfetch/codehost"
    20  )
    21  
    22  func TestMain(m *testing.M) {
    23  	os.Exit(testMain(m))
    24  }
    25  
    26  func testMain(m *testing.M) int {
    27  	dir, err := ioutil.TempDir("", "gitrepo-test-")
    28  	if err != nil {
    29  		log.Fatal(err)
    30  	}
    31  	defer os.RemoveAll(dir)
    32  
    33  	codehost.WorkRoot = dir
    34  	return m.Run()
    35  }
    36  
    37  const (
    38  	vgotest1git = "github.com/rsc/vgotest1"
    39  	vgotest1hg  = "vcs-test.golang.org/hg/vgotest1.hg"
    40  )
    41  
    42  var altVgotests = []string{
    43  	vgotest1hg,
    44  }
    45  
    46  var codeRepoTests = []struct {
    47  	path     string
    48  	lookerr  string
    49  	mpath    string
    50  	rev      string
    51  	err      string
    52  	version  string
    53  	name     string
    54  	short    string
    55  	time     time.Time
    56  	gomod    string
    57  	gomoderr string
    58  	zip      []string
    59  	ziperr   string
    60  }{
    61  	{
    62  		path:    "github.com/rsc/vgotest1",
    63  		rev:     "v0.0.0",
    64  		version: "v0.0.0",
    65  		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
    66  		short:   "80d85c5d4d17",
    67  		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
    68  		zip: []string{
    69  			"LICENSE",
    70  			"README.md",
    71  			"pkg/p.go",
    72  		},
    73  	},
    74  	{
    75  		path:    "github.com/rsc/vgotest1",
    76  		rev:     "v1.0.0",
    77  		version: "v1.0.0",
    78  		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
    79  		short:   "80d85c5d4d17",
    80  		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
    81  		zip: []string{
    82  			"LICENSE",
    83  			"README.md",
    84  			"pkg/p.go",
    85  		},
    86  	},
    87  	{
    88  		path:    "github.com/rsc/vgotest1/v2",
    89  		rev:     "v2.0.0",
    90  		version: "v2.0.0",
    91  		name:    "45f53230a74ad275c7127e117ac46914c8126160",
    92  		short:   "45f53230a74a",
    93  		time:    time.Date(2018, 7, 19, 1, 21, 27, 0, time.UTC),
    94  		ziperr:  "missing github.com/rsc/vgotest1/go.mod and .../v2/go.mod at revision v2.0.0",
    95  	},
    96  	{
    97  		path:    "github.com/rsc/vgotest1",
    98  		rev:     "80d85c5",
    99  		version: "v1.0.0",
   100  		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
   101  		short:   "80d85c5d4d17",
   102  		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
   103  		zip: []string{
   104  			"LICENSE",
   105  			"README.md",
   106  			"pkg/p.go",
   107  		},
   108  	},
   109  	{
   110  		path:    "github.com/rsc/vgotest1",
   111  		rev:     "mytag",
   112  		version: "v1.0.0",
   113  		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
   114  		short:   "80d85c5d4d17",
   115  		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
   116  		zip: []string{
   117  			"LICENSE",
   118  			"README.md",
   119  			"pkg/p.go",
   120  		},
   121  	},
   122  	{
   123  		path:     "github.com/rsc/vgotest1/v2",
   124  		rev:      "45f53230a",
   125  		version:  "v2.0.0",
   126  		name:     "45f53230a74ad275c7127e117ac46914c8126160",
   127  		short:    "45f53230a74a",
   128  		time:     time.Date(2018, 7, 19, 1, 21, 27, 0, time.UTC),
   129  		gomoderr: "missing github.com/rsc/vgotest1/go.mod and .../v2/go.mod at revision v2.0.0",
   130  		ziperr:   "missing github.com/rsc/vgotest1/go.mod and .../v2/go.mod at revision v2.0.0",
   131  	},
   132  	{
   133  		path:    "github.com/rsc/vgotest1/v54321",
   134  		rev:     "80d85c5",
   135  		version: "v54321.0.0-20180219231006-80d85c5d4d17",
   136  		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
   137  		short:   "80d85c5d4d17",
   138  		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
   139  		ziperr:  "missing github.com/rsc/vgotest1/go.mod and .../v54321/go.mod at revision 80d85c5d4d17",
   140  	},
   141  	{
   142  		path: "github.com/rsc/vgotest1/submod",
   143  		rev:  "v1.0.0",
   144  		err:  "unknown revision submod/v1.0.0",
   145  	},
   146  	{
   147  		path: "github.com/rsc/vgotest1/submod",
   148  		rev:  "v1.0.3",
   149  		err:  "unknown revision submod/v1.0.3",
   150  	},
   151  	{
   152  		path:    "github.com/rsc/vgotest1/submod",
   153  		rev:     "v1.0.4",
   154  		version: "v1.0.4",
   155  		name:    "8afe2b2efed96e0880ecd2a69b98a53b8c2738b6",
   156  		short:   "8afe2b2efed9",
   157  		time:    time.Date(2018, 2, 19, 23, 12, 7, 0, time.UTC),
   158  		gomod:   "module \"github.com/vgotest1/submod\" // submod/go.mod\n",
   159  		zip: []string{
   160  			"go.mod",
   161  			"pkg/p.go",
   162  			"LICENSE",
   163  		},
   164  	},
   165  	{
   166  		path:    "github.com/rsc/vgotest1",
   167  		rev:     "v1.1.0",
   168  		version: "v1.1.0",
   169  		name:    "b769f2de407a4db81af9c5de0a06016d60d2ea09",
   170  		short:   "b769f2de407a",
   171  		time:    time.Date(2018, 2, 19, 23, 13, 36, 0, time.UTC),
   172  		gomod:   "module \"github.com/rsc/vgotest1\" // root go.mod\nrequire \"github.com/rsc/vgotest1/submod\" v1.0.5\n",
   173  		zip: []string{
   174  			"LICENSE",
   175  			"README.md",
   176  			"go.mod",
   177  			"pkg/p.go",
   178  		},
   179  	},
   180  	{
   181  		path:    "github.com/rsc/vgotest1/v2",
   182  		rev:     "v2.0.1",
   183  		version: "v2.0.1",
   184  		name:    "ea65f87c8f52c15ea68f3bdd9925ef17e20d91e9",
   185  		short:   "ea65f87c8f52",
   186  		time:    time.Date(2018, 2, 19, 23, 14, 23, 0, time.UTC),
   187  		gomod:   "module \"github.com/rsc/vgotest1/v2\" // root go.mod\n",
   188  	},
   189  	{
   190  		path:     "github.com/rsc/vgotest1/v2",
   191  		rev:      "v2.0.3",
   192  		version:  "v2.0.3",
   193  		name:     "f18795870fb14388a21ef3ebc1d75911c8694f31",
   194  		short:    "f18795870fb1",
   195  		time:     time.Date(2018, 2, 19, 23, 16, 4, 0, time.UTC),
   196  		gomoderr: "github.com/rsc/vgotest1/v2/go.mod has non-.../v2 module path \"github.com/rsc/vgotest\" at revision v2.0.3",
   197  	},
   198  	{
   199  		path:     "github.com/rsc/vgotest1/v2",
   200  		rev:      "v2.0.4",
   201  		version:  "v2.0.4",
   202  		name:     "1f863feb76bc7029b78b21c5375644838962f88d",
   203  		short:    "1f863feb76bc",
   204  		time:     time.Date(2018, 2, 20, 0, 3, 38, 0, time.UTC),
   205  		gomoderr: "github.com/rsc/vgotest1/go.mod and .../v2/go.mod both have .../v2 module paths at revision v2.0.4",
   206  	},
   207  	{
   208  		path:    "github.com/rsc/vgotest1/v2",
   209  		rev:     "v2.0.5",
   210  		version: "v2.0.5",
   211  		name:    "2f615117ce481c8efef46e0cc0b4b4dccfac8fea",
   212  		short:   "2f615117ce48",
   213  		time:    time.Date(2018, 2, 20, 0, 3, 59, 0, time.UTC),
   214  		gomod:   "module \"github.com/rsc/vgotest1/v2\" // v2/go.mod\n",
   215  	},
   216  	{
   217  		// redirect to github
   218  		path:    "rsc.io/quote",
   219  		rev:     "v1.0.0",
   220  		version: "v1.0.0",
   221  		name:    "f488df80bcdbd3e5bafdc24ad7d1e79e83edd7e6",
   222  		short:   "f488df80bcdb",
   223  		time:    time.Date(2018, 2, 14, 0, 45, 20, 0, time.UTC),
   224  		gomod:   "module \"rsc.io/quote\"\n",
   225  	},
   226  	{
   227  		// redirect to static hosting proxy
   228  		path:    "swtch.com/testmod",
   229  		rev:     "v1.0.0",
   230  		version: "v1.0.0",
   231  		// NO name or short - we intentionally ignore those in the proxy protocol
   232  		time:  time.Date(1972, 7, 18, 12, 34, 56, 0, time.UTC),
   233  		gomod: "module \"swtch.com/testmod\"\n",
   234  	},
   235  	{
   236  		// redirect to googlesource
   237  		path:    "golang.org/x/text",
   238  		rev:     "4e4a3210bb",
   239  		version: "v0.3.1-0.20180208041248-4e4a3210bb54",
   240  		name:    "4e4a3210bb54bb31f6ab2cdca2edcc0b50c420c1",
   241  		short:   "4e4a3210bb54",
   242  		time:    time.Date(2018, 2, 8, 4, 12, 48, 0, time.UTC),
   243  	},
   244  	{
   245  		path:    "github.com/pkg/errors",
   246  		rev:     "v0.8.0",
   247  		version: "v0.8.0",
   248  		name:    "645ef00459ed84a119197bfb8d8205042c6df63d",
   249  		short:   "645ef00459ed",
   250  		time:    time.Date(2016, 9, 29, 1, 48, 1, 0, time.UTC),
   251  	},
   252  	{
   253  		// package in subdirectory - custom domain
   254  		// In general we can't reject these definitively in Lookup,
   255  		// but gopkg.in is special.
   256  		path:    "gopkg.in/yaml.v2/abc",
   257  		lookerr: "invalid module path \"gopkg.in/yaml.v2/abc\"",
   258  	},
   259  	{
   260  		// package in subdirectory - github
   261  		// Because it's a package, Stat should fail entirely.
   262  		path: "github.com/rsc/quote/buggy",
   263  		rev:  "c4d4236f",
   264  		err:  "missing github.com/rsc/quote/buggy/go.mod at revision c4d4236f9242",
   265  	},
   266  	{
   267  		path:    "gopkg.in/yaml.v2",
   268  		rev:     "d670f940",
   269  		version: "v2.0.0",
   270  		name:    "d670f9405373e636a5a2765eea47fac0c9bc91a4",
   271  		short:   "d670f9405373",
   272  		time:    time.Date(2018, 1, 9, 11, 43, 31, 0, time.UTC),
   273  		gomod:   "module gopkg.in/yaml.v2\n",
   274  	},
   275  	{
   276  		path:    "gopkg.in/check.v1",
   277  		rev:     "20d25e280405",
   278  		version: "v1.0.0-20161208181325-20d25e280405",
   279  		name:    "20d25e2804050c1cd24a7eea1e7a6447dd0e74ec",
   280  		short:   "20d25e280405",
   281  		time:    time.Date(2016, 12, 8, 18, 13, 25, 0, time.UTC),
   282  		gomod:   "module gopkg.in/check.v1\n",
   283  	},
   284  	{
   285  		path:    "gopkg.in/yaml.v2",
   286  		rev:     "v2",
   287  		version: "v2.2.1",
   288  		name:    "5420a8b6744d3b0345ab293f6fcba19c978f1183",
   289  		short:   "5420a8b6744d",
   290  		time:    time.Date(2018, 3, 28, 19, 50, 20, 0, time.UTC),
   291  		gomod:   "module \"gopkg.in/yaml.v2\"\n\nrequire (\n\t\"gopkg.in/check.v1\" v0.0.0-20161208181325-20d25e280405\n)\n",
   292  	},
   293  	{
   294  		path:    "vcs-test.golang.org/go/mod/gitrepo1",
   295  		rev:     "master",
   296  		version: "v1.2.4-annotated",
   297  		name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
   298  		short:   "ede458df7cd0",
   299  		time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   300  		gomod:   "module vcs-test.golang.org/go/mod/gitrepo1\n",
   301  	},
   302  	{
   303  		path:    "gopkg.in/natefinch/lumberjack.v2",
   304  		rev:     "latest",
   305  		version: "v2.0.0-20170531160350-a96e63847dc3",
   306  		name:    "a96e63847dc3c67d17befa69c303767e2f84e54f",
   307  		short:   "a96e63847dc3",
   308  		time:    time.Date(2017, 5, 31, 16, 3, 50, 0, time.UTC),
   309  		gomod:   "module gopkg.in/natefinch/lumberjack.v2\n",
   310  	},
   311  	{
   312  		path: "gopkg.in/natefinch/lumberjack.v2",
   313  		// This repo has a v2.1 tag.
   314  		// We only allow semver references to tags that are fully qualified, as in v2.1.0.
   315  		// Because we can't record v2.1.0 (the actual tag is v2.1), we record a pseudo-version
   316  		// instead, same as if the tag were any other non-version-looking string.
   317  		// We use a v2 pseudo-version here because of the .v2 in the path, not because
   318  		// of the v2 in the rev.
   319  		rev:     "v2.1", // non-canonical semantic version turns into pseudo-version
   320  		version: "v2.0.0-20170531160350-a96e63847dc3",
   321  		name:    "a96e63847dc3c67d17befa69c303767e2f84e54f",
   322  		short:   "a96e63847dc3",
   323  		time:    time.Date(2017, 5, 31, 16, 3, 50, 0, time.UTC),
   324  		gomod:   "module gopkg.in/natefinch/lumberjack.v2\n",
   325  	},
   326  }
   327  
   328  func TestCodeRepo(t *testing.T) {
   329  	testenv.MustHaveExternalNetwork(t)
   330  
   331  	tmpdir, err := ioutil.TempDir("", "vgo-modfetch-test-")
   332  	if err != nil {
   333  		t.Fatal(err)
   334  	}
   335  	defer os.RemoveAll(tmpdir)
   336  	for _, tt := range codeRepoTests {
   337  		f := func(t *testing.T) {
   338  			repo, err := Lookup(tt.path)
   339  			if tt.lookerr != "" {
   340  				if err != nil && err.Error() == tt.lookerr {
   341  					return
   342  				}
   343  				t.Errorf("Lookup(%q): %v, want error %q", tt.path, err, tt.lookerr)
   344  			}
   345  			if err != nil {
   346  				t.Fatalf("Lookup(%q): %v", tt.path, err)
   347  			}
   348  			if tt.mpath == "" {
   349  				tt.mpath = tt.path
   350  			}
   351  			if mpath := repo.ModulePath(); mpath != tt.mpath {
   352  				t.Errorf("repo.ModulePath() = %q, want %q", mpath, tt.mpath)
   353  			}
   354  			info, err := repo.Stat(tt.rev)
   355  			if err != nil {
   356  				if tt.err != "" {
   357  					if !strings.Contains(err.Error(), tt.err) {
   358  						t.Fatalf("repoStat(%q): %v, wanted %q", tt.rev, err, tt.err)
   359  					}
   360  					return
   361  				}
   362  				t.Fatalf("repo.Stat(%q): %v", tt.rev, err)
   363  			}
   364  			if tt.err != "" {
   365  				t.Errorf("repo.Stat(%q): success, wanted error", tt.rev)
   366  			}
   367  			if info.Version != tt.version {
   368  				t.Errorf("info.Version = %q, want %q", info.Version, tt.version)
   369  			}
   370  			if info.Name != tt.name {
   371  				t.Errorf("info.Name = %q, want %q", info.Name, tt.name)
   372  			}
   373  			if info.Short != tt.short {
   374  				t.Errorf("info.Short = %q, want %q", info.Short, tt.short)
   375  			}
   376  			if !info.Time.Equal(tt.time) {
   377  				t.Errorf("info.Time = %v, want %v", info.Time, tt.time)
   378  			}
   379  			if tt.gomod != "" || tt.gomoderr != "" {
   380  				data, err := repo.GoMod(tt.version)
   381  				if err != nil && tt.gomoderr == "" {
   382  					t.Errorf("repo.GoMod(%q): %v", tt.version, err)
   383  				} else if err != nil && tt.gomoderr != "" {
   384  					if err.Error() != tt.gomoderr {
   385  						t.Errorf("repo.GoMod(%q): %v, want %q", tt.version, err, tt.gomoderr)
   386  					}
   387  				} else if tt.gomoderr != "" {
   388  					t.Errorf("repo.GoMod(%q) = %q, want error %q", tt.version, data, tt.gomoderr)
   389  				} else if string(data) != tt.gomod {
   390  					t.Errorf("repo.GoMod(%q) = %q, want %q", tt.version, data, tt.gomod)
   391  				}
   392  			}
   393  			if tt.zip != nil || tt.ziperr != "" {
   394  				zipfile, err := repo.Zip(tt.version, tmpdir)
   395  				if err != nil {
   396  					if tt.ziperr != "" {
   397  						if err.Error() == tt.ziperr {
   398  							return
   399  						}
   400  						t.Fatalf("repo.Zip(%q): %v, want error %q", tt.version, err, tt.ziperr)
   401  					}
   402  					t.Fatalf("repo.Zip(%q): %v", tt.version, err)
   403  				}
   404  				if tt.ziperr != "" {
   405  					t.Errorf("repo.Zip(%q): success, want error %q", tt.version, tt.ziperr)
   406  				}
   407  				prefix := tt.path + "@" + tt.version + "/"
   408  				z, err := zip.OpenReader(zipfile)
   409  				if err != nil {
   410  					t.Fatalf("open zip %s: %v", zipfile, err)
   411  				}
   412  				var names []string
   413  				for _, file := range z.File {
   414  					if !strings.HasPrefix(file.Name, prefix) {
   415  						t.Errorf("zip entry %v does not start with prefix %v", file.Name, prefix)
   416  						continue
   417  					}
   418  					names = append(names, file.Name[len(prefix):])
   419  				}
   420  				z.Close()
   421  				if !reflect.DeepEqual(names, tt.zip) {
   422  					t.Fatalf("zip = %v\nwant %v\n", names, tt.zip)
   423  				}
   424  			}
   425  		}
   426  		t.Run(strings.ReplaceAll(tt.path, "/", "_")+"/"+tt.rev, f)
   427  		if strings.HasPrefix(tt.path, vgotest1git) {
   428  			for _, alt := range altVgotests {
   429  				// Note: Communicating with f through tt; should be cleaned up.
   430  				old := tt
   431  				tt.path = alt + strings.TrimPrefix(tt.path, vgotest1git)
   432  				if strings.HasPrefix(tt.mpath, vgotest1git) {
   433  					tt.mpath = alt + strings.TrimPrefix(tt.mpath, vgotest1git)
   434  				}
   435  				var m map[string]string
   436  				if alt == vgotest1hg {
   437  					m = hgmap
   438  				}
   439  				tt.version = remap(tt.version, m)
   440  				tt.name = remap(tt.name, m)
   441  				tt.short = remap(tt.short, m)
   442  				tt.rev = remap(tt.rev, m)
   443  				tt.gomoderr = remap(tt.gomoderr, m)
   444  				tt.ziperr = remap(tt.ziperr, m)
   445  				t.Run(strings.ReplaceAll(tt.path, "/", "_")+"/"+tt.rev, f)
   446  				tt = old
   447  			}
   448  		}
   449  	}
   450  }
   451  
   452  var hgmap = map[string]string{
   453  	"github.com/rsc/vgotest1/":                 "vcs-test.golang.org/hg/vgotest1.hg/",
   454  	"f18795870fb14388a21ef3ebc1d75911c8694f31": "a9ad6d1d14eb544f459f446210c7eb3b009807c6",
   455  	"ea65f87c8f52c15ea68f3bdd9925ef17e20d91e9": "f1fc0f22021b638d073d31c752847e7bf385def7",
   456  	"b769f2de407a4db81af9c5de0a06016d60d2ea09": "92c7eb888b4fac17f1c6bd2e1060a1b881a3b832",
   457  	"8afe2b2efed96e0880ecd2a69b98a53b8c2738b6": "4e58084d459ae7e79c8c2264d0e8e9a92eb5cd44",
   458  	"2f615117ce481c8efef46e0cc0b4b4dccfac8fea": "879ea98f7743c8eff54f59a918f3a24123d1cf46",
   459  	"80d85c5d4d17598a0e9055e7c175a32b415d6128": "e125018e286a4b09061079a81e7b537070b7ff71",
   460  	"1f863feb76bc7029b78b21c5375644838962f88d": "bf63880162304a9337477f3858f5b7e255c75459",
   461  	"45f53230a74ad275c7127e117ac46914c8126160": "814fce58e83abd5bf2a13892e0b0e1198abefcd4",
   462  }
   463  
   464  func remap(name string, m map[string]string) string {
   465  	if m[name] != "" {
   466  		return m[name]
   467  	}
   468  	if codehost.AllHex(name) {
   469  		for k, v := range m {
   470  			if strings.HasPrefix(k, name) {
   471  				return v[:len(name)]
   472  			}
   473  		}
   474  	}
   475  	for k, v := range m {
   476  		name = strings.ReplaceAll(name, k, v)
   477  		if codehost.AllHex(k) {
   478  			name = strings.ReplaceAll(name, k[:12], v[:12])
   479  		}
   480  	}
   481  	return name
   482  }
   483  
   484  var codeRepoVersionsTests = []struct {
   485  	path     string
   486  	prefix   string
   487  	versions []string
   488  }{
   489  	{
   490  		path:     "github.com/rsc/vgotest1",
   491  		versions: []string{"v0.0.0", "v0.0.1", "v1.0.0", "v1.0.1", "v1.0.2", "v1.0.3", "v1.1.0", "v2.0.0+incompatible"},
   492  	},
   493  	{
   494  		path:     "github.com/rsc/vgotest1",
   495  		prefix:   "v1.0",
   496  		versions: []string{"v1.0.0", "v1.0.1", "v1.0.2", "v1.0.3"},
   497  	},
   498  	{
   499  		path:     "github.com/rsc/vgotest1/v2",
   500  		versions: []string{"v2.0.0", "v2.0.1", "v2.0.2", "v2.0.3", "v2.0.4", "v2.0.5", "v2.0.6"},
   501  	},
   502  	{
   503  		path:     "swtch.com/testmod",
   504  		versions: []string{"v1.0.0", "v1.1.1"},
   505  	},
   506  	{
   507  		path:     "gopkg.in/russross/blackfriday.v2",
   508  		versions: []string{"v2.0.0", "v2.0.1"},
   509  	},
   510  	{
   511  		path:     "gopkg.in/natefinch/lumberjack.v2",
   512  		versions: []string{"v2.0.0"},
   513  	},
   514  }
   515  
   516  func TestCodeRepoVersions(t *testing.T) {
   517  	testenv.MustHaveExternalNetwork(t)
   518  
   519  	tmpdir, err := ioutil.TempDir("", "vgo-modfetch-test-")
   520  	if err != nil {
   521  		t.Fatal(err)
   522  	}
   523  	defer os.RemoveAll(tmpdir)
   524  	for _, tt := range codeRepoVersionsTests {
   525  		t.Run(strings.ReplaceAll(tt.path, "/", "_"), func(t *testing.T) {
   526  			repo, err := Lookup(tt.path)
   527  			if err != nil {
   528  				t.Fatalf("Lookup(%q): %v", tt.path, err)
   529  			}
   530  			list, err := repo.Versions(tt.prefix)
   531  			if err != nil {
   532  				t.Fatalf("Versions(%q): %v", tt.prefix, err)
   533  			}
   534  			if !reflect.DeepEqual(list, tt.versions) {
   535  				t.Fatalf("Versions(%q):\nhave %v\nwant %v", tt.prefix, list, tt.versions)
   536  			}
   537  		})
   538  	}
   539  }
   540  
   541  var latestTests = []struct {
   542  	path    string
   543  	version string
   544  	err     string
   545  }{
   546  	{
   547  		path: "github.com/rsc/empty",
   548  		err:  "no commits",
   549  	},
   550  	{
   551  		path:    "github.com/rsc/vgotest1",
   552  		version: "v0.0.0-20180219223237-a08abb797a67",
   553  	},
   554  	{
   555  		path: "github.com/rsc/vgotest1/subdir",
   556  		err:  "missing github.com/rsc/vgotest1/subdir/go.mod at revision a08abb797a67",
   557  	},
   558  	{
   559  		path:    "swtch.com/testmod",
   560  		version: "v1.1.1",
   561  	},
   562  }
   563  
   564  func TestLatest(t *testing.T) {
   565  	testenv.MustHaveExternalNetwork(t)
   566  
   567  	tmpdir, err := ioutil.TempDir("", "vgo-modfetch-test-")
   568  	if err != nil {
   569  		t.Fatal(err)
   570  	}
   571  	defer os.RemoveAll(tmpdir)
   572  	for _, tt := range latestTests {
   573  		name := strings.ReplaceAll(tt.path, "/", "_")
   574  		t.Run(name, func(t *testing.T) {
   575  			repo, err := Lookup(tt.path)
   576  			if err != nil {
   577  				t.Fatalf("Lookup(%q): %v", tt.path, err)
   578  			}
   579  			info, err := repo.Latest()
   580  			if err != nil {
   581  				if tt.err != "" {
   582  					if err.Error() == tt.err {
   583  						return
   584  					}
   585  					t.Fatalf("Latest(): %v, want %q", err, tt.err)
   586  				}
   587  				t.Fatalf("Latest(): %v", err)
   588  			}
   589  			if tt.err != "" {
   590  				t.Fatalf("Latest() = %v, want error %q", info.Version, tt.err)
   591  			}
   592  			if info.Version != tt.version {
   593  				t.Fatalf("Latest() = %v, want %v", info.Version, tt.version)
   594  			}
   595  		})
   596  	}
   597  }
   598  
   599  // fixedTagsRepo is a fake codehost.Repo that returns a fixed list of tags
   600  type fixedTagsRepo struct {
   601  	tags []string
   602  }
   603  
   604  func (ch *fixedTagsRepo) Tags(string) ([]string, error)                  { return ch.tags, nil }
   605  func (ch *fixedTagsRepo) Latest() (*codehost.RevInfo, error)             { panic("not impl") }
   606  func (ch *fixedTagsRepo) ReadFile(string, string, int64) ([]byte, error) { panic("not impl") }
   607  func (ch *fixedTagsRepo) ReadFileRevs([]string, string, int64) (map[string]*codehost.FileRev, error) {
   608  	panic("not impl")
   609  }
   610  func (ch *fixedTagsRepo) ReadZip(string, string, int64) (io.ReadCloser, string, error) {
   611  	panic("not impl")
   612  }
   613  func (ch *fixedTagsRepo) RecentTag(string, string) (string, error) {
   614  	panic("not impl")
   615  }
   616  func (ch *fixedTagsRepo) Stat(string) (*codehost.RevInfo, error) { panic("not impl") }
   617  
   618  func TestNonCanonicalSemver(t *testing.T) {
   619  	root := "golang.org/x/issue24476"
   620  	ch := &fixedTagsRepo{
   621  		tags: []string{
   622  			"", "huh?", "1.0.1",
   623  			// what about "version 1 dot dogcow"?
   624  			"v1.🐕.🐄",
   625  			"v1", "v0.1",
   626  			// and one normal one that should pass through
   627  			"v1.0.1",
   628  		},
   629  	}
   630  
   631  	cr, err := newCodeRepo(ch, root, root)
   632  	if err != nil {
   633  		t.Fatal(err)
   634  	}
   635  
   636  	v, err := cr.Versions("")
   637  	if err != nil {
   638  		t.Fatal(err)
   639  	}
   640  	if len(v) != 1 || v[0] != "v1.0.1" {
   641  		t.Fatal("unexpected versions returned:", v)
   642  	}
   643  }