diff options
| author | 2026-02-20 21:19:47 +0800 | |
|---|---|---|
| committer | 2026-02-20 21:34:42 +0800 | |
| commit | 05e07f6c6aca1662c33359f41c66e6f9b6eb935a (patch) | |
| tree | 6928a76783b2230a010c36d53b8da48ff24d611f /internal | |
| parent | oid: Add ObjectID (diff) | |
| signature | No signature | |
testgit: Add test harnesses
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/testgit/algorithms.go | 25 | ||||
| -rw-r--r-- | internal/testgit/repo.go | 10 | ||||
| -rw-r--r-- | internal/testgit/repo_cat_file.go | 13 | ||||
| -rw-r--r-- | internal/testgit/repo_commit_tree.go | 23 | ||||
| -rw-r--r-- | internal/testgit/repo_hash_object.go | 18 | ||||
| -rw-r--r-- | internal/testgit/repo_make_commit.go | 15 | ||||
| -rw-r--r-- | internal/testgit/repo_make_single_file_tree.go | 17 | ||||
| -rw-r--r-- | internal/testgit/repo_mktree.go | 18 | ||||
| -rw-r--r-- | internal/testgit/repo_new.go | 56 | ||||
| -rw-r--r-- | internal/testgit/repo_properties.go | 13 | ||||
| -rw-r--r-- | internal/testgit/repo_rev_parse.go | 18 | ||||
| -rw-r--r-- | internal/testgit/repo_run.go | 49 | ||||
| -rw-r--r-- | internal/testgit/repo_tag_annotated.go | 15 |
13 files changed, 290 insertions, 0 deletions
diff --git a/internal/testgit/algorithms.go b/internal/testgit/algorithms.go new file mode 100644 index 00000000..833b846b --- /dev/null +++ b/internal/testgit/algorithms.go @@ -0,0 +1,25 @@ +package testgit + +import ( + "testing" + + "codeberg.org/lindenii/furgit/oid" +) + +// SupportedAlgorithms returns all object ID algorithms supported by furgit. +func SupportedAlgorithms() []oid.Algorithm { + return []oid.Algorithm{ + oid.AlgorithmSHA1, + oid.AlgorithmSHA256, + } +} + +// ForEachAlgorithm runs a subtest for every supported algorithm. +func ForEachAlgorithm(t *testing.T, fn func(t *testing.T, algo oid.Algorithm)) { + t.Helper() + for _, algo := range SupportedAlgorithms() { + t.Run(algo.String(), func(t *testing.T) { + fn(t, algo) + }) + } +} diff --git a/internal/testgit/repo.go b/internal/testgit/repo.go new file mode 100644 index 00000000..c2d8b088 --- /dev/null +++ b/internal/testgit/repo.go @@ -0,0 +1,10 @@ +package testgit + +import "codeberg.org/lindenii/furgit/oid" + +// TestRepo is a temporary git repository harness for integration tests. +type TestRepo struct { + dir string + algo oid.Algorithm + env []string +} diff --git a/internal/testgit/repo_cat_file.go b/internal/testgit/repo_cat_file.go new file mode 100644 index 00000000..7533e484 --- /dev/null +++ b/internal/testgit/repo_cat_file.go @@ -0,0 +1,13 @@ +package testgit + +import ( + "testing" + + "codeberg.org/lindenii/furgit/oid" +) + +// CatFile returns raw output from git cat-file. +func (repo *TestRepo) CatFile(tb testing.TB, mode string, id oid.ObjectID) []byte { + tb.Helper() + return repo.RunBytes(tb, "cat-file", mode, id.String()) +} diff --git a/internal/testgit/repo_commit_tree.go b/internal/testgit/repo_commit_tree.go new file mode 100644 index 00000000..b5f2f587 --- /dev/null +++ b/internal/testgit/repo_commit_tree.go @@ -0,0 +1,23 @@ +package testgit + +import ( + "testing" + + "codeberg.org/lindenii/furgit/oid" +) + +// CommitTree creates a commit from a tree and message, optionally with parents. +func (repo *TestRepo) CommitTree(tb testing.TB, tree oid.ObjectID, message string, parents ...oid.ObjectID) oid.ObjectID { + tb.Helper() + args := []string{"commit-tree", tree.String()} + for _, p := range parents { + args = append(args, "-p", p.String()) + } + args = append(args, "-m", message) + hex := repo.Run(tb, args...) + id, err := oid.ParseHex(repo.algo, hex) + if err != nil { + tb.Fatalf("parse commit-tree output %q: %v", hex, err) + } + return id +} diff --git a/internal/testgit/repo_hash_object.go b/internal/testgit/repo_hash_object.go new file mode 100644 index 00000000..97a86be5 --- /dev/null +++ b/internal/testgit/repo_hash_object.go @@ -0,0 +1,18 @@ +package testgit + +import ( + "testing" + + "codeberg.org/lindenii/furgit/oid" +) + +// HashObject hashes and writes an object and returns its object ID. +func (repo *TestRepo) HashObject(tb testing.TB, objType string, body []byte) oid.ObjectID { + tb.Helper() + hex := repo.RunInput(tb, body, "hash-object", "-t", objType, "-w", "--stdin") + id, err := oid.ParseHex(repo.algo, hex) + if err != nil { + tb.Fatalf("parse git hash-object output %q: %v", hex, err) + } + return id +} diff --git a/internal/testgit/repo_make_commit.go b/internal/testgit/repo_make_commit.go new file mode 100644 index 00000000..a329ee8a --- /dev/null +++ b/internal/testgit/repo_make_commit.go @@ -0,0 +1,15 @@ +package testgit + +import ( + "testing" + + "codeberg.org/lindenii/furgit/oid" +) + +// MakeCommit creates a commit over a single-file tree and returns (blobID, treeID, commitID). +func (repo *TestRepo) MakeCommit(tb testing.TB, message string) (oid.ObjectID, oid.ObjectID, oid.ObjectID) { + tb.Helper() + blobID, treeID := repo.MakeSingleFileTree(tb, "file.txt", []byte("commit-body\n")) + commitID := repo.CommitTree(tb, treeID, message) + return blobID, treeID, commitID +} diff --git a/internal/testgit/repo_make_single_file_tree.go b/internal/testgit/repo_make_single_file_tree.go new file mode 100644 index 00000000..69b2362e --- /dev/null +++ b/internal/testgit/repo_make_single_file_tree.go @@ -0,0 +1,17 @@ +package testgit + +import ( + "fmt" + "testing" + + "codeberg.org/lindenii/furgit/oid" +) + +// MakeSingleFileTree writes one blob and one tree entry for it and returns (blobID, treeID). +func (repo *TestRepo) MakeSingleFileTree(tb testing.TB, fileName string, fileContent []byte) (oid.ObjectID, oid.ObjectID) { + tb.Helper() + blobID := repo.HashObject(tb, "blob", fileContent) + treeInput := fmt.Sprintf("100644 blob %s\t%s\n", blobID.String(), fileName) + treeID := repo.Mktree(tb, treeInput) + return blobID, treeID +} diff --git a/internal/testgit/repo_mktree.go b/internal/testgit/repo_mktree.go new file mode 100644 index 00000000..bd2785e2 --- /dev/null +++ b/internal/testgit/repo_mktree.go @@ -0,0 +1,18 @@ +package testgit + +import ( + "testing" + + "codeberg.org/lindenii/furgit/oid" +) + +// Mktree creates a tree from textual mktree input and returns its ID. +func (repo *TestRepo) Mktree(tb testing.TB, input string) oid.ObjectID { + tb.Helper() + hex := repo.RunInput(tb, []byte(input), "mktree") + id, err := oid.ParseHex(repo.algo, hex) + if err != nil { + tb.Fatalf("parse mktree output %q: %v", hex, err) + } + return id +} diff --git a/internal/testgit/repo_new.go b/internal/testgit/repo_new.go new file mode 100644 index 00000000..51277f78 --- /dev/null +++ b/internal/testgit/repo_new.go @@ -0,0 +1,56 @@ +package testgit + +import ( + "os" + "testing" + + "codeberg.org/lindenii/furgit/oid" +) + +// NewBareRepo creates a temporary bare repository initialized with the requested algorithm. +func NewBareRepo(tb testing.TB, algo oid.Algorithm) *TestRepo { + tb.Helper() + return newRepo(tb, algo, true) +} + +// NewWorkRepo creates a temporary non-bare repository initialized with the requested algorithm. +func NewWorkRepo(tb testing.TB, algo oid.Algorithm) *TestRepo { + tb.Helper() + return newRepo(tb, algo, false) +} + +func newRepo(tb testing.TB, algo oid.Algorithm, bare bool) *TestRepo { + tb.Helper() + if algo.Size() == 0 { + tb.Fatalf("invalid algorithm: %v", algo) + } + + dir, err := os.MkdirTemp("", "furgit-testgit-*") + if err != nil { + tb.Fatalf("create temp dir: %v", err) + } + tb.Cleanup(func() { _ = os.RemoveAll(dir) }) + + repo := &TestRepo{ + dir: dir, + algo: algo, + env: append(os.Environ(), + "GIT_CONFIG_GLOBAL=/dev/null", + "GIT_CONFIG_SYSTEM=/dev/null", + "GIT_AUTHOR_NAME=Test Author", + "GIT_AUTHOR_EMAIL=test@example.org", + "GIT_COMMITTER_NAME=Test Committer", + "GIT_COMMITTER_EMAIL=committer@example.org", + "GIT_AUTHOR_DATE=1234567890 +0000", + "GIT_COMMITTER_DATE=1234567890 +0000", + ), + } + + args := []string{"init", "--object-format=" + algo.String()} + if bare { + args = append(args, "--bare") + } + args = append(args, dir) + repo.runBytes(tb, nil, "", args...) + return repo +} diff --git a/internal/testgit/repo_properties.go b/internal/testgit/repo_properties.go new file mode 100644 index 00000000..9a48a43b --- /dev/null +++ b/internal/testgit/repo_properties.go @@ -0,0 +1,13 @@ +package testgit + +import "codeberg.org/lindenii/furgit/oid" + +// Dir returns the repository directory path. +func (repo *TestRepo) Dir() string { + return repo.dir +} + +// Algorithm returns the object ID algorithm configured for this repository. +func (repo *TestRepo) Algorithm() oid.Algorithm { + return repo.algo +} diff --git a/internal/testgit/repo_rev_parse.go b/internal/testgit/repo_rev_parse.go new file mode 100644 index 00000000..7c3365a1 --- /dev/null +++ b/internal/testgit/repo_rev_parse.go @@ -0,0 +1,18 @@ +package testgit + +import ( + "testing" + + "codeberg.org/lindenii/furgit/oid" +) + +// RevParse resolves rev expressions to object IDs. +func (repo *TestRepo) RevParse(tb testing.TB, spec string) oid.ObjectID { + tb.Helper() + hex := repo.Run(tb, "rev-parse", spec) + id, err := oid.ParseHex(repo.algo, hex) + if err != nil { + tb.Fatalf("parse rev-parse output %q: %v", hex, err) + } + return id +} diff --git a/internal/testgit/repo_run.go b/internal/testgit/repo_run.go new file mode 100644 index 00000000..66222569 --- /dev/null +++ b/internal/testgit/repo_run.go @@ -0,0 +1,49 @@ +package testgit + +import ( + "bytes" + "os/exec" + "strings" + "testing" +) + +// Run executes git and returns trimmed textual output. +func (repo *TestRepo) Run(tb testing.TB, args ...string) string { + tb.Helper() + out := repo.runBytes(tb, nil, repo.dir, args...) + return strings.TrimSpace(string(out)) +} + +// RunBytes executes git and returns raw output bytes. +func (repo *TestRepo) RunBytes(tb testing.TB, args ...string) []byte { + tb.Helper() + return repo.runBytes(tb, nil, repo.dir, args...) +} + +// RunInput executes git with stdin and returns trimmed textual output. +func (repo *TestRepo) RunInput(tb testing.TB, stdin []byte, args ...string) string { + tb.Helper() + out := repo.runBytes(tb, stdin, repo.dir, args...) + return strings.TrimSpace(string(out)) +} + +// RunInputBytes executes git with stdin and returns raw output bytes. +func (repo *TestRepo) RunInputBytes(tb testing.TB, stdin []byte, args ...string) []byte { + tb.Helper() + return repo.runBytes(tb, stdin, repo.dir, args...) +} + +func (repo *TestRepo) runBytes(tb testing.TB, stdin []byte, dir string, args ...string) []byte { + tb.Helper() + cmd := exec.Command("git", args...) + cmd.Dir = dir + cmd.Env = repo.env + if stdin != nil { + cmd.Stdin = bytes.NewReader(stdin) + } + out, err := cmd.CombinedOutput() + if err != nil { + tb.Fatalf("git %v failed: %v\n%s", args, err, out) + } + return out +} diff --git a/internal/testgit/repo_tag_annotated.go b/internal/testgit/repo_tag_annotated.go new file mode 100644 index 00000000..167aaa96 --- /dev/null +++ b/internal/testgit/repo_tag_annotated.go @@ -0,0 +1,15 @@ +package testgit + +import ( + "fmt" + "testing" + + "codeberg.org/lindenii/furgit/oid" +) + +// TagAnnotated creates an annotated tag object and returns the resulting tag object ID. +func (repo *TestRepo) TagAnnotated(tb testing.TB, name string, target oid.ObjectID, message string) oid.ObjectID { + tb.Helper() + repo.Run(tb, "tag", "-a", name, target.String(), "-m", message) + return repo.RevParse(tb, fmt.Sprintf("refs/tags/%s", name)) +} |
