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

View as plain text