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  	"cmd/go/internal/cfg"
    11  	"cmd/go/internal/vcweb/vcstest"
    12  	"context"
    13  	"flag"
    14  	"internal/testenv"
    15  	"io"
    16  	"io/fs"
    17  	"log"
    18  	"os"
    19  	"os/exec"
    20  	"path"
    21  	"path/filepath"
    22  	"reflect"
    23  	"regexp"
    24  	"runtime"
    25  	"strings"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  
    30  	"golang.org/x/mod/semver"
    31  )
    32  
    33  func TestMain(m *testing.M) {
    34  	flag.Parse()
    35  	if err := testMain(m); err != nil {
    36  		log.Fatal(err)
    37  	}
    38  }
    39  
    40  var gitrepo1, gitsha256repo, hgrepo1, vgotest1 string
    41  
    42  var altRepos = func() []string {
    43  	return []string{
    44  		"localGitRepo",
    45  		hgrepo1,
    46  	}
    47  }
    48  
    49  // TODO: Convert gitrepo1 to svn, bzr, fossil and add tests.
    50  // For now, at least the hgrepo1 tests check the general vcs.go logic.
    51  
    52  // localGitRepo is like gitrepo1 but allows archive access
    53  // (although that doesn't really matter after CL 120041),
    54  // and has a file:// URL instead of http:// or https://
    55  // (which might still matter).
    56  var localGitRepo string
    57  
    58  // localGitURL initializes the repo in localGitRepo and returns its URL.
    59  func localGitURL(t testing.TB) string {
    60  	testenv.MustHaveExecPath(t, "git")
    61  	if runtime.GOOS == "android" && strings.HasSuffix(testenv.Builder(), "-corellium") {
    62  		testenv.SkipFlaky(t, 59940)
    63  	}
    64  
    65  	localGitURLOnce.Do(func() {
    66  		// Clone gitrepo1 into a local directory.
    67  		// If we use a file:// URL to access the local directory,
    68  		// then git starts up all the usual protocol machinery,
    69  		// which will let us test remote git archive invocations.
    70  		_, localGitURLErr = Run(context.Background(), "", "git", "clone", "--mirror", gitrepo1, localGitRepo)
    71  		if localGitURLErr != nil {
    72  			return
    73  		}
    74  		repo := gitRepo{dir: localGitRepo}
    75  		_, localGitURLErr = repo.runGit(context.Background(), "git", "config", "daemon.uploadarch", "true")
    76  		// TODO(david.finkel): do the same with the git repo using sha256 object hashes
    77  	})
    78  
    79  	if localGitURLErr != nil {
    80  		t.Fatal(localGitURLErr)
    81  	}
    82  	// Convert absolute path to file URL. LocalGitRepo will not accept
    83  	// Windows absolute paths because they look like a host:path remote.
    84  	// TODO(golang.org/issue/32456): use url.FromFilePath when implemented.
    85  	if strings.HasPrefix(localGitRepo, "/") {
    86  		return "file://" + localGitRepo
    87  	} else {
    88  		return "file:///" + filepath.ToSlash(localGitRepo)
    89  	}
    90  }
    91  
    92  var (
    93  	localGitURLOnce sync.Once
    94  	localGitURLErr  error
    95  )
    96  
    97  func testMain(m *testing.M) (err error) {
    98  	cfg.BuildX = testing.Verbose()
    99  
   100  	srv, err := vcstest.NewServer()
   101  	if err != nil {
   102  		return err
   103  	}
   104  	defer func() {
   105  		if closeErr := srv.Close(); err == nil {
   106  			err = closeErr
   107  		}
   108  	}()
   109  
   110  	gitrepo1 = srv.HTTP.URL + "/git/gitrepo1"
   111  	gitsha256repo = srv.HTTP.URL + "/git/gitrepo-sha256"
   112  	hgrepo1 = srv.HTTP.URL + "/hg/hgrepo1"
   113  	vgotest1 = srv.HTTP.URL + "/git/vgotest1"
   114  
   115  	dir, err := os.MkdirTemp("", "gitrepo-test-")
   116  	if err != nil {
   117  		return err
   118  	}
   119  	defer func() {
   120  		if rmErr := os.RemoveAll(dir); err == nil {
   121  			err = rmErr
   122  		}
   123  	}()
   124  
   125  	localGitRepo = filepath.Join(dir, "gitrepo2")
   126  
   127  	// Redirect the module cache to a fresh directory to avoid crosstalk, and make
   128  	// it read/write so that the test can still clean it up easily when done.
   129  	cfg.GOMODCACHE = filepath.Join(dir, "modcache")
   130  	cfg.ModCacheRW = true
   131  
   132  	m.Run()
   133  	return nil
   134  }
   135  
   136  func testContext(t testing.TB) context.Context {
   137  	w := newTestWriter(t)
   138  	return cfg.WithBuildXWriter(context.Background(), w)
   139  }
   140  
   141  // A testWriter is an io.Writer that writes to a test's log.
   142  //
   143  // The writer batches written data until the last byte of a write is a newline
   144  // character, then flushes the batched data as a single call to Logf.
   145  // Any remaining unflushed data is logged during Cleanup.
   146  type testWriter struct {
   147  	t testing.TB
   148  
   149  	mu  sync.Mutex
   150  	buf bytes.Buffer
   151  }
   152  
   153  func newTestWriter(t testing.TB) *testWriter {
   154  	w := &testWriter{t: t}
   155  
   156  	t.Cleanup(func() {
   157  		w.mu.Lock()
   158  		defer w.mu.Unlock()
   159  		if b := w.buf.Bytes(); len(b) > 0 {
   160  			w.t.Logf("%s", b)
   161  			w.buf.Reset()
   162  		}
   163  	})
   164  
   165  	return w
   166  }
   167  
   168  func (w *testWriter) Write(p []byte) (int, error) {
   169  	w.mu.Lock()
   170  	defer w.mu.Unlock()
   171  	n, err := w.buf.Write(p)
   172  	if b := w.buf.Bytes(); len(b) > 0 && b[len(b)-1] == '\n' {
   173  		w.t.Logf("%s", b)
   174  		w.buf.Reset()
   175  	}
   176  	return n, err
   177  }
   178  
   179  func testRepo(ctx context.Context, t *testing.T, remote string) (Repo, error) {
   180  	if remote == "localGitRepo" {
   181  		return NewRepo(ctx, "git", localGitURL(t), false)
   182  	}
   183  	vcsName := "git"
   184  	for _, k := range []string{"hg"} {
   185  		if strings.Contains(remote, "/"+k+"/") {
   186  			vcsName = k
   187  		}
   188  	}
   189  	if testing.Short() && vcsName == "hg" {
   190  		t.Skipf("skipping hg test in short mode: hg is slow")
   191  	}
   192  	testenv.MustHaveExecPath(t, vcsName)
   193  	if runtime.GOOS == "android" && strings.HasSuffix(testenv.Builder(), "-corellium") {
   194  		testenv.SkipFlaky(t, 59940)
   195  	}
   196  	return NewRepo(ctx, vcsName, remote, false)
   197  }
   198  
   199  var gitVersLineExtract = regexp.MustCompile(`git version\s+([\d.]+)`)
   200  
   201  func gitVersion(t testing.TB) string {
   202  	gitOut, runErr := exec.Command("git", "version").CombinedOutput()
   203  	if runErr != nil {
   204  		t.Logf("failed to execute git version: %s", runErr)
   205  		return "v0"
   206  	}
   207  	matches := gitVersLineExtract.FindSubmatch(gitOut)
   208  	if len(matches) < 2 {
   209  		t.Logf("git version extraction regexp did not match version line: %q", gitOut)
   210  		return "v0"
   211  	}
   212  	return "v" + string(matches[1])
   213  }
   214  
   215  const minGitSHA256Vers = "v2.29"
   216  
   217  func TestTags(t *testing.T) {
   218  	t.Parallel()
   219  
   220  	gitVers := gitVersion(t)
   221  
   222  	type tagsTest struct {
   223  		repo   string
   224  		prefix string
   225  		tags   []Tag
   226  	}
   227  
   228  	runTest := func(tt tagsTest) func(*testing.T) {
   229  		return func(t *testing.T) {
   230  			t.Parallel()
   231  			if tt.repo == gitsha256repo && semver.Compare(gitVers, minGitSHA256Vers) < 0 {
   232  				t.Skipf("git version is too old (%+v); skipping git sha256 test", gitVers)
   233  			}
   234  			ctx := testContext(t)
   235  
   236  			r, err := testRepo(ctx, t, tt.repo)
   237  			if err != nil {
   238  				t.Fatal(err)
   239  			}
   240  			tags, err := r.Tags(ctx, tt.prefix)
   241  			if err != nil {
   242  				t.Fatal(err)
   243  			}
   244  			if tags == nil || !reflect.DeepEqual(tags.List, tt.tags) {
   245  				t.Errorf("Tags(%q): incorrect tags\nhave %v\nwant %v", tt.prefix, tags, tt.tags)
   246  			}
   247  		}
   248  	}
   249  
   250  	for _, tt := range []tagsTest{
   251  		{gitrepo1, "xxx", []Tag{}},
   252  		{gitrepo1, "", []Tag{
   253  			{"v1.2.3", "ede458df7cd0fdca520df19a33158086a8a68e81"},
   254  			{"v1.2.4-annotated", "ede458df7cd0fdca520df19a33158086a8a68e81"},
   255  			{"v2.0.1", "76a00fb249b7f93091bc2c89a789dab1fc1bc26f"},
   256  			{"v2.0.2", "9d02800338b8a55be062c838d1f02e0c5780b9eb"},
   257  			{"v2.3", "76a00fb249b7f93091bc2c89a789dab1fc1bc26f"},
   258  		}},
   259  		{gitrepo1, "v", []Tag{
   260  			{"v1.2.3", "ede458df7cd0fdca520df19a33158086a8a68e81"},
   261  			{"v1.2.4-annotated", "ede458df7cd0fdca520df19a33158086a8a68e81"},
   262  			{"v2.0.1", "76a00fb249b7f93091bc2c89a789dab1fc1bc26f"},
   263  			{"v2.0.2", "9d02800338b8a55be062c838d1f02e0c5780b9eb"},
   264  			{"v2.3", "76a00fb249b7f93091bc2c89a789dab1fc1bc26f"},
   265  		}},
   266  		{gitrepo1, "v1", []Tag{
   267  			{"v1.2.3", "ede458df7cd0fdca520df19a33158086a8a68e81"},
   268  			{"v1.2.4-annotated", "ede458df7cd0fdca520df19a33158086a8a68e81"},
   269  		}},
   270  		{gitrepo1, "2", []Tag{}},
   271  		{gitsha256repo, "xxx", []Tag{}},
   272  		{gitsha256repo, "", []Tag{
   273  			{"v1.2.3", "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c"},
   274  			{"v1.2.4-annotated", "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c"},
   275  			{"v1.3.0", "a9157cad2aa6dc2f78aa31fced5887f04e758afa8703f04d0178702ebf04ee17"},
   276  			{"v2.0.1", "b7550fd9d2129c724c39ae0536e8b2fae4364d8c82bb8b0880c9b71f67295d09"},
   277  			{"v2.0.2", "1401e4e1fdb4169b51d44a1ff62af63ccc708bf5c12d15051268b51bbb6cbd82"},
   278  			{"v2.3", "b7550fd9d2129c724c39ae0536e8b2fae4364d8c82bb8b0880c9b71f67295d09"},
   279  		}},
   280  		{gitsha256repo, "v", []Tag{
   281  			{"v1.2.3", "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c"},
   282  			{"v1.2.4-annotated", "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c"},
   283  			{"v1.3.0", "a9157cad2aa6dc2f78aa31fced5887f04e758afa8703f04d0178702ebf04ee17"},
   284  			{"v2.0.1", "b7550fd9d2129c724c39ae0536e8b2fae4364d8c82bb8b0880c9b71f67295d09"},
   285  			{"v2.0.2", "1401e4e1fdb4169b51d44a1ff62af63ccc708bf5c12d15051268b51bbb6cbd82"},
   286  			{"v2.3", "b7550fd9d2129c724c39ae0536e8b2fae4364d8c82bb8b0880c9b71f67295d09"},
   287  		}},
   288  		{gitsha256repo, "v1", []Tag{
   289  			{"v1.2.3", "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c"},
   290  			{"v1.2.4-annotated", "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c"},
   291  			{"v1.3.0", "a9157cad2aa6dc2f78aa31fced5887f04e758afa8703f04d0178702ebf04ee17"},
   292  		}},
   293  		{gitsha256repo, "2", []Tag{}},
   294  	} {
   295  		t.Run(path.Base(tt.repo)+"/"+tt.prefix, runTest(tt))
   296  		if tt.repo == gitrepo1 {
   297  			// Clear hashes.
   298  			clearTags := []Tag{}
   299  			for _, tag := range tt.tags {
   300  				clearTags = append(clearTags, Tag{tag.Name, ""})
   301  			}
   302  			tags := tt.tags
   303  			for _, tt.repo = range altRepos() {
   304  				if strings.Contains(tt.repo, "Git") {
   305  					tt.tags = tags
   306  				} else {
   307  					tt.tags = clearTags
   308  				}
   309  				t.Run(path.Base(tt.repo)+"/"+tt.prefix, runTest(tt))
   310  			}
   311  		}
   312  	}
   313  }
   314  
   315  func TestLatest(t *testing.T) {
   316  	t.Parallel()
   317  
   318  	gitVers := gitVersion(t)
   319  
   320  	type latestTest struct {
   321  		repo string
   322  		info *RevInfo
   323  	}
   324  	runTest := func(tt latestTest) func(*testing.T) {
   325  		return func(t *testing.T) {
   326  			t.Parallel()
   327  			ctx := testContext(t)
   328  
   329  			if tt.repo == gitsha256repo && semver.Compare(gitVers, minGitSHA256Vers) < 0 {
   330  				t.Skipf("git version is too old (%+v); skipping git sha256 test", gitVers)
   331  			}
   332  
   333  			r, err := testRepo(ctx, t, tt.repo)
   334  			if err != nil {
   335  				t.Fatal(err)
   336  			}
   337  			info, err := r.Latest(ctx)
   338  			if err != nil {
   339  				t.Fatal(err)
   340  			}
   341  			if !reflect.DeepEqual(info, tt.info) {
   342  				t.Errorf("Latest: incorrect info\nhave %+v (origin %+v)\nwant %+v (origin %+v)", info, info.Origin, tt.info, tt.info.Origin)
   343  			}
   344  		}
   345  	}
   346  
   347  	for _, tt := range []latestTest{
   348  		{
   349  			gitrepo1,
   350  			&RevInfo{
   351  				Origin: &Origin{
   352  					VCS:  "git",
   353  					URL:  gitrepo1,
   354  					Ref:  "HEAD",
   355  					Hash: "ede458df7cd0fdca520df19a33158086a8a68e81",
   356  				},
   357  				Name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
   358  				Short:   "ede458df7cd0",
   359  				Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
   360  				Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   361  				Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   362  			},
   363  		},
   364  		{
   365  			gitsha256repo,
   366  			&RevInfo{
   367  				Origin: &Origin{
   368  					VCS:  "git",
   369  					URL:  gitsha256repo,
   370  					Ref:  "HEAD",
   371  					Hash: "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c",
   372  				},
   373  				Name:    "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c",
   374  				Short:   "47b8b51b2a2d",
   375  				Version: "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c",
   376  				Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   377  				Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   378  			},
   379  		},
   380  		{
   381  			hgrepo1,
   382  			&RevInfo{
   383  				Origin: &Origin{
   384  					VCS:  "hg",
   385  					URL:  hgrepo1,
   386  					Hash: "18518c07eb8ed5c80221e997e518cccaa8c0c287",
   387  				},
   388  				Name:    "18518c07eb8ed5c80221e997e518cccaa8c0c287",
   389  				Short:   "18518c07eb8e",
   390  				Version: "18518c07eb8ed5c80221e997e518cccaa8c0c287",
   391  				Time:    time.Date(2018, 6, 27, 16, 16, 30, 0, time.UTC),
   392  			},
   393  		},
   394  	} {
   395  		t.Run(path.Base(tt.repo), runTest(tt))
   396  		if tt.repo == gitrepo1 {
   397  			tt.repo = "localGitRepo"
   398  			info := *tt.info
   399  			tt.info = &info
   400  			o := *info.Origin
   401  			info.Origin = &o
   402  			o.URL = localGitURL(t)
   403  			t.Run(path.Base(tt.repo), runTest(tt))
   404  		}
   405  	}
   406  }
   407  
   408  func TestReadFile(t *testing.T) {
   409  	t.Parallel()
   410  
   411  	gitVers := gitVersion(t)
   412  
   413  	type readFileTest struct {
   414  		repo string
   415  		rev  string
   416  		file string
   417  		err  string
   418  		data string
   419  	}
   420  	runTest := func(tt readFileTest) func(*testing.T) {
   421  		return func(t *testing.T) {
   422  			t.Parallel()
   423  			ctx := testContext(t)
   424  
   425  			if tt.repo == gitsha256repo && semver.Compare(gitVers, minGitSHA256Vers) < 0 {
   426  				t.Skipf("git version is too old (%+v); skipping git sha256 test", gitVers)
   427  			}
   428  
   429  			r, err := testRepo(ctx, t, tt.repo)
   430  			if err != nil {
   431  				t.Fatal(err)
   432  			}
   433  			data, err := r.ReadFile(ctx, tt.rev, tt.file, 100)
   434  			if err != nil {
   435  				if tt.err == "" {
   436  					t.Fatalf("ReadFile: unexpected error %v", err)
   437  				}
   438  				if !strings.Contains(err.Error(), tt.err) {
   439  					t.Fatalf("ReadFile: wrong error %q, want %q", err, tt.err)
   440  				}
   441  				if len(data) != 0 {
   442  					t.Errorf("ReadFile: non-empty data %q with error %v", data, err)
   443  				}
   444  				return
   445  			}
   446  			if tt.err != "" {
   447  				t.Fatalf("ReadFile: no error, wanted %v", tt.err)
   448  			}
   449  			if string(data) != tt.data {
   450  				t.Errorf("ReadFile: incorrect data\nhave %q\nwant %q", data, tt.data)
   451  			}
   452  		}
   453  	}
   454  
   455  	for _, tt := range []readFileTest{
   456  		{
   457  			repo: gitrepo1,
   458  			rev:  "latest",
   459  			file: "README",
   460  			data: "",
   461  		},
   462  		{
   463  			repo: gitrepo1,
   464  			rev:  "v2",
   465  			file: "another.txt",
   466  			data: "another\n",
   467  		},
   468  		{
   469  			repo: gitrepo1,
   470  			rev:  "v2.3.4",
   471  			file: "another.txt",
   472  			err:  fs.ErrNotExist.Error(),
   473  		},
   474  		{
   475  			repo: gitsha256repo,
   476  			rev:  "latest",
   477  			file: "README",
   478  			data: "",
   479  		},
   480  		{
   481  			repo: gitsha256repo,
   482  			rev:  "v2",
   483  			file: "another.txt",
   484  			data: "another\n",
   485  		},
   486  		{
   487  			repo: gitsha256repo,
   488  			rev:  "v2.3.4",
   489  			file: "another.txt",
   490  			err:  fs.ErrNotExist.Error(),
   491  		},
   492  	} {
   493  		t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, runTest(tt))
   494  		if tt.repo == gitrepo1 {
   495  			for _, tt.repo = range altRepos() {
   496  				t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, runTest(tt))
   497  			}
   498  		}
   499  	}
   500  }
   501  
   502  type zipFile struct {
   503  	name string
   504  	size int64
   505  }
   506  
   507  func TestReadZip(t *testing.T) {
   508  	t.Parallel()
   509  
   510  	gitVers := gitVersion(t)
   511  
   512  	type readZipTest struct {
   513  		repo   string
   514  		rev    string
   515  		subdir string
   516  		err    string
   517  		files  map[string]uint64
   518  	}
   519  	runTest := func(tt readZipTest) func(*testing.T) {
   520  		return func(t *testing.T) {
   521  			t.Parallel()
   522  			ctx := testContext(t)
   523  
   524  			if tt.repo == gitsha256repo && semver.Compare(gitVers, minGitSHA256Vers) < 0 {
   525  				t.Skipf("git version is too old (%+v); skipping git sha256 test", gitVers)
   526  			}
   527  
   528  			r, err := testRepo(ctx, t, tt.repo)
   529  			if err != nil {
   530  				t.Fatal(err)
   531  			}
   532  			rc, err := r.ReadZip(ctx, tt.rev, tt.subdir, 100000)
   533  			if err != nil {
   534  				if tt.err == "" {
   535  					t.Fatalf("ReadZip: unexpected error %v", err)
   536  				}
   537  				if !strings.Contains(err.Error(), tt.err) {
   538  					t.Fatalf("ReadZip: wrong error %q, want %q", err, tt.err)
   539  				}
   540  				if rc != nil {
   541  					t.Errorf("ReadZip: non-nil io.ReadCloser with error %v", err)
   542  				}
   543  				return
   544  			}
   545  			defer rc.Close()
   546  			if tt.err != "" {
   547  				t.Fatalf("ReadZip: no error, wanted %v", tt.err)
   548  			}
   549  			zipdata, err := io.ReadAll(rc)
   550  			if err != nil {
   551  				t.Fatal(err)
   552  			}
   553  			z, err := zip.NewReader(bytes.NewReader(zipdata), int64(len(zipdata)))
   554  			if err != nil {
   555  				t.Fatalf("ReadZip: cannot read zip file: %v", err)
   556  			}
   557  			have := make(map[string]bool)
   558  			for _, f := range z.File {
   559  				size, ok := tt.files[f.Name]
   560  				if !ok {
   561  					t.Errorf("ReadZip: unexpected file %s", f.Name)
   562  					continue
   563  				}
   564  				have[f.Name] = true
   565  				if size != ^uint64(0) && f.UncompressedSize64 != size {
   566  					t.Errorf("ReadZip: file %s has unexpected size %d != %d", f.Name, f.UncompressedSize64, size)
   567  				}
   568  			}
   569  			for name := range tt.files {
   570  				if !have[name] {
   571  					t.Errorf("ReadZip: missing file %s", name)
   572  				}
   573  			}
   574  		}
   575  	}
   576  
   577  	for _, tt := range []readZipTest{
   578  		{
   579  			repo:   gitrepo1,
   580  			rev:    "v2.3.4",
   581  			subdir: "",
   582  			files: map[string]uint64{
   583  				"prefix/":       0,
   584  				"prefix/README": 0,
   585  				"prefix/v2":     3,
   586  			},
   587  		},
   588  		{
   589  			repo:   gitsha256repo,
   590  			rev:    "v2.3.4",
   591  			subdir: "",
   592  			files: map[string]uint64{
   593  				"prefix/":       0,
   594  				"prefix/README": 0,
   595  				"prefix/v2":     3,
   596  			},
   597  		},
   598  		{
   599  			repo:   hgrepo1,
   600  			rev:    "v2.3.4",
   601  			subdir: "",
   602  			files: map[string]uint64{
   603  				"prefix/.hg_archival.txt": ^uint64(0),
   604  				"prefix/README":           0,
   605  				"prefix/v2":               3,
   606  			},
   607  		},
   608  
   609  		{
   610  			repo:   gitrepo1,
   611  			rev:    "v2",
   612  			subdir: "",
   613  			files: map[string]uint64{
   614  				"prefix/":            0,
   615  				"prefix/README":      0,
   616  				"prefix/v2":          3,
   617  				"prefix/another.txt": 8,
   618  				"prefix/foo.txt":     13,
   619  			},
   620  		},
   621  		{
   622  			repo:   gitsha256repo,
   623  			rev:    "v2",
   624  			subdir: "",
   625  			files: map[string]uint64{
   626  				"prefix/":            0,
   627  				"prefix/README":      0,
   628  				"prefix/v2":          3,
   629  				"prefix/another.txt": 8,
   630  				"prefix/foo.txt":     13,
   631  			},
   632  		},
   633  		{
   634  			repo:   hgrepo1,
   635  			rev:    "v2",
   636  			subdir: "",
   637  			files: map[string]uint64{
   638  				"prefix/.hg_archival.txt": ^uint64(0),
   639  				"prefix/README":           0,
   640  				"prefix/v2":               3,
   641  				"prefix/another.txt":      8,
   642  				"prefix/foo.txt":          13,
   643  			},
   644  		},
   645  
   646  		{
   647  			repo:   gitrepo1,
   648  			rev:    "v3",
   649  			subdir: "",
   650  			files: map[string]uint64{
   651  				"prefix/":                    0,
   652  				"prefix/v3/":                 0,
   653  				"prefix/v3/sub/":             0,
   654  				"prefix/v3/sub/dir/":         0,
   655  				"prefix/v3/sub/dir/file.txt": 16,
   656  				"prefix/README":              0,
   657  			},
   658  		},
   659  		{
   660  			repo:   gitsha256repo,
   661  			rev:    "v3",
   662  			subdir: "",
   663  			files: map[string]uint64{
   664  				"prefix/":                    0,
   665  				"prefix/v3/":                 0,
   666  				"prefix/v3/sub/":             0,
   667  				"prefix/v3/sub/dir/":         0,
   668  				"prefix/v3/sub/dir/file.txt": 16,
   669  				"prefix/README":              0,
   670  			},
   671  		},
   672  		{
   673  			repo:   hgrepo1,
   674  			rev:    "v3",
   675  			subdir: "",
   676  			files: map[string]uint64{
   677  				"prefix/.hg_archival.txt":    ^uint64(0),
   678  				"prefix/.hgtags":             405,
   679  				"prefix/v3/sub/dir/file.txt": 16,
   680  				"prefix/README":              0,
   681  			},
   682  		},
   683  
   684  		{
   685  			repo:   gitrepo1,
   686  			rev:    "v3",
   687  			subdir: "v3/sub/dir",
   688  			files: map[string]uint64{
   689  				"prefix/":                    0,
   690  				"prefix/v3/":                 0,
   691  				"prefix/v3/sub/":             0,
   692  				"prefix/v3/sub/dir/":         0,
   693  				"prefix/v3/sub/dir/file.txt": 16,
   694  			},
   695  		},
   696  		{
   697  			repo:   gitsha256repo,
   698  			rev:    "v3",
   699  			subdir: "v3/sub/dir",
   700  			files: map[string]uint64{
   701  				"prefix/":                    0,
   702  				"prefix/v3/":                 0,
   703  				"prefix/v3/sub/":             0,
   704  				"prefix/v3/sub/dir/":         0,
   705  				"prefix/v3/sub/dir/file.txt": 16,
   706  			},
   707  		},
   708  		{
   709  			repo:   hgrepo1,
   710  			rev:    "v3",
   711  			subdir: "v3/sub/dir",
   712  			files: map[string]uint64{
   713  				"prefix/v3/sub/dir/file.txt": 16,
   714  			},
   715  		},
   716  
   717  		{
   718  			repo:   gitrepo1,
   719  			rev:    "v3",
   720  			subdir: "v3/sub",
   721  			files: map[string]uint64{
   722  				"prefix/":                    0,
   723  				"prefix/v3/":                 0,
   724  				"prefix/v3/sub/":             0,
   725  				"prefix/v3/sub/dir/":         0,
   726  				"prefix/v3/sub/dir/file.txt": 16,
   727  			},
   728  		},
   729  		{
   730  			repo:   gitsha256repo,
   731  			rev:    "v3",
   732  			subdir: "v3/sub",
   733  			files: map[string]uint64{
   734  				"prefix/":                    0,
   735  				"prefix/v3/":                 0,
   736  				"prefix/v3/sub/":             0,
   737  				"prefix/v3/sub/dir/":         0,
   738  				"prefix/v3/sub/dir/file.txt": 16,
   739  			},
   740  		},
   741  		{
   742  			repo:   hgrepo1,
   743  			rev:    "v3",
   744  			subdir: "v3/sub",
   745  			files: map[string]uint64{
   746  				"prefix/v3/sub/dir/file.txt": 16,
   747  			},
   748  		},
   749  
   750  		{
   751  			repo:   gitrepo1,
   752  			rev:    "aaaaaaaaab",
   753  			subdir: "",
   754  			err:    "unknown revision",
   755  		},
   756  		{
   757  			repo:   gitsha256repo,
   758  			rev:    "aaaaaaaaab",
   759  			subdir: "",
   760  			err:    "unknown revision",
   761  		},
   762  		{
   763  			repo:   hgrepo1,
   764  			rev:    "aaaaaaaaab",
   765  			subdir: "",
   766  			err:    "unknown revision",
   767  		},
   768  
   769  		{
   770  			repo:   vgotest1,
   771  			rev:    "submod/v1.0.4",
   772  			subdir: "submod",
   773  			files: map[string]uint64{
   774  				"prefix/":                0,
   775  				"prefix/submod/":         0,
   776  				"prefix/submod/go.mod":   53,
   777  				"prefix/submod/pkg/":     0,
   778  				"prefix/submod/pkg/p.go": 31,
   779  			},
   780  		},
   781  	} {
   782  		t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, runTest(tt))
   783  		if tt.repo == gitrepo1 {
   784  			tt.repo = "localGitRepo"
   785  			t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, runTest(tt))
   786  		}
   787  	}
   788  }
   789  
   790  var hgmap = map[string]string{
   791  	"HEAD": "41964ddce1180313bdc01d0a39a2813344d6261d", // not tip due to bad hgrepo1 conversion
   792  	"9d02800338b8a55be062c838d1f02e0c5780b9eb": "8f49ee7a6ddcdec6f0112d9dca48d4a2e4c3c09e",
   793  	"76a00fb249b7f93091bc2c89a789dab1fc1bc26f": "88fde824ec8b41a76baa16b7e84212cee9f3edd0",
   794  	"ede458df7cd0fdca520df19a33158086a8a68e81": "41964ddce1180313bdc01d0a39a2813344d6261d",
   795  	"97f6aa59c81c623494825b43d39e445566e429a4": "c0cbbfb24c7c3c50c35c7b88e7db777da4ff625d",
   796  }
   797  
   798  func TestStat(t *testing.T) {
   799  	t.Parallel()
   800  
   801  	gitVers := gitVersion(t)
   802  
   803  	type statTest struct {
   804  		repo string
   805  		rev  string
   806  		err  string
   807  		info *RevInfo
   808  	}
   809  	runTest := func(tt statTest) func(*testing.T) {
   810  		return func(t *testing.T) {
   811  			t.Parallel()
   812  			ctx := testContext(t)
   813  
   814  			if tt.repo == gitsha256repo && semver.Compare(gitVers, minGitSHA256Vers) < 0 {
   815  				t.Skipf("git version is too old (%+v); skipping git sha256 test", gitVers)
   816  			}
   817  
   818  			r, err := testRepo(ctx, t, tt.repo)
   819  			if err != nil {
   820  				t.Fatal(err)
   821  			}
   822  			info, err := r.Stat(ctx, tt.rev)
   823  			if err != nil {
   824  				if tt.err == "" {
   825  					t.Fatalf("Stat: unexpected error %v", err)
   826  				}
   827  				if !strings.Contains(err.Error(), tt.err) {
   828  					t.Fatalf("Stat: wrong error %q, want %q", err, tt.err)
   829  				}
   830  				if info != nil && info.Origin == nil {
   831  					t.Errorf("Stat: non-nil info with nil Origin with error %q", err)
   832  				}
   833  				return
   834  			}
   835  			info.Origin = nil // TestLatest and ../../../testdata/script/reuse_git.txt test Origin well enough
   836  			if !reflect.DeepEqual(info, tt.info) {
   837  				t.Errorf("Stat: incorrect info\nhave %+v\nwant %+v", *info, *tt.info)
   838  			}
   839  		}
   840  	}
   841  
   842  	for _, tt := range []statTest{
   843  		{
   844  			repo: gitrepo1,
   845  			rev:  "HEAD",
   846  			info: &RevInfo{
   847  				Name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
   848  				Short:   "ede458df7cd0",
   849  				Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
   850  				Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   851  				Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   852  			},
   853  		},
   854  		{
   855  			repo: gitrepo1,
   856  			rev:  "v2", // branch
   857  			info: &RevInfo{
   858  				Name:    "9d02800338b8a55be062c838d1f02e0c5780b9eb",
   859  				Short:   "9d02800338b8",
   860  				Version: "9d02800338b8a55be062c838d1f02e0c5780b9eb",
   861  				Time:    time.Date(2018, 4, 17, 20, 00, 32, 0, time.UTC),
   862  				Tags:    []string{"v2.0.2"},
   863  			},
   864  		},
   865  		{
   866  			repo: gitrepo1,
   867  			rev:  "v2.3.4", // badly-named branch (semver should be a tag)
   868  			info: &RevInfo{
   869  				Name:    "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
   870  				Short:   "76a00fb249b7",
   871  				Version: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
   872  				Time:    time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
   873  				Tags:    []string{"v2.0.1", "v2.3"},
   874  			},
   875  		},
   876  		{
   877  			repo: gitrepo1,
   878  			rev:  "v2.3", // badly-named tag (we only respect full semver v2.3.0)
   879  			info: &RevInfo{
   880  				Name:    "76a00fb249b7f93091bc2c89a789dab1fc1bc26f",
   881  				Short:   "76a00fb249b7",
   882  				Version: "v2.3",
   883  				Time:    time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
   884  				Tags:    []string{"v2.0.1", "v2.3"},
   885  			},
   886  		},
   887  		{
   888  			repo: gitrepo1,
   889  			rev:  "v1.2.3", // tag
   890  			info: &RevInfo{
   891  				Name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
   892  				Short:   "ede458df7cd0",
   893  				Version: "v1.2.3",
   894  				Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   895  				Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   896  			},
   897  		},
   898  		{
   899  			repo: gitrepo1,
   900  			rev:  "ede458df", // hash prefix in refs
   901  			info: &RevInfo{
   902  				Name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
   903  				Short:   "ede458df7cd0",
   904  				Version: "ede458df7cd0fdca520df19a33158086a8a68e81",
   905  				Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   906  				Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   907  			},
   908  		},
   909  		{
   910  			repo: gitrepo1,
   911  			rev:  "97f6aa59", // hash prefix not in refs
   912  			info: &RevInfo{
   913  				Name:    "97f6aa59c81c623494825b43d39e445566e429a4",
   914  				Short:   "97f6aa59c81c",
   915  				Version: "97f6aa59c81c623494825b43d39e445566e429a4",
   916  				Time:    time.Date(2018, 4, 17, 20, 0, 19, 0, time.UTC),
   917  			},
   918  		},
   919  		{
   920  			repo: gitrepo1,
   921  			rev:  "v1.2.4-annotated", // annotated tag uses unwrapped commit hash
   922  			info: &RevInfo{
   923  				Name:    "ede458df7cd0fdca520df19a33158086a8a68e81",
   924  				Short:   "ede458df7cd0",
   925  				Version: "v1.2.4-annotated",
   926  				Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   927  				Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   928  			},
   929  		},
   930  		{
   931  			repo: gitrepo1,
   932  			rev:  "aaaaaaaaab",
   933  			err:  "unknown revision",
   934  		},
   935  		{
   936  			repo: gitsha256repo,
   937  			rev:  "HEAD",
   938  			info: &RevInfo{
   939  				Name:    "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c",
   940  				Short:   "47b8b51b2a2d",
   941  				Version: "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c",
   942  				Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   943  				Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   944  			},
   945  		},
   946  		{
   947  			repo: gitsha256repo,
   948  			rev:  "v2", // branch
   949  			info: &RevInfo{
   950  				Name:    "1401e4e1fdb4169b51d44a1ff62af63ccc708bf5c12d15051268b51bbb6cbd82",
   951  				Short:   "1401e4e1fdb4",
   952  				Version: "1401e4e1fdb4169b51d44a1ff62af63ccc708bf5c12d15051268b51bbb6cbd82",
   953  				Time:    time.Date(2018, 4, 17, 20, 00, 32, 0, time.UTC),
   954  				Tags:    []string{"v2.0.2"},
   955  			},
   956  		},
   957  		{
   958  			repo: gitsha256repo,
   959  			rev:  "v2.3.4", // badly-named branch (semver should be a tag)
   960  			info: &RevInfo{
   961  				Name:    "b7550fd9d2129c724c39ae0536e8b2fae4364d8c82bb8b0880c9b71f67295d09",
   962  				Short:   "b7550fd9d212",
   963  				Version: "b7550fd9d2129c724c39ae0536e8b2fae4364d8c82bb8b0880c9b71f67295d09",
   964  				Time:    time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
   965  				Tags:    []string{"v2.0.1", "v2.3"},
   966  			},
   967  		},
   968  		{
   969  			repo: gitsha256repo,
   970  			rev:  "v2.3", // badly-named tag (we only respect full semver v2.3.0)
   971  			info: &RevInfo{
   972  				Name:    "b7550fd9d2129c724c39ae0536e8b2fae4364d8c82bb8b0880c9b71f67295d09",
   973  				Short:   "b7550fd9d212",
   974  				Version: "v2.3",
   975  				Time:    time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC),
   976  				Tags:    []string{"v2.0.1", "v2.3"},
   977  			},
   978  		},
   979  		{
   980  			repo: gitsha256repo,
   981  			rev:  "v1.2.3", // tag
   982  			info: &RevInfo{
   983  				Name:    "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c",
   984  				Short:   "47b8b51b2a2d",
   985  				Version: "v1.2.3",
   986  				Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   987  				Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   988  			},
   989  		},
   990  		{
   991  			repo: gitsha256repo,
   992  			rev:  "47b8b51b", // hash prefix in refs
   993  			info: &RevInfo{
   994  				Name:    "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c",
   995  				Short:   "47b8b51b2a2d",
   996  				Version: "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c",
   997  				Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
   998  				Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
   999  			},
  1000  		},
  1001  		{
  1002  			repo: gitsha256repo,
  1003  			rev:  "0be440b6", // hash prefix not in refs
  1004  			info: &RevInfo{
  1005  				Name:    "0be440b60b6c81be26c7256781d8e57112ec46c8cd1a9481a8e78a283f10570c",
  1006  				Short:   "0be440b60b6c",
  1007  				Version: "0be440b60b6c81be26c7256781d8e57112ec46c8cd1a9481a8e78a283f10570c",
  1008  				Time:    time.Date(2018, 4, 17, 20, 0, 19, 0, time.UTC),
  1009  			},
  1010  		},
  1011  		{
  1012  			repo: gitsha256repo,
  1013  			rev:  "v1.2.4-annotated", // annotated tag uses unwrapped commit hash
  1014  			info: &RevInfo{
  1015  				Name:    "47b8b51b2a2d9d5caa3d460096c4e01f05700ce3a9390deb54400bd508214c5c",
  1016  				Short:   "47b8b51b2a2d",
  1017  				Version: "v1.2.4-annotated",
  1018  				Time:    time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC),
  1019  				Tags:    []string{"v1.2.3", "v1.2.4-annotated"},
  1020  			},
  1021  		},
  1022  		{
  1023  			repo: gitsha256repo,
  1024  			rev:  "aaaaaaaaab",
  1025  			err:  "unknown revision",
  1026  		},
  1027  	} {
  1028  		t.Run(path.Base(tt.repo)+"/"+tt.rev, runTest(tt))
  1029  		if tt.repo == gitrepo1 {
  1030  			for _, tt.repo = range altRepos() {
  1031  				old := tt
  1032  				var m map[string]string
  1033  				if tt.repo == hgrepo1 {
  1034  					m = hgmap
  1035  				}
  1036  				if tt.info != nil {
  1037  					info := *tt.info
  1038  					tt.info = &info
  1039  					tt.info.Name = remap(tt.info.Name, m)
  1040  					tt.info.Version = remap(tt.info.Version, m)
  1041  					tt.info.Short = remap(tt.info.Short, m)
  1042  				}
  1043  				tt.rev = remap(tt.rev, m)
  1044  				t.Run(path.Base(tt.repo)+"/"+tt.rev, runTest(tt))
  1045  				tt = old
  1046  			}
  1047  		}
  1048  	}
  1049  }
  1050  
  1051  func remap(name string, m map[string]string) string {
  1052  	if m[name] != "" {
  1053  		return m[name]
  1054  	}
  1055  	if AllHex(name) {
  1056  		for k, v := range m {
  1057  			if strings.HasPrefix(k, name) {
  1058  				return v[:len(name)]
  1059  			}
  1060  		}
  1061  	}
  1062  	return name
  1063  }
  1064  

View as plain text