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

View as plain text