Source file src/cmd/go/internal/modfetch/codehost/git_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 codehost
     6  
     7  import (
     8  	"archive/zip"
     9  	"bytes"
    10  	"flag"
    11  	"internal/testenv"
    12  	"io"
    13  	"io/fs"
    14  	"log"
    15  	"os"
    16  	"os/exec"
    17  	"path"
    18  	"path/filepath"
    19  	"reflect"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  )
    24  
    25  func TestMain(m *testing.M) {
    26  	// needed for initializing the test environment variables as testing.Short
    27  	// and HasExternalNetwork
    28  	flag.Parse()
    29  	os.Exit(testMain(m))
    30  }
    31  
    32  const (
    33  	gitrepo1 = "https://vcs-test.golang.org/git/gitrepo1"
    34  	hgrepo1  = "https://vcs-test.golang.org/hg/hgrepo1"
    35  )
    36  
    37  var altRepos = []string{
    38  	"localGitRepo",
    39  	hgrepo1,
    40  }
    41  
    42  // TODO: Convert gitrepo1 to svn, bzr, fossil and add tests.
    43  // For now, at least the hgrepo1 tests check the general vcs.go logic.
    44  
    45  // localGitRepo is like gitrepo1 but allows archive access.
    46  var localGitRepo string
    47  
    48  func testMain(m *testing.M) int {
    49  	dir, err := os.MkdirTemp("", "gitrepo-test-")
    50  	if err != nil {
    51  		log.Fatal(err)
    52  	}
    53  	defer os.RemoveAll(dir)
    54  
    55  	if testenv.HasExternalNetwork() && testenv.HasExec() {
    56  		if _, err := exec.LookPath("git"); err == nil {
    57  			// Clone gitrepo1 into a local directory.
    58  			// If we use a file:// URL to access the local directory,
    59  			// then git starts up all the usual protocol machinery,
    60  			// which will let us test remote git archive invocations.
    61  			localGitRepo = filepath.Join(dir, "gitrepo2")
    62  			if _, err := Run("", "git", "clone", "--mirror", gitrepo1, localGitRepo); err != nil {
    63  				log.Fatal(err)
    64  			}
    65  			if _, err := Run(localGitRepo, "git", "config", "daemon.uploadarch", "true"); err != nil {
    66  				log.Fatal(err)
    67  			}
    68  		}
    69  	}
    70  
    71  	return m.Run()
    72  }
    73  
    74  func testRepo(t *testing.T, remote string) (Repo, error) {
    75  	if remote == "localGitRepo" {
    76  		// Convert absolute path to file URL. LocalGitRepo will not accept
    77  		// Windows absolute paths because they look like a host:path remote.
    78  		// TODO(golang.org/issue/32456): use url.FromFilePath when implemented.
    79  		var url string
    80  		if strings.HasPrefix(localGitRepo, "/") {
    81  			url = "file://" + localGitRepo
    82  		} else {
    83  			url = "file:///" + filepath.ToSlash(localGitRepo)
    84  		}
    85  		testenv.MustHaveExecPath(t, "git")
    86  		return LocalGitRepo(url)
    87  	}
    88  	vcs := "git"
    89  	for _, k := range []string{"hg"} {
    90  		if strings.Contains(remote, "/"+k+"/") {
    91  			vcs = k
    92  		}
    93  	}
    94  	testenv.MustHaveExecPath(t, vcs)
    95  	return NewRepo(vcs, remote)
    96  }
    97  
    98  var tagsTests = []struct {
    99  	repo   string
   100  	prefix string
   101  	tags   []string
   102  }{
   103  	{gitrepo1, "xxx", []string{}},
   104  	{gitrepo1, "", []string{"v1.2.3", "v1.2.4-annotated", "v2.0.1", "v2.0.2", "v2.3"}},
   105  	{gitrepo1, "v", []string{"v1.2.3", "v1.2.4-annotated", "v2.0.1", "v2.0.2", "v2.3"}},
   106  	{gitrepo1, "v1", []string{"v1.2.3", "v1.2.4-annotated"}},
   107  	{gitrepo1, "2", []string{}},
   108  }
   109  
   110  func TestTags(t *testing.T) {
   111  	testenv.MustHaveExternalNetwork(t)
   112  	testenv.MustHaveExec(t)
   113  
   114  	for _, tt := range tagsTests {
   115  		f := func(t *testing.T) {
   116  			r, err := testRepo(t, tt.repo)
   117  			if err != nil {
   118  				t.Fatal(err)
   119  			}
   120  			tags, err := r.Tags(tt.prefix)
   121  			if err != nil {
   122  				t.Fatal(err)
   123  			}
   124  			if !reflect.DeepEqual(tags, tt.tags) {
   125  				t.Errorf("Tags: incorrect tags\nhave %v\nwant %v", tags, tt.tags)
   126  			}
   127  		}
   128  		t.Run(path.Base(tt.repo)+"/"+tt.prefix, f)
   129  		if tt.repo == gitrepo1 {
   130  			for _, tt.repo = range altRepos {
   131  				t.Run(path.Base(tt.repo)+"/"+tt.prefix, f)
   132  			}
   133  		}
   134  	}
   135  }
   136  
   137  var latestTests = []struct {
   138  	repo string
   139  	info *RevInfo
   140  }{
   141  	{
   142  		gitrepo1,
   143  		&RevInfo{
   144  			Name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
   145  			Short:   "ede458df7cd0",
   146  			Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
   147  			Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   148  			Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   149  		},
   150  	},
   151  	{
   152  		hgrepo1,
   153  		&RevInfo{
   154  			Name:    "18518c07eb8ed5c80221e997e518cccaa8c0c287",
   155  			Short:   "18518c07eb8e",
   156  			Version: "18518c07eb8ed5c80221e997e518cccaa8c0c287",
   157  			Time:    time.Date(2018, 6, 27, 16, 16, 30, 0, time.UTC),
   158  		},
   159  	},
   160  }
   161  
   162  func TestLatest(t *testing.T) {
   163  	testenv.MustHaveExternalNetwork(t)
   164  	testenv.MustHaveExec(t)
   165  
   166  	for _, tt := range latestTests {
   167  		f := func(t *testing.T) {
   168  			r, err := testRepo(t, tt.repo)
   169  			if err != nil {
   170  				t.Fatal(err)
   171  			}
   172  			info, err := r.Latest()
   173  			if err != nil {
   174  				t.Fatal(err)
   175  			}
   176  			if !reflect.DeepEqual(info, tt.info) {
   177  				t.Errorf("Latest: incorrect info\nhave %+v\nwant %+v", *info, *tt.info)
   178  			}
   179  		}
   180  		t.Run(path.Base(tt.repo), f)
   181  		if tt.repo == gitrepo1 {
   182  			tt.repo = "localGitRepo"
   183  			t.Run(path.Base(tt.repo), f)
   184  		}
   185  	}
   186  }
   187  
   188  var readFileTests = []struct {
   189  	repo string
   190  	rev  string
   191  	file string
   192  	err  string
   193  	data string
   194  }{
   195  	{
   196  		repo: gitrepo1,
   197  		rev:  "latest",
   198  		file: "README",
   199  		data: "",
   200  	},
   201  	{
   202  		repo: gitrepo1,
   203  		rev:  "v2",
   204  		file: "another.txt",
   205  		data: "another\n",
   206  	},
   207  	{
   208  		repo: gitrepo1,
   209  		rev:  "v2.3.4",
   210  		file: "another.txt",
   211  		err:  fs.ErrNotExist.Error(),
   212  	},
   213  }
   214  
   215  func TestReadFile(t *testing.T) {
   216  	testenv.MustHaveExternalNetwork(t)
   217  	testenv.MustHaveExec(t)
   218  
   219  	for _, tt := range readFileTests {
   220  		f := func(t *testing.T) {
   221  			r, err := testRepo(t, tt.repo)
   222  			if err != nil {
   223  				t.Fatal(err)
   224  			}
   225  			data, err := r.ReadFile(tt.rev, tt.file, 100)
   226  			if err != nil {
   227  				if tt.err == "" {
   228  					t.Fatalf("ReadFile: unexpected error %v", err)
   229  				}
   230  				if !strings.Contains(err.Error(), tt.err) {
   231  					t.Fatalf("ReadFile: wrong error %q, want %q", err, tt.err)
   232  				}
   233  				if len(data) != 0 {
   234  					t.Errorf("ReadFile: non-empty data %q with error %v", data, err)
   235  				}
   236  				return
   237  			}
   238  			if tt.err != "" {
   239  				t.Fatalf("ReadFile: no error, wanted %v", tt.err)
   240  			}
   241  			if string(data) != tt.data {
   242  				t.Errorf("ReadFile: incorrect data\nhave %q\nwant %q", data, tt.data)
   243  			}
   244  		}
   245  		t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, f)
   246  		if tt.repo == gitrepo1 {
   247  			for _, tt.repo = range altRepos {
   248  				t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, f)
   249  			}
   250  		}
   251  	}
   252  }
   253  
   254  var readZipTests = []struct {
   255  	repo   string
   256  	rev    string
   257  	subdir string
   258  	err    string
   259  	files  map[string]uint64
   260  }{
   261  	{
   262  		repo:   gitrepo1,
   263  		rev:    "v2.3.4",
   264  		subdir: "",
   265  		files: map[string]uint64{
   266  			"prefix/":       0,
   267  			"prefix/README": 0,
   268  			"prefix/v2":     3,
   269  		},
   270  	},
   271  	{
   272  		repo:   hgrepo1,
   273  		rev:    "v2.3.4",
   274  		subdir: "",
   275  		files: map[string]uint64{
   276  			"prefix/.hg_archival.txt": ^uint64(0),
   277  			"prefix/README":           0,
   278  			"prefix/v2":               3,
   279  		},
   280  	},
   281  
   282  	{
   283  		repo:   gitrepo1,
   284  		rev:    "v2",
   285  		subdir: "",
   286  		files: map[string]uint64{
   287  			"prefix/":            0,
   288  			"prefix/README":      0,
   289  			"prefix/v2":          3,
   290  			"prefix/another.txt": 8,
   291  			"prefix/foo.txt":     13,
   292  		},
   293  	},
   294  	{
   295  		repo:   hgrepo1,
   296  		rev:    "v2",
   297  		subdir: "",
   298  		files: map[string]uint64{
   299  			"prefix/.hg_archival.txt": ^uint64(0),
   300  			"prefix/README":           0,
   301  			"prefix/v2":               3,
   302  			"prefix/another.txt":      8,
   303  			"prefix/foo.txt":          13,
   304  		},
   305  	},
   306  
   307  	{
   308  		repo:   gitrepo1,
   309  		rev:    "v3",
   310  		subdir: "",
   311  		files: map[string]uint64{
   312  			"prefix/":                    0,
   313  			"prefix/v3/":                 0,
   314  			"prefix/v3/sub/":             0,
   315  			"prefix/v3/sub/dir/":         0,
   316  			"prefix/v3/sub/dir/file.txt": 16,
   317  			"prefix/README":              0,
   318  		},
   319  	},
   320  	{
   321  		repo:   hgrepo1,
   322  		rev:    "v3",
   323  		subdir: "",
   324  		files: map[string]uint64{
   325  			"prefix/.hg_archival.txt":    ^uint64(0),
   326  			"prefix/.hgtags":             405,
   327  			"prefix/v3/sub/dir/file.txt": 16,
   328  			"prefix/README":              0,
   329  		},
   330  	},
   331  
   332  	{
   333  		repo:   gitrepo1,
   334  		rev:    "v3",
   335  		subdir: "v3/sub/dir",
   336  		files: map[string]uint64{
   337  			"prefix/":                    0,
   338  			"prefix/v3/":                 0,
   339  			"prefix/v3/sub/":             0,
   340  			"prefix/v3/sub/dir/":         0,
   341  			"prefix/v3/sub/dir/file.txt": 16,
   342  		},
   343  	},
   344  	{
   345  		repo:   hgrepo1,
   346  		rev:    "v3",
   347  		subdir: "v3/sub/dir",
   348  		files: map[string]uint64{
   349  			"prefix/v3/sub/dir/file.txt": 16,
   350  		},
   351  	},
   352  
   353  	{
   354  		repo:   gitrepo1,
   355  		rev:    "v3",
   356  		subdir: "v3/sub",
   357  		files: map[string]uint64{
   358  			"prefix/":                    0,
   359  			"prefix/v3/":                 0,
   360  			"prefix/v3/sub/":             0,
   361  			"prefix/v3/sub/dir/":         0,
   362  			"prefix/v3/sub/dir/file.txt": 16,
   363  		},
   364  	},
   365  	{
   366  		repo:   hgrepo1,
   367  		rev:    "v3",
   368  		subdir: "v3/sub",
   369  		files: map[string]uint64{
   370  			"prefix/v3/sub/dir/file.txt": 16,
   371  		},
   372  	},
   373  
   374  	{
   375  		repo:   gitrepo1,
   376  		rev:    "aaaaaaaaab",
   377  		subdir: "",
   378  		err:    "unknown revision",
   379  	},
   380  	{
   381  		repo:   hgrepo1,
   382  		rev:    "aaaaaaaaab",
   383  		subdir: "",
   384  		err:    "unknown revision",
   385  	},
   386  
   387  	{
   388  		repo:   "https://github.com/rsc/vgotest1",
   389  		rev:    "submod/v1.0.4",
   390  		subdir: "submod",
   391  		files: map[string]uint64{
   392  			"prefix/":                0,
   393  			"prefix/submod/":         0,
   394  			"prefix/submod/go.mod":   53,
   395  			"prefix/submod/pkg/":     0,
   396  			"prefix/submod/pkg/p.go": 31,
   397  		},
   398  	},
   399  }
   400  
   401  type zipFile struct {
   402  	name string
   403  	size int64
   404  }
   405  
   406  func TestReadZip(t *testing.T) {
   407  	testenv.MustHaveExternalNetwork(t)
   408  	testenv.MustHaveExec(t)
   409  
   410  	for _, tt := range readZipTests {
   411  		f := func(t *testing.T) {
   412  			r, err := testRepo(t, tt.repo)
   413  			if err != nil {
   414  				t.Fatal(err)
   415  			}
   416  			rc, err := r.ReadZip(tt.rev, tt.subdir, 100000)
   417  			if err != nil {
   418  				if tt.err == "" {
   419  					t.Fatalf("ReadZip: unexpected error %v", err)
   420  				}
   421  				if !strings.Contains(err.Error(), tt.err) {
   422  					t.Fatalf("ReadZip: wrong error %q, want %q", err, tt.err)
   423  				}
   424  				if rc != nil {
   425  					t.Errorf("ReadZip: non-nil io.ReadCloser with error %v", err)
   426  				}
   427  				return
   428  			}
   429  			defer rc.Close()
   430  			if tt.err != "" {
   431  				t.Fatalf("ReadZip: no error, wanted %v", tt.err)
   432  			}
   433  			zipdata, err := io.ReadAll(rc)
   434  			if err != nil {
   435  				t.Fatal(err)
   436  			}
   437  			z, err := zip.NewReader(bytes.NewReader(zipdata), int64(len(zipdata)))
   438  			if err != nil {
   439  				t.Fatalf("ReadZip: cannot read zip file: %v", err)
   440  			}
   441  			have := make(map[string]bool)
   442  			for _, f := range z.File {
   443  				size, ok := tt.files[f.Name]
   444  				if !ok {
   445  					t.Errorf("ReadZip: unexpected file %s", f.Name)
   446  					continue
   447  				}
   448  				have[f.Name] = true
   449  				if size != ^uint64(0) && f.UncompressedSize64 != size {
   450  					t.Errorf("ReadZip: file %s has unexpected size %d != %d", f.Name, f.UncompressedSize64, size)
   451  				}
   452  			}
   453  			for name := range tt.files {
   454  				if !have[name] {
   455  					t.Errorf("ReadZip: missing file %s", name)
   456  				}
   457  			}
   458  		}
   459  		t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, f)
   460  		if tt.repo == gitrepo1 {
   461  			tt.repo = "localGitRepo"
   462  			t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, f)
   463  		}
   464  	}
   465  }
   466  
   467  var hgmap = map[string]string{
   468  	"HEAD": "41964ddce1180313bdc01d0a39a2813344d6261d", // not tip due to bad hgrepo1 conversion
   469  	"9d02800338b8a55be062c838d1f02e0c5780b9eb": "8f49ee7a6ddcdec6f0112d9dca48d4a2e4c3c09e",
   470  	"76a00fb249b7f93091bc2c89a789dab1fc1bc26f": "88fde824ec8b41a76baa16b7e84212cee9f3edd0",
   471  	"ede458df7cd0fdca520df19a33158086a8a68e81": "41964ddce1180313bdc01d0a39a2813344d6261d",
   472  	"97f6aa59c81c623494825b43d39e445566e429a4": "c0cbbfb24c7c3c50c35c7b88e7db777da4ff625d",
   473  }
   474  
   475  var statTests = []struct {
   476  	repo string
   477  	rev  string
   478  	err  string
   479  	info *RevInfo
   480  }{
   481  	{
   482  		repo: gitrepo1,
   483  		rev:  "HEAD",
   484  		info: &RevInfo{
   485  			Name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
   486  			Short:   "ede458df7cd0",
   487  			Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
   488  			Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   489  			Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   490  		},
   491  	},
   492  	{
   493  		repo: gitrepo1,
   494  		rev:  "v2", // branch
   495  		info: &RevInfo{
   496  			Name:    "9d02800338b8a55be062c838d1f02e0c5780b9eb",
   497  			Short:   "9d02800338b8",
   498  			Version: "9d02800338b8a55be062c838d1f02e0c5780b9eb",
   499  			Time:    time.Date(2018, 4, 17, 20, 00, 32, 0, time.UTC),
   500  			Tags:    []string{"v2.0.2"},
   501  		},
   502  	},
   503  	{
   504  		repo: gitrepo1,
   505  		rev:  "v2.3.4", // badly-named branch (semver should be a tag)
   506  		info: &RevInfo{
   507  			Name:    "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
   508  			Short:   "76a00fb249b7",
   509  			Version: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
   510  			Time:    time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
   511  			Tags:    []string{"v2.0.1", "v2.3"},
   512  		},
   513  	},
   514  	{
   515  		repo: gitrepo1,
   516  		rev:  "v2.3", // badly-named tag (we only respect full semver v2.3.0)
   517  		info: &RevInfo{
   518  			Name:    "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
   519  			Short:   "76a00fb249b7",
   520  			Version: "v2.3",
   521  			Time:    time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
   522  			Tags:    []string{"v2.0.1", "v2.3"},
   523  		},
   524  	},
   525  	{
   526  		repo: gitrepo1,
   527  		rev:  "v1.2.3", // tag
   528  		info: &RevInfo{
   529  			Name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
   530  			Short:   "ede458df7cd0",
   531  			Version: "v1.2.3",
   532  			Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   533  			Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   534  		},
   535  	},
   536  	{
   537  		repo: gitrepo1,
   538  		rev:  "ede458df", // hash prefix in refs
   539  		info: &RevInfo{
   540  			Name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
   541  			Short:   "ede458df7cd0",
   542  			Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
   543  			Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   544  			Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   545  		},
   546  	},
   547  	{
   548  		repo: gitrepo1,
   549  		rev:  "97f6aa59", // hash prefix not in refs
   550  		info: &RevInfo{
   551  			Name:    "97f6aa59c81c623494825b43d39e445566e429a4",
   552  			Short:   "97f6aa59c81c",
   553  			Version: "97f6aa59c81c623494825b43d39e445566e429a4",
   554  			Time:    time.Date(2018, 4, 17, 20, 0, 19, 0, time.UTC),
   555  		},
   556  	},
   557  	{
   558  		repo: gitrepo1,
   559  		rev:  "v1.2.4-annotated", // annotated tag uses unwrapped commit hash
   560  		info: &RevInfo{
   561  			Name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
   562  			Short:   "ede458df7cd0",
   563  			Version: "v1.2.4-annotated",
   564  			Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   565  			Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   566  		},
   567  	},
   568  	{
   569  		repo: gitrepo1,
   570  		rev:  "aaaaaaaaab",
   571  		err:  "unknown revision",
   572  	},
   573  }
   574  
   575  func TestStat(t *testing.T) {
   576  	testenv.MustHaveExternalNetwork(t)
   577  	testenv.MustHaveExec(t)
   578  
   579  	for _, tt := range statTests {
   580  		f := func(t *testing.T) {
   581  			r, err := testRepo(t, tt.repo)
   582  			if err != nil {
   583  				t.Fatal(err)
   584  			}
   585  			info, err := r.Stat(tt.rev)
   586  			if err != nil {
   587  				if tt.err == "" {
   588  					t.Fatalf("Stat: unexpected error %v", err)
   589  				}
   590  				if !strings.Contains(err.Error(), tt.err) {
   591  					t.Fatalf("Stat: wrong error %q, want %q", err, tt.err)
   592  				}
   593  				if info != nil {
   594  					t.Errorf("Stat: non-nil info with error %q", err)
   595  				}
   596  				return
   597  			}
   598  			if !reflect.DeepEqual(info, tt.info) {
   599  				t.Errorf("Stat: incorrect info\nhave %+v\nwant %+v", *info, *tt.info)
   600  			}
   601  		}
   602  		t.Run(path.Base(tt.repo)+"/"+tt.rev, f)
   603  		if tt.repo == gitrepo1 {
   604  			for _, tt.repo = range altRepos {
   605  				old := tt
   606  				var m map[string]string
   607  				if tt.repo == hgrepo1 {
   608  					m = hgmap
   609  				}
   610  				if tt.info != nil {
   611  					info := *tt.info
   612  					tt.info = &info
   613  					tt.info.Name = remap(tt.info.Name, m)
   614  					tt.info.Version = remap(tt.info.Version, m)
   615  					tt.info.Short = remap(tt.info.Short, m)
   616  				}
   617  				tt.rev = remap(tt.rev, m)
   618  				t.Run(path.Base(tt.repo)+"/"+tt.rev, f)
   619  				tt = old
   620  			}
   621  		}
   622  	}
   623  }
   624  
   625  func remap(name string, m map[string]string) string {
   626  	if m[name] != "" {
   627  		return m[name]
   628  	}
   629  	if AllHex(name) {
   630  		for k, v := range m {
   631  			if strings.HasPrefix(k, name) {
   632  				return v[:len(name)]
   633  			}
   634  		}
   635  	}
   636  	return name
   637  }
   638  

View as plain text