blob: 0a7337e053ce9a8d9dae197886a3c8fca31ac4cd [file] [log] [blame]
package git_checkout
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cipd_git "go.skia.org/infra/bazel/external/cipd/git"
"go.skia.org/infra/go/git/testutils"
"go.skia.org/infra/perf/go/config"
"go.skia.org/infra/perf/go/git/provider"
"go.skia.org/infra/perf/go/types"
)
var (
// StartTime is the time of the first commit.
StartTime = time.Unix(1680000000, 0)
)
// NewForTest returns all the necessary variables needed to test against infra/go/git.
//
// The repo is populated with 8 commits, one minute apart, starting at StartTime.
//
// The hashes for each commit are going to be random and so are returned also.
func NewForTest(t *testing.T) (context.Context, *testutils.GitBuilder, []string, *config.InstanceConfig) {
ctx := cipd_git.UseGitFinder(context.Background())
ctx, cancel := context.WithCancel(ctx)
// Create a git repo for testing purposes.
gb := testutils.GitInit(t, ctx)
hashes := []string{}
hashes = append(hashes, gb.CommitGenAt(ctx, "foo.txt", StartTime))
hashes = append(hashes, gb.CommitGenAt(ctx, "foo.txt", StartTime.Add(time.Minute)))
hashes = append(hashes, gb.CommitGenAt(ctx, "foo.txt", StartTime.Add(2*time.Minute)))
hashes = append(hashes, gb.CommitGenAt(ctx, "bar.txt", StartTime.Add(3*time.Minute)))
hashes = append(hashes, gb.CommitGenAt(ctx, "foo.txt", StartTime.Add(4*time.Minute)))
hashes = append(hashes, gb.CommitGenAt(ctx, "foo.txt", StartTime.Add(5*time.Minute)))
hashes = append(hashes, gb.CommitGenAt(ctx, "bar.txt", StartTime.Add(6*time.Minute)))
hashes = append(hashes, gb.CommitGenAt(ctx, "foo.txt", StartTime.Add(7*time.Minute)))
// Get tmp dir to use for repo checkout.
tmpDir, err := os.MkdirTemp("", "git")
require.NoError(t, err)
// Create the cleanup function.
t.Cleanup(func() {
cancel()
err = os.RemoveAll(tmpDir)
assert.NoError(t, err)
gb.Cleanup()
})
instanceConfig := &config.InstanceConfig{
GitRepoConfig: config.GitRepoConfig{
URL: gb.Dir(),
Dir: filepath.Join(tmpDir, "checkout"),
},
}
return ctx, gb, hashes, instanceConfig
}
func TestParseGitRevLogStream_Success(t *testing.T) {
r := strings.NewReader(
`commit 6079a7810530025d9877916895dd14eb8bb454c0
Joe Gregorio <joe@bitworking.org>
Change #9
1584837783`)
err := parseGitRevLogStream(io.NopCloser(r), func(p provider.Commit) error {
assert.Equal(t, provider.Commit{
CommitNumber: types.BadCommitNumber,
GitHash: "6079a7810530025d9877916895dd14eb8bb454c0",
Timestamp: 1584837783,
Author: "Joe Gregorio <joe@bitworking.org>",
Subject: "Change #9"}, p)
return nil
})
assert.NoError(t, err)
}
func TestParseGitRevLogStream_ErrPropagatesWhenCallbackReturnsError(t *testing.T) {
r := strings.NewReader(
`commit 6079a7810530025d9877916895dd14eb8bb454c0
Joe Gregorio <joe@bitworking.org>
Change #9
1584837783`)
err := parseGitRevLogStream(io.NopCloser(r), func(p provider.Commit) error {
return fmt.Errorf("This is an error.")
})
assert.Contains(t, err.Error(), "This is an error.")
}
func TestParseGitRevLogStream_SuccessForTwoCommits(t *testing.T) {
r := strings.NewReader(
`commit 6079a7810530025d9877916895dd14eb8bb454c0
Joe Gregorio <joe@bitworking.org>
Change #9
1584837783
commit 977e0ef44bec17659faf8c5d4025c5a068354817
Joe Gregorio <joe@bitworking.org>
Change #8
1584837780`)
count := 0
hashes := []string{"6079a7810530025d9877916895dd14eb8bb454c0", "977e0ef44bec17659faf8c5d4025c5a068354817"}
err := parseGitRevLogStream(io.NopCloser(r), func(p provider.Commit) error {
assert.Equal(t, "Joe Gregorio <joe@bitworking.org>", p.Author)
assert.Equal(t, hashes[count], p.GitHash)
count++
return nil
})
assert.Equal(t, 2, count)
assert.NoError(t, err)
}
func TestParseGitRevLogStream_EmptyFile_Success(t *testing.T) {
r := strings.NewReader("")
err := parseGitRevLogStream(io.NopCloser(r), func(p provider.Commit) error {
assert.Fail(t, "Should never get here.")
return nil
})
assert.NoError(t, err)
}
func TestParseGitRevLogStream_ErrMissingTimestamp(t *testing.T) {
r := strings.NewReader(
`commit 6079a7810530025d9877916895dd14eb8bb454c0
Joe Gregorio <joe@bitworking.org>
Change #9`)
err := parseGitRevLogStream(io.NopCloser(r), func(p provider.Commit) error {
assert.Fail(t, "Should never get here.")
return nil
})
assert.Contains(t, err.Error(), "expecting a timestamp")
}
func TestParseGitRevLogStream_ErrFailedToParseTimestamp(t *testing.T) {
r := strings.NewReader(
`commit 6079a7810530025d9877916895dd14eb8bb454c0
Joe Gregorio <joe@bitworking.org>
Change #9
ooops 1584837780`)
err := parseGitRevLogStream(io.NopCloser(r), func(p provider.Commit) error {
assert.Fail(t, "Should never get here.")
return nil
})
assert.Contains(t, err.Error(), "Failed to parse timestamp")
}
func TestParseGitRevLogStream_ErrMissingSubject(t *testing.T) {
r := strings.NewReader(
`commit 6079a7810530025d9877916895dd14eb8bb454c0
Joe Gregorio <joe@bitworking.org>`)
err := parseGitRevLogStream(io.NopCloser(r), func(p provider.Commit) error {
assert.Fail(t, "Should never get here.")
return nil
})
assert.Contains(t, err.Error(), "expecting a subject")
}
func TestParseGitRevLogStream_ErrMissingAuthor(t *testing.T) {
r := strings.NewReader(
`commit 6079a7810530025d9877916895dd14eb8bb454c0`)
err := parseGitRevLogStream(io.NopCloser(r), func(p provider.Commit) error {
assert.Fail(t, "Should never get here.")
return nil
})
assert.Contains(t, err.Error(), "expecting an author")
}
func TestParseGitRevLogStream_ErrMalformedCommitLine(t *testing.T) {
r := strings.NewReader(
`something_not_commit 6079a7810530025d9877916895dd14eb8bb454c0`)
err := parseGitRevLogStream(io.NopCloser(r), func(p provider.Commit) error {
assert.Fail(t, "Should never get here.")
return nil
})
assert.Contains(t, err.Error(), "expected commit at")
}
func TestLogEntry_Success(t *testing.T) {
ctx, _, hashes, instanceConfig := NewForTest(t)
g, err := New(ctx, instanceConfig)
require.NoError(t, err)
got, err := g.LogEntry(ctx, hashes[1])
require.NoError(t, err)
expected := `commit 881dfc43620250859549bb7e0301b6910d9b8e70
Author: test <test@google.com>
Date: Tue Mar 28 10:41:00 2023 +0000
501233450539197794
`
require.Equal(t, expected, got)
}
func TestLogEntry_BadCommitId_ReturnsError(t *testing.T) {
ctx, _, _, instanceConfig := NewForTest(t)
g, err := New(ctx, instanceConfig)
require.NoError(t, err)
_, err = g.LogEntry(ctx, "this-is-not-a-known-git-hash")
require.Error(t, err)
}
func TestUpdate_SuccessAndNewCommitAppears(t *testing.T) {
ctx, gb, _, instanceConfig := NewForTest(t)
g, err := New(ctx, instanceConfig)
require.NoError(t, err)
_, err = g.LogEntry(ctx, "this-is-not-a-known-git-hash")
require.Error(t, err)
newHash := gb.CommitGenAt(ctx, "foo.txt", StartTime.Add(4*time.Minute))
err = g.Update(ctx)
require.NoError(t, err)
_, err = g.LogEntry(ctx, newHash)
require.NoError(t, err)
}
func TestGitHashesInRangeForFile_FileIsChangedAtBeginHash_BeginHashIsExcludedFromResponse(t *testing.T) {
// The 'bar.txt' file is only changed commit 3 and 6.
ctx, _, hashes, instanceConfig := NewForTest(t)
g, err := New(ctx, instanceConfig)
require.NoError(t, err)
// GitHashesInRangeForFile is exclusive of 'begin', so it should not be in
// the results.
changedAt, err := g.GitHashesInRangeForFile(ctx, hashes[3], hashes[7], "bar.txt")
require.NoError(t, err)
require.Equal(t, []string{hashes[6]}, changedAt)
}
func TestGitHashesInRangeForFile_BeginHashIsEmpty_SearchGoesToBeginningOfRepoHistory(t *testing.T) {
// The 'bar.txt' file is only changed commit 3 and 6.
ctx, _, hashes, instanceConfig := NewForTest(t)
g, err := New(ctx, instanceConfig)
require.NoError(t, err)
changedAt, err := g.GitHashesInRangeForFile(ctx, "", hashes[7], "bar.txt")
require.NoError(t, err)
require.Equal(t, []string{hashes[3], hashes[6]}, changedAt)
}
func TestGitHashesInRangeForFile_BeginHashIsEmptyButStartCommitIsSet_SearchGoesToBeginningOfRepoHistory(t *testing.T) {
// The 'bar.txt' file is only changed commit 3 and 6.
ctx, _, hashes, instanceConfig := NewForTest(t)
// We change the StartCommit to 3, so we should only see the change at 6.
instanceConfig.GitRepoConfig.StartCommit = hashes[3]
g, err := New(ctx, instanceConfig)
require.NoError(t, err)
changedAt, err := g.GitHashesInRangeForFile(ctx, "", hashes[7], "bar.txt")
require.NoError(t, err)
require.Equal(t, []string{hashes[6]}, changedAt)
}
func TestCommitsFromMostRecentGitHashToHead_ProvideEmptyGitHash_ReceiveAllHashesInRepo(t *testing.T) {
ctx, _, hashes, instanceConfig := NewForTest(t)
g, err := New(ctx, instanceConfig)
require.NoError(t, err)
err = g.CommitsFromMostRecentGitHashToHead(ctx, "", func(c provider.Commit) error {
require.Equal(t, hashes[0], c.GitHash)
hashes = hashes[1:]
return nil
})
require.NoError(t, err)
}
func TestCommitsFromMostRecentGitHashToHead_ProvideEmptyGitHashButStartCommitIsSet_ReceiveAllHashesInRepoStartingFromStartCommit(t *testing.T) {
ctx, _, hashes, instanceConfig := NewForTest(t)
instanceConfig.GitRepoConfig.StartCommit = hashes[2]
g, err := New(ctx, instanceConfig)
require.NoError(t, err)
// StartCommit is set to 2, so we should get all commits after that.
expected := hashes[3:]
err = g.CommitsFromMostRecentGitHashToHead(ctx, "", func(c provider.Commit) error {
require.Equal(t, expected[0], c.GitHash)
expected = expected[1:]
return nil
})
require.NoError(t, err)
}
func TestCommitsFromMostRecentGitHashToHead_ProvideNonEmptyGitHash_ReceiveAllNewerHashesInRepo(t *testing.T) {
ctx, _, hashes, instanceConfig := NewForTest(t)
g, err := New(ctx, instanceConfig)
require.NoError(t, err)
// Note we use 3 here, because we pass hashes[2] below, so
// CommitsFromMostRecentGitHashToHead will return all commits newer than
// hashes[2] exclusive.
expected := hashes[3:]
err = g.CommitsFromMostRecentGitHashToHead(ctx, hashes[2], func(c provider.Commit) error {
require.Equal(t, expected[0], c.GitHash)
expected = expected[1:]
return nil
})
require.NoError(t, err)
}