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