blob: 97c30ed53a2875997f1d8e58ab5670e9cd132b45 [file] [log] [blame]
package checkout
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
"go.skia.org/infra/go/common"
"go.skia.org/infra/go/exec"
"go.skia.org/infra/go/git"
git_testutils "go.skia.org/infra/go/git/testutils"
"go.skia.org/infra/go/testutils"
"go.skia.org/infra/go/testutils/unittest"
"go.skia.org/infra/task_driver/go/td"
"go.skia.org/infra/task_scheduler/go/types"
)
func TestEnsureGitCheckout(t *testing.T) {
unittest.LargeTest(t)
// Setup.
ctx := context.Background()
f := "myfile.txt"
gb := git_testutils.GitInit(t, ctx)
c1 := gb.CommitGen(ctx, f)
rs := types.RepoState{
Repo: gb.RepoUrl(),
Revision: c1,
}
// check is a helper function which creates a temporary dir, runs the
// passed-in function so that the caller can manipulate the dir, then
// runs EnsureGitCheckout and verifies that it did the correct thing.
check := func(rs types.RepoState, fn func(string)) {
require.True(t, rs.Valid())
// Create temp dir.
wd, cleanup := testutils.TempDir(t)
defer cleanup()
dest := filepath.Join(wd, "repo")
// Let the caller manipulate the dir.
fn(dest)
// Run EnsureGitCheckout, asserting that it returns no error.
var co *git.Checkout
_ = td.RunTestSteps(t, false, func(ctx context.Context) error {
var err error
co, err = EnsureGitCheckout(ctx, dest, rs)
require.NoError(t, err)
return nil
})
// Verify that the checkout is in the correct state.
// Assert that we're at the correct revision.
if rs.IsTryJob() {
// For try jobs, we end up with a different HEAD, so we
// can only check that the patch ref was applied on top
// of the requested revision.
ancestor, err := co.IsAncestor(ctx, rs.Revision, "HEAD")
require.NoError(t, err)
require.True(t, ancestor)
// This is only true for our test case, but assert that
// exactly one commit was applied on top of rs.Revision.
revs, err := co.RevList(ctx, fmt.Sprintf("%s..HEAD", rs.Revision))
require.NoError(t, err)
require.Equal(t, 1, len(revs))
} else {
gotRev, err := co.RevParse(ctx, "HEAD")
require.NoError(t, err)
require.Equal(t, rs.Revision, gotRev)
}
// Assert that there are no modified or untracked files.
output, err := co.Git(ctx, "status", "--porcelain")
require.NoError(t, err)
require.Equal(t, output, "")
// Assert that we're on the main branch, regardless of what
// commit we wanted.
output, err = co.Git(ctx, "rev-parse", "--abbrev-ref", "HEAD")
require.NoError(t, err)
require.Equal(t, git.DefaultBranch, strings.TrimSpace(output))
}
// Case 1: Dest dir does not exist.
check(rs, func(dest string) {})
// Case 2: Dest dir exists and is empty. We should remove the dir before
// syncing in this case.
check(rs, func(dest string) {
require.NoError(t, os.MkdirAll(dest, os.ModePerm))
})
// Case 3: Dest dir exists and has a .git dir in it. It is NOT a valid
// git checkout, however.
check(rs, func(dest string) {
require.NoError(t, os.MkdirAll(filepath.Join(dest, ".git"), os.ModePerm))
})
// Case 4: Dest dir is a git checkout with no remote.
check(rs, func(dest string) {
require.NoError(t, os.MkdirAll(dest, os.ModePerm))
_, err := git.GitDir(dest).Git(ctx, "init")
require.NoError(t, err)
})
// Case 5: Dest dir is a git checkout with the wrong remote.
check(rs, func(dest string) {
require.NoError(t, os.MkdirAll(dest, os.ModePerm))
g := git.GitDir(dest)
_, err := g.Git(ctx, "init")
require.NoError(t, err)
_, err = g.Git(ctx, "remote", "add", git.DefaultRemote, common.REPO_SKIA)
require.NoError(t, err)
})
// Case 6: Dest dir is a git checkout with the right remote, but it has
// never fetched. In this case, there is no HEAD.
check(rs, func(dest string) {
require.NoError(t, os.MkdirAll(dest, os.ModePerm))
g := git.GitDir(dest)
_, err := g.Git(ctx, "init")
require.NoError(t, err)
_, err = g.Git(ctx, "remote", "add", git.DefaultRemote, rs.Repo)
require.NoError(t, err)
})
// Case 7: Checkout already exists and is clean. We should not remove
// the dir in this case.
gitExec, err := git.Executable(ctx)
require.NoError(t, err)
check(rs, func(dest string) {
_, err := exec.RunCwd(ctx, ".", gitExec, "clone", rs.Repo, dest)
require.NoError(t, err)
})
// Case 8: Checkout already exists and has modified / untracked files.
check(rs, func(dest string) {
_, err := exec.RunCwd(ctx, ".", gitExec, "clone", rs.Repo, dest)
require.NoError(t, err)
testutils.WriteFile(t, filepath.Join(dest, f), "modified file")
testutils.WriteFile(t, filepath.Join(dest, "untracked_file"), "this file is untracked")
})
// Case 9: Checkout already exists and has new commits.
check(rs, func(dest string) {
_, err := exec.RunCwd(ctx, ".", gitExec, "clone", rs.Repo, dest)
require.NoError(t, err)
updated := filepath.Join(dest, f)
testutils.WriteFile(t, updated, "modified file")
_, err = git.GitDir(dest).Git(ctx, "commit", "-a", "-m", "modified a file")
require.NoError(t, err)
})
// Case 10: Checkout already exists and is on a different branch.
check(rs, func(dest string) {
_, err := exec.RunCwd(ctx, ".", gitExec, "clone", rs.Repo, dest)
require.NoError(t, err)
gd := git.GitDir(dest)
_, err = gd.Git(ctx, "checkout", "-b", "newbranch", "-t", git.DefaultBranch)
require.NoError(t, err)
updated := filepath.Join(dest, f)
testutils.WriteFile(t, updated, "modified file")
_, err = gd.Git(ctx, "commit", "-a", "-m", "modified a file")
require.NoError(t, err)
})
// Case 11: Apply a patch.
rs.Issue = "11011"
rs.Patchset = "2"
rs.Server = "https://fake-review.googlesource.com"
gb.CreateFakeGerritCLGen(ctx, rs.Issue, rs.Patchset)
check(rs, func(dest string) {})
}