github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/vcs/git_test.go (about)

     1  // Copyright 2017 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package vcs
     5  
     6  import (
     7  	"os"
     8  	"reflect"
     9  	"sort"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/google/go-cmp/cmp"
    14  	"github.com/stretchr/testify/assert"
    15  )
    16  
    17  func TestGitParseCommit(t *testing.T) {
    18  	tests := map[string]*Commit{
    19  		`2075b16e32c26e4031b9fd3cbe26c54676a8fcb5
    20  rbtree: include rcu.h
    21  foobar@foobar.de
    22  Foo Bar
    23  Fri May 11 16:02:14 2018 -0700
    24  78eb0c6356cda285c6ee6e29bea0c0188368103e
    25  Fri May 11 17:28:45 2018 -0700
    26  Since commit c1adf20052d8 ("Introduce rb_replace_node_rcu()")
    27  rbtree_augmented.h uses RCU related data structures but does not include
    28  the header file.  It works as long as it gets somehow included before
    29  that and fails otherwise.
    30  
    31  Link: http://lkml.kernel.org/r/20180504103159.19938-1-bigeasy@linutronix.de
    32  Signed-off-by: Foo Bad Baz <another@email.de>
    33  Reviewed-by: <yetanother@email.org>
    34  Cc: Unrelated Guy <somewhere@email.com>
    35  Acked-by: Subsystem reviewer <Subsystem@reviewer.com>
    36  Reported-and-tested-by: and@me.com
    37  Reported-and-Tested-by: Name-name <name@name.com>
    38  Tested-by: Must be correct <mustbe@correct.com>
    39  Signed-off-by: Linux Master <linux@linux-foundation.org>
    40  `: {
    41  			Hash:       "2075b16e32c26e4031b9fd3cbe26c54676a8fcb5",
    42  			Title:      "rbtree: include rcu.h",
    43  			Author:     "foobar@foobar.de",
    44  			AuthorName: "Foo Bar",
    45  			Recipients: NewRecipients([]string{
    46  				"and@me.com",
    47  				"another@email.de",
    48  				"foobar@foobar.de",
    49  				"linux@linux-foundation.org",
    50  				"mustbe@correct.com",
    51  				"name@name.com",
    52  				"subsystem@reviewer.com",
    53  				"yetanother@email.org",
    54  			}, To),
    55  			Date:       time.Date(2018, 5, 11, 16, 02, 14, 0, time.FixedZone("", -7*60*60)),
    56  			CommitDate: time.Date(2018, 5, 11, 17, 28, 45, 0, time.FixedZone("", -7*60*60)),
    57  		},
    58  	}
    59  	for input, com := range tests {
    60  		res, err := gitParseCommit([]byte(input), nil, nil, nil)
    61  		if err != nil && com != nil {
    62  			t.Fatalf("want %+v, got error: %v", com, err)
    63  		}
    64  		if err == nil && com == nil {
    65  			t.Fatalf("want error, got commit %+v", res)
    66  		}
    67  		if com == nil {
    68  			continue
    69  		}
    70  		if com.Hash != res.Hash {
    71  			t.Fatalf("want hash %q, got %q", com.Hash, res.Hash)
    72  		}
    73  		if com.Title != res.Title {
    74  			t.Fatalf("want title %q, got %q", com.Title, res.Title)
    75  		}
    76  		if com.Author != res.Author {
    77  			t.Fatalf("want author %q, got %q", com.Author, res.Author)
    78  		}
    79  		if diff := cmp.Diff(com.Recipients, res.Recipients); diff != "" {
    80  			t.Fatalf("bad CC: %v", diff)
    81  		}
    82  		if !com.Date.Equal(res.Date) {
    83  			t.Fatalf("want date %v, got %v", com.Date, res.Date)
    84  		}
    85  		if !com.CommitDate.Equal(res.CommitDate) {
    86  			t.Fatalf("want date %v, got %v", com.CommitDate, res.CommitDate)
    87  		}
    88  	}
    89  }
    90  
    91  func TestGitParseReleaseTags(t *testing.T) {
    92  	input := `
    93  v3.1
    94  v2.6.12
    95  v2.6.39
    96  v3.0
    97  v3.10
    98  v2.6.13
    99  v3.11
   100  v3.19
   101  v3.9
   102  v3.2
   103  v4.9-rc1
   104  v4.9
   105  v4.9-rc3
   106  v4.9-rc2
   107  v2.6.32
   108  v4.0
   109  vv4.1
   110  v2.6-rc5
   111  v4.1foo
   112  voo
   113  v1.foo
   114  v2.6-rc2
   115  v10.2.foo
   116  v1.2.
   117  v1.
   118  `
   119  	want := []string{
   120  		"v4.9",
   121  		"v4.0",
   122  		"v3.19",
   123  		"v3.11",
   124  		"v3.10",
   125  		"v3.9",
   126  		"v3.2",
   127  		"v3.1",
   128  		"v3.0",
   129  		"v2.6.39",
   130  		"v2.6.32",
   131  		"v2.6.13",
   132  		"v2.6.12",
   133  	}
   134  	got := gitParseReleaseTags([]byte(input), false)
   135  	if !reflect.DeepEqual(got, want) {
   136  		t.Fatalf("got bad tags\ngot:  %+v\nwant: %+v", got, want)
   137  	}
   138  	wantRC := []string{
   139  		"v4.9",
   140  		"v4.9-rc3",
   141  		"v4.9-rc2",
   142  		"v4.9-rc1",
   143  		"v4.0",
   144  		"v3.19",
   145  		"v3.11",
   146  		"v3.10",
   147  		"v3.9",
   148  		"v3.2",
   149  		"v3.1",
   150  		"v3.0",
   151  		"v2.6.39",
   152  		"v2.6.32",
   153  		"v2.6.13",
   154  		"v2.6.12",
   155  		"v2.6-rc5",
   156  		"v2.6-rc2",
   157  	}
   158  	gotRC := gitParseReleaseTags([]byte(input), true)
   159  	if !reflect.DeepEqual(gotRC, wantRC) {
   160  		t.Fatalf("got bad tags\ngot:  %+v\nwant: %+v", gotRC, wantRC)
   161  	}
   162  }
   163  
   164  func TestGetCommitsByTitles(t *testing.T) {
   165  	baseDir := t.TempDir()
   166  	repo := MakeTestRepo(t, baseDir)
   167  
   168  	validateSuccess := func(commit *Commit, results []*Commit, missing []string, err error) {
   169  		if err != nil {
   170  			t.Fatalf("expected success, got %v", err)
   171  		}
   172  		if len(missing) > 0 {
   173  			t.Fatalf("expected 0 missing, got %v", missing)
   174  		}
   175  		if len(results) != 1 {
   176  			t.Fatalf("expected 1 results, got %v", len(results))
   177  		}
   178  		if results[0].Hash != commit.Hash {
   179  			t.Fatalf("found unexpected commit %v", results[0].Hash)
   180  		}
   181  	}
   182  
   183  	// Put three commits in branch-a, two with the title we search for.
   184  	// We expect GetCommitsByTitles to only return the most recent match.
   185  	repo.Git("checkout", "-b", "branch-a")
   186  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   187  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "abc")
   188  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   189  	commitA, _ := repo.repo.Commit(HEAD)
   190  	results, missing, err := repo.repo.GetCommitsByTitles([]string{"target"})
   191  	validateSuccess(commitA, results, missing, err)
   192  
   193  	// Put another commit with the title we search for in another branch.
   194  	// We expect GetCommitsByTitles to only find commits in the current branch.
   195  	repo.Git("checkout", "-b", "branch-b")
   196  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   197  	repo.Git("checkout", "branch-a")
   198  	results, missing, err = repo.repo.GetCommitsByTitles([]string{"target"})
   199  	validateSuccess(commitA, results, missing, err)
   200  
   201  	// We expect GetCommitsByTitles to only find commits in the current branch.
   202  	repo.Git("checkout", "branch-b")
   203  	results, missing, err = repo.repo.GetCommitsByTitles([]string{"xyz"})
   204  	if err != nil {
   205  		t.Fatalf("expected success, got %v", err)
   206  	}
   207  	if len(results) > 0 {
   208  		t.Fatalf("expected 0 results, got %v", len(results))
   209  	}
   210  	if len(missing) != 1 {
   211  		t.Fatalf("expected 1 missing, got %v", missing)
   212  	}
   213  	if missing[0] != "xyz" {
   214  		t.Fatalf("found unexpected value in missing %v", missing[0])
   215  	}
   216  }
   217  
   218  func TestContains(t *testing.T) {
   219  	baseDir := t.TempDir()
   220  	repo := MakeTestRepo(t, baseDir)
   221  
   222  	// We expect Contains to return true, if commit is in current checkout.
   223  	repo.Git("checkout", "-b", "branch-a")
   224  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   225  	commitA, _ := repo.repo.Commit(HEAD)
   226  	if contained, _ := repo.repo.Contains(commitA.Hash); !contained {
   227  		t.Fatalf("contains claims commit that should be present is not")
   228  	}
   229  	if contained, _ := repo.repo.Contains("fake-hash"); contained {
   230  		t.Fatalf("contains claims commit that is not present is present")
   231  	}
   232  
   233  	// Commits must only be searched for from the checkedout HEAD.
   234  	repo.Git("checkout", "-b", "branch-b")
   235  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   236  	commitB, _ := repo.repo.Commit(HEAD)
   237  	repo.Git("checkout", "branch-a")
   238  	if contained, _ := repo.repo.Contains(commitB.Hash); contained {
   239  		t.Fatalf("contains found commit that is not in current branch")
   240  	}
   241  }
   242  
   243  func TestLatestCommits(t *testing.T) {
   244  	baseDir := t.TempDir()
   245  	repo := MakeTestRepo(t, baseDir)
   246  
   247  	repo.Git("checkout", "-b", "branch-a")
   248  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   249  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   250  	got, err := repo.repo.LatestCommits("", time.Time{})
   251  	assert.NoError(t, err)
   252  	assert.Len(t, got, 2, "expected 2 commits")
   253  	for i, commit := range got {
   254  		if contained, _ := repo.repo.Contains(commit.Hash); !contained {
   255  			t.Fatalf("commit %d is not contained", i)
   256  		}
   257  	}
   258  
   259  	// Now ignore the first commit.
   260  	got2, err := repo.repo.LatestCommits(got[1].Hash, time.Time{})
   261  	assert.NoError(t, err)
   262  	assert.Len(t, got2, 1, "expected 1 commit")
   263  	assert.Equal(t, got2[0].Hash, got[0].Hash, "expected to see the HEAD commit")
   264  
   265  	// TODO: test the afterDate argument.
   266  	// It will require setting the GIT_COMMITTER_DATE env variable.
   267  }
   268  
   269  func TestObject(t *testing.T) {
   270  	baseDir := t.TempDir()
   271  	repo := MakeTestRepo(t, baseDir)
   272  	firstRev := []byte("First revision")
   273  	secondRev := []byte("Second revision")
   274  
   275  	if err := os.WriteFile(baseDir+"/object.txt", firstRev, 0644); err != nil {
   276  		t.Fatal(err)
   277  	}
   278  	repo.Git("add", "object.txt")
   279  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   280  
   281  	if err := os.WriteFile(baseDir+"/object.txt", secondRev, 0644); err != nil {
   282  		t.Fatal(err)
   283  	}
   284  	repo.Git("add", "object.txt")
   285  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   286  
   287  	commits, err := repo.repo.LatestCommits("", time.Time{})
   288  	if err != nil {
   289  		t.Fatal(err)
   290  	}
   291  	if len(commits) != 2 {
   292  		t.Fatalf("expected 2 commits, got %d", len(commits))
   293  	}
   294  	// Verify file's contents at the first revision.
   295  	data, err := repo.repo.Object("object.txt", commits[1].Hash)
   296  	if err != nil {
   297  		t.Fatal(err)
   298  	}
   299  	if diff := cmp.Diff(data, firstRev); diff != "" {
   300  		t.Fatal(diff)
   301  	}
   302  	// And at the second one.
   303  	data, err = repo.repo.Object("object.txt", commits[0].Hash)
   304  	if err != nil {
   305  		t.Fatal(err)
   306  	}
   307  	if diff := cmp.Diff(data, secondRev); diff != "" {
   308  		t.Fatal(diff)
   309  	}
   310  	com, err := repo.repo.Commit(commits[0].Hash)
   311  	if err != nil {
   312  		t.Fatal(err.Error())
   313  	}
   314  	patch := []byte(`diff --git a/object.txt b/object.txt
   315  index 103167d..fbf7a68 100644
   316  --- a/object.txt
   317  +++ b/object.txt
   318  @@ -1 +1 @@
   319  -First revision
   320  \ No newline at end of file
   321  +Second revision
   322  \ No newline at end of file
   323  `)
   324  	if diff := cmp.Diff(com.Patch, patch); diff != "" {
   325  		t.Fatal(diff)
   326  	}
   327  }
   328  
   329  func TestMergeBase(t *testing.T) {
   330  	baseDir := t.TempDir()
   331  	repo := MakeTestRepo(t, baseDir)
   332  
   333  	// Create base branch.
   334  	repo.Git("checkout", "-b", "base")
   335  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   336  	baseCommit, _ := repo.repo.Commit(HEAD)
   337  
   338  	// Fork off another branch.
   339  	repo.Git("checkout", "-b", "fork")
   340  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   341  	forkCommit, _ := repo.repo.Commit(HEAD)
   342  
   343  	// Ensure that merge base points to the base commit.
   344  	mergeCommits, err := repo.repo.MergeBases(baseCommit.Hash, forkCommit.Hash)
   345  	if err != nil {
   346  		t.Fatal(err)
   347  	} else if len(mergeCommits) != 1 || mergeCommits[0].Hash != baseCommit.Hash {
   348  		t.Fatalf("expected base commit, got %v", mergeCommits)
   349  	}
   350  
   351  	// Let branches diverge.
   352  	repo.Git("checkout", "base")
   353  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "newBase")
   354  	newBaseCommit, _ := repo.repo.Commit(HEAD)
   355  
   356  	repo.Git("checkout", "fork")
   357  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "newFork")
   358  	newForkCommit, _ := repo.repo.Commit(HEAD)
   359  
   360  	// The merge base should remain the same.
   361  	mergeCommits, err = repo.repo.MergeBases(newBaseCommit.Hash, newForkCommit.Hash)
   362  	if err != nil {
   363  		t.Fatal(err)
   364  	} else if len(mergeCommits) != 1 || mergeCommits[0].Hash != baseCommit.Hash {
   365  		t.Fatalf("expected base commit (%s), got %d other commits",
   366  			baseCommit.Hash, len(mergeCommits))
   367  	}
   368  
   369  	// Now do the merge.
   370  	repo.Git("merge", "base")
   371  
   372  	// And advance the fork branch.
   373  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   374  	newNewForkCommit, _ := repo.repo.Commit(HEAD)
   375  
   376  	// The merge base should point to the last commit in `base`.
   377  	mergeCommits, err = repo.repo.MergeBases(newBaseCommit.Hash, newNewForkCommit.Hash)
   378  	if err != nil {
   379  		t.Fatal(err)
   380  	} else if len(mergeCommits) != 1 || mergeCommits[0].Hash != newBaseCommit.Hash {
   381  		t.Fatalf("expected base commit, got %v", mergeCommits)
   382  	}
   383  }
   384  
   385  func TestGitCustomRefs(t *testing.T) {
   386  	remoteRepoDir := t.TempDir()
   387  	remote := MakeTestRepo(t, remoteRepoDir)
   388  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "base commit")
   389  	remote.Git("checkout", "-b", "base_branch")
   390  	remote.Git("tag", "base_tag")
   391  
   392  	// Create a commit non reachable from any branch or tag.
   393  	remote.Git("checkout", "base_branch")
   394  	remote.Git("checkout", "-b", "temp_branch")
   395  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "detached commit")
   396  	// Add a ref to prevent the commit from getting garbage collected.
   397  	remote.Git("update-ref", "refs/custom/test", "temp_branch")
   398  	refCommit, _ := remote.repo.Commit(HEAD)
   399  
   400  	// Remove the branch, let the commit stay only in refs.
   401  	remote.Git("checkout", "base_branch")
   402  	remote.Git("branch", "-D", "temp_branch")
   403  
   404  	// Create a local repo.
   405  	localRepoDir := t.TempDir()
   406  	local := newGitRepo(localRepoDir, nil, nil)
   407  
   408  	// Fetch the commit from the custom ref.
   409  	_, err := local.CheckoutCommit(remoteRepoDir, refCommit.Hash)
   410  	assert.NoError(t, err)
   411  }
   412  
   413  func TestGitRemoteTags(t *testing.T) {
   414  	remoteRepoDir := t.TempDir()
   415  	remote := MakeTestRepo(t, remoteRepoDir)
   416  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "base commit")
   417  	remote.Git("checkout", "-b", "base_branch")
   418  	remote.Git("tag", "v1.0")
   419  
   420  	// Diverge sub_branch and add a tag.
   421  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "sub-branch")
   422  	remote.Git("checkout", "-b", "sub_branch")
   423  	remote.Git("tag", "v2.0")
   424  
   425  	// Create a local repo.
   426  	localRepoDir := t.TempDir()
   427  	local := newGitRepo(localRepoDir, nil, nil)
   428  
   429  	// Ensure all tags were fetched.
   430  	commit, err := local.CheckoutCommit(remoteRepoDir, "sub_branch")
   431  	assert.NoError(t, err)
   432  	tags, err := local.previousReleaseTags(commit.Hash, true, false, false)
   433  	assert.NoError(t, err)
   434  	sort.Strings(tags)
   435  	assert.Equal(t, []string{"v1.0", "v2.0"}, tags)
   436  }
   437  
   438  func TestGitFetchShortHash(t *testing.T) {
   439  	remoteRepoDir := t.TempDir()
   440  	remote := MakeTestRepo(t, remoteRepoDir)
   441  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "base commit")
   442  	remote.Git("checkout", "-b", "base_branch")
   443  	remote.Git("tag", "base_tag")
   444  	remote.Git("checkout", "-b", "temp_branch")
   445  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "detached commit")
   446  	refCommit, _ := remote.repo.Commit(HEAD)
   447  
   448  	// Create a local repo.
   449  	localRepoDir := t.TempDir()
   450  	local := newGitRepo(localRepoDir, nil, nil)
   451  
   452  	// Fetch the commit from the custom ref.
   453  	_, err := local.CheckoutCommit(remoteRepoDir, refCommit.Hash[:12])
   454  	assert.NoError(t, err)
   455  }
   456  
   457  func TestParseGitDiff(t *testing.T) {
   458  	files := ParseGitDiff([]byte(`diff --git a/a.txt b/a.txt
   459  index 4c5fd91..8fe1e32 100644
   460  --- a/a.txt
   461  +++ b/a.txt
   462  @@ -1 +1 @@
   463  -First file
   464  +First file!
   465  diff --git a/b.txt b/b.txt
   466  new file mode 100644
   467  index 0000000..f8a9677
   468  --- /dev/null
   469  +++ b/b.txt
   470  @@ -0,0 +1 @@
   471  +Second file.
   472  diff --git a/c/c.txt b/c/c.txt
   473  new file mode 100644
   474  index 0000000..e69de29
   475  `))
   476  	assert.ElementsMatch(t, files, []string{"a.txt", "b.txt", "c/c.txt"})
   477  }