Files
simplegit/gitcmd/fixture_test.go
T
2026-07-15 11:06:48 -04:00

280 lines
7.2 KiB
Go

package gitcmd
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
const (
aliceName = "Alice"
aliceEmail = "alice@example.com"
bobName = "Bob"
bobEmail = "bob@example.com"
readmeV1 = "line1\nline2\n"
readmeV2 = "line1\nline3\n"
mainTxt = "main line\n"
deletedTxt = "to be deleted\n"
)
var binaryContent = []byte{'B', 0x00, 'i', 'n', 0x00, '!', '*', '*'}
const (
d1 = "2024-01-01T10:00:00+00:00"
d2 = "2024-01-02T10:00:00+00:00"
d3 = "2024-01-03T10:00:00+00:00"
d4 = "2024-01-04T10:00:00+00:00"
d5 = "2024-01-05T10:00:00+00:00"
)
func mustParseTime(t *testing.T, s string) time.Time {
t.Helper()
tm, err := time.Parse(time.RFC3339, s)
if err != nil {
t.Fatalf("parse time %q: %v", s, err)
}
return tm
}
type fixture struct {
repo *Repository
c1 string
c2 string
c3 string
c4 string
c5 string
}
func requireGit(t *testing.T) string {
t.Helper()
p, err := exec.LookPath("git")
if err != nil {
t.Skipf("git not found on PATH: %v", err)
}
return p
}
var isolateEnv = []string{"GIT_CONFIG_NOSYSTEM=1", "GIT_CONFIG_GLOBAL=/dev/null"}
func gitRun(t *testing.T, dir string, env []string, args ...string) {
t.Helper()
cmd := exec.Command("git", args...)
if dir != "" {
cmd.Dir = dir
}
cmd.Env = append(os.Environ(), isolateEnv...)
cmd.Env = append(cmd.Env, env...)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v in %q: %v\n%s", args, dir, err, out)
}
}
func headSHA(t *testing.T, dir string) string {
t.Helper()
out, err := exec.Command("git", "-C", dir, "rev-parse", "HEAD").Output()
if err != nil {
t.Fatalf("rev-parse HEAD in %q: %v", dir, err)
}
return strings.TrimSpace(string(out))
}
func writeFile(t *testing.T, path, content string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}
func writeFileBytes(t *testing.T, path string, content []byte) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, content, 0o644); err != nil {
t.Fatal(err)
}
}
func commitEnv(name, email, dateRFC3339 string) []string {
return []string{
"GIT_AUTHOR_NAME=" + name,
"GIT_AUTHOR_EMAIL=" + email,
"GIT_AUTHOR_DATE=" + dateRFC3339,
"GIT_COMMITTER_NAME=" + name,
"GIT_COMMITTER_EMAIL=" + email,
"GIT_COMMITTER_DATE=" + dateRFC3339,
}
}
func commitAs(t *testing.T, dir, name, email, date, msg string) {
t.Helper()
gitRun(t, dir, nil, "add", "-A")
gitRun(t, dir, commitEnv(name, email, date), "commit", "-q", "-m", msg)
}
func newFixture(t *testing.T) *fixture {
t.Helper()
requireGit(t)
dir := t.TempDir()
work := filepath.Join(dir, "work")
bare := filepath.Join(dir, "bare.git")
gitRun(t, "", nil, "init", "-q", "-b", "main", work)
writeFile(t, filepath.Join(work, "README.md"), readmeV1)
commitAs(t, work, aliceName, aliceEmail, d1, "add readme")
c1 := headSHA(t, work)
writeFile(t, filepath.Join(work, "src", "main.txt"), mainTxt)
writeFile(t, filepath.Join(work, "deleted.txt"), deletedTxt)
commitAs(t, work, bobName, bobEmail, d2, "add main and temp")
c2 := headSHA(t, work)
writeFile(t, filepath.Join(work, "README.md"), readmeV2)
commitAs(t, work, aliceName, aliceEmail, d3, "expand readme")
c3 := headSHA(t, work)
gitRun(t, work, nil, "branch", "feature")
writeFileBytes(t, filepath.Join(work, "binary.bin"), binaryContent)
commitAs(t, work, aliceName, aliceEmail, d4, "add binary")
c4 := headSHA(t, work)
if err := os.Remove(filepath.Join(work, "deleted.txt")); err != nil {
t.Fatal(err)
}
commitAs(t, work, bobName, bobEmail, d5, "remove temp")
c5 := headSHA(t, work)
gitRun(t, work, commitEnv(aliceName, aliceEmail, d1), "tag", "-a", "v1.0", c1, "-m", "release v1.0")
gitRun(t, work, nil, "tag", "v0.9", c2)
gitRun(t, "", nil, "clone", "--bare", "-q", work, bare)
repo, err := OpenRepository(context.Background(), bare)
if err != nil {
t.Fatalf("OpenRepository: %v", err)
}
return &fixture{repo: repo, c1: c1, c2: c2, c3: c3, c4: c4, c5: c5}
}
func newMergeFixture(t *testing.T) *fixture {
t.Helper()
requireGit(t)
dir := t.TempDir()
work := filepath.Join(dir, "work")
bare := filepath.Join(dir, "bare.git")
gitRun(t, "", nil, "init", "-q", "-b", "main", work)
writeFile(t, filepath.Join(work, "a.txt"), "a\n")
commitAs(t, work, "Merger", "m@x", "2024-02-01T10:00:00+00:00", "c1 a")
c1 := headSHA(t, work)
gitRun(t, work, nil, "branch", "feature")
gitRun(t, work, nil, "checkout", "-q", "feature")
writeFile(t, filepath.Join(work, "b.txt"), "b\n")
commitAs(t, work, "Merger", "m@x", "2024-02-02T10:00:00+00:00", "c2 b")
c2 := headSHA(t, work)
gitRun(t, work, nil, "checkout", "-q", "main")
writeFile(t, filepath.Join(work, "c.txt"), "c\n")
commitAs(t, work, "Merger", "m@x", "2024-02-03T10:00:00+00:00", "c3 c")
c3 := headSHA(t, work)
gitRun(t, work, commitEnv("Merger", "m@x", "2024-02-04T10:00:00+00:00"),
"merge", "--no-ff", "feature", "-m", "merge feature")
c4 := headSHA(t, work)
gitRun(t, "", nil, "clone", "--bare", "-q", work, bare)
repo, err := OpenRepository(context.Background(), bare)
if err != nil {
t.Fatalf("OpenRepository: %v", err)
}
return &fixture{repo: repo, c1: c1, c2: c2, c3: c3, c4: c4}
}
func newDetachedHeadRepo(t *testing.T) *Repository {
t.Helper()
requireGit(t)
dir := t.TempDir()
bare := filepath.Join(dir, "detached.git")
gitRun(t, "", nil, "init", "-q", "--bare", bare)
if err := os.WriteFile(filepath.Join(bare, "HEAD"),
[]byte("0123456789abcdef0123456789abcdef01234567\n"), 0o644); err != nil {
t.Fatal(err)
}
repo, err := OpenRepository(context.Background(), bare)
if err != nil {
t.Fatalf("OpenRepository: %v", err)
}
return repo
}
func TestFixtureSmoke(t *testing.T) {
f := newFixture(t)
cs, err := f.repo.ListCommits("HEAD", 0)
if err != nil {
t.Fatal(err)
}
if len(cs) != 5 {
t.Fatalf("commits = %d, want 5", len(cs))
}
if cs[0].ID != f.c5 || cs[4].ID != f.c1 {
t.Errorf("commit order: first=%s (want %s), last=%s (want %s)",
cs[0].ID, f.c5, cs[4].ID, f.c1)
}
bs, err := f.repo.ListBranches()
if err != nil {
t.Fatal(err)
}
if len(bs) != 2 {
t.Fatalf("branches = %d, want 2", len(bs))
}
branchByName := map[string]Branch{}
for _, b := range bs {
branchByName[b.Name] = b
}
if main, ok := branchByName["main"]; !ok || !main.IsDefault || main.SHA != f.c5 {
t.Errorf("main branch = %+v", branchByName["main"])
}
if feat, ok := branchByName["feature"]; !ok || feat.SHA != f.c3 {
t.Errorf("feature branch = %+v", branchByName["feature"])
}
ts, err := f.repo.ListTags()
if err != nil {
t.Fatal(err)
}
if len(ts) != 2 {
t.Fatalf("tags = %d, want 2", len(ts))
}
contribs, err := f.repo.GetContributors("HEAD")
if err != nil {
t.Fatal(err)
}
if len(contribs) != 2 {
t.Fatalf("contributors = %d, want 2", len(contribs))
}
if contribs[0].Author != aliceName || contribs[0].Commits != 3 {
t.Errorf("contribs[0] = %+v, want Alice/3", contribs[0])
}
if contribs[1].Author != bobName || contribs[1].Commits != 2 {
t.Errorf("contribs[1] = %+v, want Bob/2", contribs[1])
}
if db, err := f.repo.DefaultBranch(); err != nil || db != "main" {
t.Errorf("DefaultBranch = %q, %v", db, err)
}
}