github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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.HeadCommit()
   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.HeadCommit()
   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.HeadCommit()
   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 TestCommitHashes(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("checkout", "-b", "branch-b")
   250  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   251  	got, err := repo.repo.ListCommitHashes("HEAD")
   252  	if err != nil {
   253  		t.Fatal(err)
   254  	}
   255  	if len(got) != 2 {
   256  		t.Fatalf("expected 2 commits")
   257  	}
   258  	for i, commit := range got {
   259  		if contained, _ := repo.repo.Contains(commit); !contained {
   260  			t.Fatalf("commit %d is not contained", i)
   261  		}
   262  	}
   263  
   264  	// Now change HEAD.
   265  	repo.Git("checkout", "branch-a")
   266  	got, err = repo.repo.ListCommitHashes("HEAD")
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	if len(got) != 1 {
   271  		t.Fatalf("expected 1 commit, got %d", len(got))
   272  	}
   273  	if contained, _ := repo.repo.Contains(got[0]); !contained {
   274  		t.Fatalf("commit in branch-b is not contained")
   275  	}
   276  }
   277  
   278  func TestObject(t *testing.T) {
   279  	baseDir := t.TempDir()
   280  	repo := MakeTestRepo(t, baseDir)
   281  	firstRev := []byte("First revision")
   282  	secondRev := []byte("Second revision")
   283  
   284  	if err := os.WriteFile(baseDir+"/object.txt", firstRev, 0644); err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	repo.Git("add", "object.txt")
   288  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   289  
   290  	if err := os.WriteFile(baseDir+"/object.txt", secondRev, 0644); err != nil {
   291  		t.Fatal(err)
   292  	}
   293  	repo.Git("add", "object.txt")
   294  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   295  
   296  	commits, err := repo.repo.ListCommitHashes("HEAD")
   297  	if err != nil {
   298  		t.Fatal(err)
   299  	}
   300  	if len(commits) != 2 {
   301  		t.Fatalf("expected 2 commits, got %d", len(commits))
   302  	}
   303  	// Verify file's contents at the first revision.
   304  	data, err := repo.repo.Object("object.txt", commits[1])
   305  	if err != nil {
   306  		t.Fatal(err)
   307  	}
   308  	if diff := cmp.Diff(data, firstRev); diff != "" {
   309  		t.Fatal(diff)
   310  	}
   311  	// And at the second one.
   312  	data, err = repo.repo.Object("object.txt", commits[0])
   313  	if err != nil {
   314  		t.Fatal(err)
   315  	}
   316  	if diff := cmp.Diff(data, secondRev); diff != "" {
   317  		t.Fatal(diff)
   318  	}
   319  }
   320  
   321  func TestMergeBase(t *testing.T) {
   322  	baseDir := t.TempDir()
   323  	repo := MakeTestRepo(t, baseDir)
   324  
   325  	// Create base branch.
   326  	repo.Git("checkout", "-b", "base")
   327  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   328  	baseCommit, _ := repo.repo.HeadCommit()
   329  
   330  	// Fork off another branch.
   331  	repo.Git("checkout", "-b", "fork")
   332  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   333  	forkCommit, _ := repo.repo.HeadCommit()
   334  
   335  	// Ensure that merge base points to the base commit.
   336  	mergeCommits, err := repo.repo.MergeBases(baseCommit.Hash, forkCommit.Hash)
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	} else if len(mergeCommits) != 1 || mergeCommits[0].Hash != baseCommit.Hash {
   340  		t.Fatalf("expected base commit, got %v", mergeCommits)
   341  	}
   342  
   343  	// Let branches diverge.
   344  	repo.Git("checkout", "base")
   345  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "newBase")
   346  	newBaseCommit, _ := repo.repo.HeadCommit()
   347  
   348  	repo.Git("checkout", "fork")
   349  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "newFork")
   350  	newForkCommit, _ := repo.repo.HeadCommit()
   351  
   352  	// The merge base should remain the same.
   353  	mergeCommits, err = repo.repo.MergeBases(newBaseCommit.Hash, newForkCommit.Hash)
   354  	if err != nil {
   355  		t.Fatal(err)
   356  	} else if len(mergeCommits) != 1 || mergeCommits[0].Hash != baseCommit.Hash {
   357  		t.Fatalf("expected base commit (%s), got %d other commits",
   358  			baseCommit.Hash, len(mergeCommits))
   359  	}
   360  
   361  	// Now do the merge.
   362  	repo.Git("merge", "base")
   363  
   364  	// And advance the fork branch.
   365  	repo.Git("commit", "--no-edit", "--allow-empty", "-m", "target")
   366  	newNewForkCommit, _ := repo.repo.HeadCommit()
   367  
   368  	// The merge base should point to the last commit in `base`.
   369  	mergeCommits, err = repo.repo.MergeBases(newBaseCommit.Hash, newNewForkCommit.Hash)
   370  	if err != nil {
   371  		t.Fatal(err)
   372  	} else if len(mergeCommits) != 1 || mergeCommits[0].Hash != newBaseCommit.Hash {
   373  		t.Fatalf("expected base commit, got %v", mergeCommits)
   374  	}
   375  }
   376  
   377  func TestGitCustomRefs(t *testing.T) {
   378  	remoteRepoDir := t.TempDir()
   379  	remote := MakeTestRepo(t, remoteRepoDir)
   380  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "base commit")
   381  	remote.Git("checkout", "-b", "base_branch")
   382  	remote.Git("tag", "base_tag")
   383  
   384  	// Create a commit non reachable from any branch or tag.
   385  	remote.Git("checkout", "base_branch")
   386  	remote.Git("checkout", "-b", "temp_branch")
   387  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "detached commit")
   388  	// Add a ref to prevent the commit from getting garbage collected.
   389  	remote.Git("update-ref", "refs/custom/test", "temp_branch")
   390  	refCommit, _ := remote.repo.HeadCommit()
   391  
   392  	// Remove the branch, let the commit stay only in refs.
   393  	remote.Git("checkout", "base_branch")
   394  	remote.Git("branch", "-D", "temp_branch")
   395  
   396  	// Create a local repo.
   397  	localRepoDir := t.TempDir()
   398  	local := newGit(localRepoDir, nil, nil)
   399  
   400  	// Fetch the commit from the custom ref.
   401  	_, err := local.CheckoutCommit(remoteRepoDir, refCommit.Hash)
   402  	assert.NoError(t, err)
   403  }
   404  
   405  func TestGitRemoteTags(t *testing.T) {
   406  	remoteRepoDir := t.TempDir()
   407  	remote := MakeTestRepo(t, remoteRepoDir)
   408  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "base commit")
   409  	remote.Git("checkout", "-b", "base_branch")
   410  	remote.Git("tag", "v1.0")
   411  
   412  	// Diverge sub_branch and add a tag.
   413  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "sub-branch")
   414  	remote.Git("checkout", "-b", "sub_branch")
   415  	remote.Git("tag", "v2.0")
   416  
   417  	// Create a local repo.
   418  	localRepoDir := t.TempDir()
   419  	local := newGit(localRepoDir, nil, nil)
   420  
   421  	// Ensure all tags were fetched.
   422  	commit, err := local.CheckoutCommit(remoteRepoDir, "sub_branch")
   423  	assert.NoError(t, err)
   424  	tags, err := local.previousReleaseTags(commit.Hash, true, false, false)
   425  	assert.NoError(t, err)
   426  	sort.Strings(tags)
   427  	assert.Equal(t, []string{"v1.0", "v2.0"}, tags)
   428  }
   429  
   430  func TestGitFetchShortHash(t *testing.T) {
   431  	remoteRepoDir := t.TempDir()
   432  	remote := MakeTestRepo(t, remoteRepoDir)
   433  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "base commit")
   434  	remote.Git("checkout", "-b", "base_branch")
   435  	remote.Git("tag", "base_tag")
   436  	remote.Git("checkout", "-b", "temp_branch")
   437  	remote.Git("commit", "--no-edit", "--allow-empty", "-m", "detached commit")
   438  	refCommit, _ := remote.repo.HeadCommit()
   439  
   440  	// Create a local repo.
   441  	localRepoDir := t.TempDir()
   442  	local := newGit(localRepoDir, nil, nil)
   443  
   444  	// Fetch the commit from the custom ref.
   445  	_, err := local.CheckoutCommit(remoteRepoDir, refCommit.Hash[:12])
   446  	assert.NoError(t, err)
   447  }