sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/git/git_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // This test imports "sigs.k8s.io/prow/pkg/git/localgit", which also reference
    18  // "sigs.k8s.io/prow/pkg/git", has to be a separate package to avoid
    19  // circular dependency(however this file implicitly referencing "sigs.k8s.io/prow/pkg/git")
    20  package git_test
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"testing"
    29  
    30  	"github.com/sirupsen/logrus"
    31  
    32  	"sigs.k8s.io/prow/pkg/git/localgit"
    33  	"sigs.k8s.io/prow/pkg/git/types"
    34  )
    35  
    36  var defaultBranch = localgit.DefaultBranch("")
    37  
    38  func TestCloneV2(t *testing.T) {
    39  	testClone(localgit.NewV2, t)
    40  }
    41  
    42  func testClone(clients localgit.Clients, t *testing.T) {
    43  	lg, c, err := clients()
    44  	if err != nil {
    45  		t.Fatalf("Making local git repo: %v", err)
    46  	}
    47  	defer func() {
    48  		if err := lg.Clean(); err != nil {
    49  			t.Errorf("Error cleaning LocalGit: %v", err)
    50  		}
    51  		if err := c.Clean(); err != nil {
    52  			t.Errorf("Error cleaning Client: %v", err)
    53  		}
    54  	}()
    55  	if err := lg.MakeFakeRepo("foo", "bar"); err != nil {
    56  		t.Fatalf("Making fake repo: %v", err)
    57  	}
    58  	if err := lg.MakeFakeRepo("foo", "baz"); err != nil {
    59  		t.Fatalf("Making fake repo: %v", err)
    60  	}
    61  
    62  	// Fresh clone, will be a cache miss.
    63  	r1, err := c.ClientFor("foo", "bar")
    64  	if err != nil {
    65  		t.Fatalf("Cloning the first time: %v", err)
    66  	}
    67  	defer func() {
    68  		if err := r1.Clean(); err != nil {
    69  			t.Errorf("Cleaning repo: %v", err)
    70  		}
    71  	}()
    72  
    73  	// Clone from the same org.
    74  	r2, err := c.ClientFor("foo", "baz")
    75  	if err != nil {
    76  		t.Fatalf("Cloning another repo in the same org: %v", err)
    77  	}
    78  	defer func() {
    79  		if err := r2.Clean(); err != nil {
    80  			t.Errorf("Cleaning repo: %v", err)
    81  		}
    82  	}()
    83  
    84  	// Make sure it fetches when we clone again.
    85  	if err := lg.AddCommit("foo", "bar", map[string][]byte{"second": {}}); err != nil {
    86  		t.Fatalf("Adding second commit: %v", err)
    87  	}
    88  	r3, err := c.ClientFor("foo", "bar")
    89  	if err != nil {
    90  		t.Fatalf("Cloning a second time: %v", err)
    91  	}
    92  	defer func() {
    93  		if err := r3.Clean(); err != nil {
    94  			t.Errorf("Cleaning repo: %v", err)
    95  		}
    96  	}()
    97  	log := exec.Command("git", "log", "--oneline")
    98  	log.Dir = r3.Directory()
    99  	if b, err := log.CombinedOutput(); err != nil {
   100  		t.Fatalf("git log: %v, %s", err, string(b))
   101  	} else {
   102  		t.Logf("git log output: %s", string(b))
   103  		if len(bytes.Split(bytes.TrimSpace(b), []byte("\n"))) != 2 {
   104  			t.Error("Wrong number of commits in git log output. Expected 2")
   105  		}
   106  	}
   107  }
   108  
   109  func TestCheckoutPRV2(t *testing.T) {
   110  	testCheckoutPR(localgit.NewV2, t)
   111  }
   112  
   113  func testCheckoutPR(clients localgit.Clients, t *testing.T) {
   114  	lg, c, err := clients()
   115  	if err != nil {
   116  		t.Fatalf("Making local git repo: %v", err)
   117  	}
   118  	defer func() {
   119  		if err := lg.Clean(); err != nil {
   120  			t.Errorf("Error cleaning LocalGit: %v", err)
   121  		}
   122  		if err := c.Clean(); err != nil {
   123  			t.Errorf("Error cleaning Client: %v", err)
   124  		}
   125  	}()
   126  	if err := lg.MakeFakeRepo("foo", "bar"); err != nil {
   127  		t.Fatalf("Making fake repo: %v", err)
   128  	}
   129  	r, err := c.ClientFor("foo", "bar")
   130  	if err != nil {
   131  		t.Fatalf("Cloning: %v", err)
   132  	}
   133  	defer func() {
   134  		if err := r.Clean(); err != nil {
   135  			t.Errorf("Cleaning repo: %v", err)
   136  		}
   137  	}()
   138  
   139  	if err := lg.CheckoutNewBranch("foo", "bar", "pull/123/head"); err != nil {
   140  		t.Fatalf("Checkout new branch: %v", err)
   141  	}
   142  	if err := lg.AddCommit("foo", "bar", map[string][]byte{"wow": {}}); err != nil {
   143  		t.Fatalf("Add commit: %v", err)
   144  	}
   145  
   146  	if err := r.CheckoutPullRequest(123); err != nil {
   147  		t.Fatalf("Checking out PR: %v", err)
   148  	}
   149  	if _, err := os.Stat(filepath.Join(r.Directory(), "wow")); err != nil {
   150  		t.Errorf("Didn't find file in PR after checking out: %v", err)
   151  	}
   152  }
   153  
   154  func TestMergeCommitsExistBetweenV2(t *testing.T) {
   155  	testMergeCommitsExistBetween(localgit.NewV2, t)
   156  }
   157  
   158  func testMergeCommitsExistBetween(clients localgit.Clients, t *testing.T) {
   159  	lg, c, err := clients()
   160  	if err != nil {
   161  		t.Fatalf("Making local git repo: %v", err)
   162  	}
   163  	defer func() {
   164  		if err := lg.Clean(); err != nil {
   165  			t.Errorf("Cleaning up localgit: %v", err)
   166  		}
   167  		if err := c.Clean(); err != nil {
   168  			t.Errorf("Cleaning up client: %v", err)
   169  		}
   170  	}()
   171  	if err := lg.MakeFakeRepo("foo", "bar"); err != nil {
   172  		t.Fatalf("Making fake repo: %v", err)
   173  	}
   174  	r, err := c.ClientFor("foo", "bar")
   175  	if err != nil {
   176  		t.Fatalf("Cloning: %v", err)
   177  	}
   178  	defer func() {
   179  		if err := r.Clean(); err != nil {
   180  			t.Errorf("Cleaning repo: %v", err)
   181  		}
   182  	}()
   183  	var (
   184  		checkoutPR = func(prNum int) {
   185  			if err := lg.CheckoutNewBranch("foo", "bar", fmt.Sprintf("pull/%d/head", prNum)); err != nil {
   186  				t.Fatalf("Creating & checking out pull branch pull/%d/head: %v", prNum, err)
   187  			}
   188  		}
   189  		checkoutBranch = func(branch string) {
   190  			if err := lg.Checkout("foo", "bar", branch); err != nil {
   191  				t.Fatalf("Checking out branch %s: %v", branch, err)
   192  			}
   193  		}
   194  		addCommit = func(file string) {
   195  			if err := lg.AddCommit("foo", "bar", map[string][]byte{file: {}}); err != nil {
   196  				t.Fatalf("Adding commit: %v", err)
   197  			}
   198  		}
   199  		mergeMaster = func() {
   200  			if _, err := lg.Merge("foo", "bar", defaultBranch); err != nil {
   201  				t.Fatalf("Rebasing commit: %v", err)
   202  			}
   203  		}
   204  		rebaseMaster = func() {
   205  			if _, err := lg.Rebase("foo", "bar", defaultBranch); err != nil {
   206  				t.Fatalf("Rebasing commit: %v", err)
   207  			}
   208  		}
   209  	)
   210  
   211  	type testCase struct {
   212  		name          string
   213  		prNum         int
   214  		checkout      func()
   215  		mergeOrRebase func()
   216  		checkoutPR    func() error
   217  		want          bool
   218  	}
   219  	testcases := []testCase{
   220  		{
   221  			name:          "PR has merge commits",
   222  			prNum:         1,
   223  			checkout:      func() { checkoutBranch("pull/1/head") },
   224  			mergeOrRebase: mergeMaster,
   225  			checkoutPR:    func() error { return r.CheckoutPullRequest(1) },
   226  			want:          true,
   227  		},
   228  		{
   229  			name:          "PR doesn't have merge commits",
   230  			prNum:         2,
   231  			checkout:      func() { checkoutBranch("pull/2/head") },
   232  			mergeOrRebase: rebaseMaster,
   233  			checkoutPR:    func() error { return r.CheckoutPullRequest(2) },
   234  			want:          false,
   235  		},
   236  	}
   237  
   238  	addCommit("wow")
   239  	// preparation work: branch off all prs upon commit 'wow'
   240  	for _, tt := range testcases {
   241  		checkoutPR(tt.prNum)
   242  	}
   243  	// switch back to master and create a new commit 'ouch'
   244  	checkoutBranch(defaultBranch)
   245  	addCommit("ouch")
   246  	masterSHA, err := lg.RevParse("foo", "bar", "HEAD")
   247  	if err != nil {
   248  		t.Fatalf("Fetching SHA: %v", err)
   249  	}
   250  
   251  	for _, tt := range testcases {
   252  		tt.checkout()
   253  		tt.mergeOrRebase()
   254  		prSHA, err := lg.RevParse("foo", "bar", "HEAD")
   255  		if err != nil {
   256  			t.Fatalf("Fetching SHA: %v", err)
   257  		}
   258  		if err := tt.checkoutPR(); err != nil {
   259  			t.Fatalf("Checking out PR: %v", err)
   260  		}
   261  		// verify the content is up to dated
   262  		ouchPath := filepath.Join(r.Directory(), "ouch")
   263  		if _, err := os.Stat(ouchPath); err != nil {
   264  			t.Fatalf("Didn't find file 'ouch' in PR %d after merging: %v", tt.prNum, err)
   265  		}
   266  
   267  		got, err := r.MergeCommitsExistBetween(masterSHA, prSHA)
   268  		key := fmt.Sprintf("foo/bar/%d", tt.prNum)
   269  		if err != nil {
   270  			t.Errorf("Case: %v. Expect err is nil, but got %v", key, err)
   271  		}
   272  		if tt.want != got {
   273  			t.Errorf("Case: %v. Expect MergeCommitsExistBetween()=%v, but got %v", key, tt.want, got)
   274  		}
   275  	}
   276  }
   277  
   278  func TestMergeAndCheckoutV2(t *testing.T) {
   279  	testMergeAndCheckout(localgit.NewV2, t)
   280  }
   281  
   282  func testMergeAndCheckout(clients localgit.Clients, t *testing.T) {
   283  	testCases := []struct {
   284  		name          string
   285  		setBaseSHA    bool
   286  		prBranches    []string
   287  		mergeStrategy types.PullRequestMergeType
   288  		err           string
   289  	}{
   290  		{
   291  			name: "Unset baseSHA, error",
   292  			err:  "baseSHA must be set",
   293  		},
   294  		{
   295  			name:       "No mergeStrategy, error",
   296  			setBaseSHA: true,
   297  			prBranches: []string{"my-pr-branch"},
   298  			err:        "merge strategy \"\" is not supported",
   299  		},
   300  		{
   301  			name:          "Merge succeeds with rebase strategy",
   302  			setBaseSHA:    true,
   303  			prBranches:    []string{"my-pr-branch"},
   304  			mergeStrategy: types.MergeRebase,
   305  		},
   306  		{
   307  			name:       "No pullRequestHead, no error",
   308  			setBaseSHA: true,
   309  		},
   310  		{
   311  			name:          "Merge succeeds with one head and merge strategy",
   312  			setBaseSHA:    true,
   313  			prBranches:    []string{"my-pr-branch"},
   314  			mergeStrategy: types.MergeMerge,
   315  		},
   316  		{
   317  			name:          "Merge succeeds with multiple heads and merge strategy",
   318  			setBaseSHA:    true,
   319  			prBranches:    []string{"my-pr-branch", "my-other-pr-branch"},
   320  			mergeStrategy: types.MergeMerge,
   321  		},
   322  		{
   323  			name:          "Merge succeeds with one head and squash strategy",
   324  			setBaseSHA:    true,
   325  			prBranches:    []string{"my-pr-branch"},
   326  			mergeStrategy: types.MergeSquash,
   327  		},
   328  		{
   329  			name:          "Merge succeeds with multiple heads and squash stragey",
   330  			setBaseSHA:    true,
   331  			prBranches:    []string{"my-pr-branch", "my-other-pr-branch"},
   332  			mergeStrategy: types.MergeSquash,
   333  		},
   334  	}
   335  
   336  	const (
   337  		org  = "my-org"
   338  		repo = "my-repo"
   339  	)
   340  	for _, tc := range testCases {
   341  		t.Run(tc.name, func(t *testing.T) {
   342  			tc := tc
   343  			t.Parallel()
   344  
   345  			lg, c, err := clients()
   346  			if err != nil {
   347  				t.Fatalf("Making local git repo: %v", err)
   348  			}
   349  			logrus.SetLevel(logrus.DebugLevel)
   350  			defer func() {
   351  				if err := lg.Clean(); err != nil {
   352  					t.Errorf("Error cleaning LocalGit: %v", err)
   353  				}
   354  				if err := c.Clean(); err != nil {
   355  					t.Errorf("Error cleaning Client: %v", err)
   356  				}
   357  			}()
   358  			if err := lg.MakeFakeRepo(org, repo); err != nil {
   359  				t.Fatalf("Making fake repo: %v", err)
   360  			}
   361  
   362  			var commitsToMerge []string
   363  			for _, prBranch := range tc.prBranches {
   364  				if err := lg.CheckoutNewBranch(org, repo, prBranch); err != nil {
   365  					t.Fatalf("failed to checkout new branch %q: %v", prBranch, err)
   366  				}
   367  				if err := lg.AddCommit(org, repo, map[string][]byte{prBranch: []byte("val")}); err != nil {
   368  					t.Fatalf("failed to add commit: %v", err)
   369  				}
   370  				headRef, err := lg.RevParse(org, repo, "HEAD")
   371  				if err != nil {
   372  					t.Fatalf("failed to run git rev-parse: %v", err)
   373  				}
   374  				commitsToMerge = append(commitsToMerge, headRef)
   375  			}
   376  			if len(tc.prBranches) > 0 {
   377  				if err := lg.Checkout(org, repo, defaultBranch); err != nil {
   378  					t.Fatalf("failed to run git checkout master: %v", err)
   379  				}
   380  			}
   381  
   382  			var baseSHA string
   383  			if tc.setBaseSHA {
   384  				baseSHA, err = lg.RevParse(org, repo, defaultBranch)
   385  				if err != nil {
   386  					t.Fatalf("failed to run git rev-parse master: %v", err)
   387  				}
   388  			}
   389  
   390  			clonedRepo, err := c.ClientFor(org, repo)
   391  			if err != nil {
   392  				t.Fatalf("Cloning failed: %v", err)
   393  			}
   394  			if err := clonedRepo.Config("user.name", "prow"); err != nil {
   395  				t.Fatalf("failed to set name for test repo: %v", err)
   396  			}
   397  			if err := clonedRepo.Config("user.email", "prow@localhost"); err != nil {
   398  				t.Fatalf("failed to set email for test repo: %v", err)
   399  			}
   400  			if err := clonedRepo.Config("commit.gpgsign", "false"); err != nil {
   401  				t.Fatalf("failed to disable gpg signing for test repo: %v", err)
   402  			}
   403  
   404  			err = clonedRepo.MergeAndCheckout(baseSHA, string(tc.mergeStrategy), commitsToMerge...)
   405  			if err == nil && tc.err == "" {
   406  				return
   407  			}
   408  			if err == nil || err.Error() != tc.err {
   409  				t.Errorf("Expected err %q but got \"%v\"", tc.err, err)
   410  			}
   411  		})
   412  	}
   413  
   414  }
   415  
   416  func TestMergingV2(t *testing.T) {
   417  	testMerging(localgit.NewV2, t)
   418  }
   419  
   420  func testMerging(clients localgit.Clients, t *testing.T) {
   421  	testCases := []struct {
   422  		name     string
   423  		strategy string
   424  		// branch -> filename -> content
   425  		branches   map[string]map[string][]byte
   426  		mergeOrder []string
   427  	}{
   428  		{
   429  			name:     "Multiple branches, squash strategy",
   430  			strategy: "squash",
   431  			branches: map[string]map[string][]byte{
   432  				"pr-1": {"file-1": []byte("some-content")},
   433  				"pr-2": {"file-2": []byte("some-content")},
   434  			},
   435  			mergeOrder: []string{"pr-1", "pr-2"},
   436  		},
   437  		{
   438  			name:     "Multiple branches, mergeMerge strategy",
   439  			strategy: "merge",
   440  			branches: map[string]map[string][]byte{
   441  				"pr-1": {"file-1": []byte("some-content")},
   442  				"pr-2": {"file-2": []byte("some-content")},
   443  			},
   444  			mergeOrder: []string{"pr-1", "pr-2"},
   445  		},
   446  		{
   447  			name:     "Multiple branches, rebase strategy",
   448  			strategy: "rebase",
   449  			branches: map[string]map[string][]byte{
   450  				"pr-1": {"file-1": []byte("some-content")},
   451  				"pr-2": {"file-2": []byte("some-content")},
   452  			},
   453  			mergeOrder: []string{"pr-1", "pr-2"},
   454  		},
   455  	}
   456  
   457  	const org, repo = "org", "repo"
   458  	for _, tc := range testCases {
   459  		t.Run(tc.name, func(t *testing.T) {
   460  			tc := tc
   461  			t.Parallel()
   462  
   463  			lg, c, err := clients()
   464  			if err != nil {
   465  				t.Fatalf("Making local git repo: %v", err)
   466  			}
   467  			logrus.SetLevel(logrus.DebugLevel)
   468  			defer func() {
   469  				if err := lg.Clean(); err != nil {
   470  					t.Errorf("Error cleaning LocalGit: %v", err)
   471  				}
   472  				if err := c.Clean(); err != nil {
   473  					t.Errorf("Error cleaning Client: %v", err)
   474  				}
   475  			}()
   476  			if err := lg.MakeFakeRepo(org, repo); err != nil {
   477  				t.Fatalf("Making fake repo: %v", err)
   478  			}
   479  			baseSHA, err := lg.RevParse(org, repo, "HEAD")
   480  			if err != nil {
   481  				t.Fatalf("rev-parse HEAD: %v", err)
   482  			}
   483  
   484  			for branchName, branchContent := range tc.branches {
   485  				if err := lg.Checkout(org, repo, baseSHA); err != nil {
   486  					t.Fatalf("checkout baseSHA: %v", err)
   487  				}
   488  				if err := lg.CheckoutNewBranch(org, repo, branchName); err != nil {
   489  					t.Fatalf("checkout new branch: %v", err)
   490  				}
   491  				if err := lg.AddCommit(org, repo, branchContent); err != nil {
   492  					t.Fatalf("addCommit: %v", err)
   493  				}
   494  			}
   495  
   496  			if err := lg.Checkout(org, repo, baseSHA); err != nil {
   497  				t.Fatalf("checkout baseSHA: %v", err)
   498  			}
   499  
   500  			r, err := c.ClientFor(org, repo)
   501  			if err != nil {
   502  				t.Fatalf("clone: %v", err)
   503  			}
   504  			if err := r.Config("user.name", "prow"); err != nil {
   505  				t.Fatalf("config user.name: %v", err)
   506  			}
   507  			if err := r.Config("user.email", "prow@localhost"); err != nil {
   508  				t.Fatalf("config user.email: %v", err)
   509  			}
   510  			if err := r.Checkout(baseSHA); err != nil {
   511  				t.Fatalf("checkout baseSHA: %v", err)
   512  			}
   513  
   514  			for _, branch := range tc.mergeOrder {
   515  				if _, err := r.MergeWithStrategy("origin/"+branch, tc.strategy); err != nil {
   516  					t.Fatalf("mergeWithStrategy %s: %v", branch, err)
   517  				}
   518  			}
   519  		})
   520  	}
   521  }
   522  
   523  func TestShowRefV2(t *testing.T) {
   524  	testShowRef(localgit.NewV2, t)
   525  }
   526  
   527  func testShowRef(clients localgit.Clients, t *testing.T) {
   528  	const org, repo = "org", "repo"
   529  	lg, c, err := clients()
   530  	if err != nil {
   531  		t.Fatalf("failed to get clients: %v", err)
   532  	}
   533  	defer func() {
   534  		if err := lg.Clean(); err != nil {
   535  			t.Errorf("Error cleaning LocalGit: %v", err)
   536  		}
   537  		if err := c.Clean(); err != nil {
   538  			t.Errorf("Error cleaning Client: %v", err)
   539  		}
   540  	}()
   541  	if err := lg.MakeFakeRepo(org, repo); err != nil {
   542  		t.Fatalf("Making fake repo: %v", err)
   543  	}
   544  	reference, err := lg.RevParse(org, repo, "HEAD")
   545  	if err != nil {
   546  		t.Fatalf("lg.RevParse: %v", err)
   547  	}
   548  
   549  	client, err := c.ClientFor(org, repo)
   550  	if err != nil {
   551  		t.Fatalf("clientFor: %v", err)
   552  	}
   553  	res, err := client.ShowRef("HEAD")
   554  	if err != nil {
   555  		t.Fatalf("ShowRef: %v", err)
   556  	}
   557  	if res != reference {
   558  		t.Errorf("expeted result to be %s, was %s", reference, res)
   559  	}
   560  }