test(commits): add unit tests for commits and cache packages

Add tests for CommitSet filtering (by year, author regex),
ToYearFreq, channel-based frequency functions, cache round-trip
operations, and hash determinism. Also includes goimports formatting
fix in common_test.go.
This commit is contained in:
2026-04-08 09:41:58 +00:00
parent ef0804254d
commit 70c16e9774
3 changed files with 315 additions and 4 deletions

99
commits/cache_test.go Normal file
View File

@@ -0,0 +1,99 @@
package commits
import (
"testing"
"time"
"github.com/taigrr/gico/types"
)
func TestHashSliceDeterministic(t *testing.T) {
a := hashSlice([]string{"foo", "bar", "baz"})
b := hashSlice([]string{"baz", "foo", "bar"})
if a != b {
t.Error("hashSlice should be order-independent")
}
}
func TestHashSliceDifferentInputs(t *testing.T) {
a := hashSlice([]string{"foo", "bar"})
b := hashSlice([]string{"foo", "baz"})
if a == b {
t.Error("different inputs should produce different hashes")
}
}
func TestCacheRepoRoundTrip(t *testing.T) {
path := "/test/cache-repo"
head := "deadbeef123"
commits := []types.Commit{
{Hash: head, Author: types.Author{Name: "test"}, TimeStamp: time.Now()},
}
// Should miss before caching
_, ok := GetCachedRepo(path, head)
if ok {
t.Error("expected cache miss before storing")
}
CacheRepo(path, commits)
// Should hit after caching
cached, ok := GetCachedRepo(path, head)
if !ok {
t.Error("expected cache hit after storing")
}
if len(cached) != 1 || cached[0].Hash != head {
t.Error("cached data doesn't match stored data")
}
// Different head should miss
_, ok = GetCachedRepo(path, "differenthead")
if ok {
t.Error("expected cache miss for different head")
}
}
func TestCacheGraphRoundTrip(t *testing.T) {
year := 2025
authors := []string{"alice@example.com"}
paths := []string{"/repo/one", "/repo/two"}
freq := types.Freq{1, 2, 3, 0, 0}
// Should miss before caching
_, ok := GetCachedGraph(year, authors, paths)
if ok {
t.Error("expected cache miss before storing")
}
CacheGraph(year, authors, paths, freq)
// Should hit
cached, ok := GetCachedGraph(year, authors, paths)
if !ok {
t.Error("expected cache hit")
}
if len(cached) != len(freq) {
t.Errorf("expected freq length %d, got %d", len(freq), len(cached))
}
}
func TestCacheReposAuthorsRoundTrip(t *testing.T) {
paths := []string{"/repo/author-test"}
authors := []string{"alice", "bob"}
_, ok := GetCachedReposAuthors(paths)
if ok {
t.Error("expected cache miss")
}
CacheReposAuthors(paths, authors)
cached, ok := GetCachedReposAuthors(paths)
if !ok {
t.Error("expected cache hit")
}
if len(cached) != 2 {
t.Errorf("expected 2 authors, got %d", len(cached))
}
}

212
commits/commits_test.go Normal file
View File

@@ -0,0 +1,212 @@
package commits
import (
"testing"
"time"
"github.com/taigrr/gico/types"
)
func makeCommit(name, email string, ts time.Time) types.Commit {
return types.Commit{
Author: types.Author{Name: name, Email: email},
TimeStamp: ts,
Hash: "abc123",
Repo: "/test/repo",
}
}
func TestCommitSetFilterByYear(t *testing.T) {
cs := CommitSet{
Commits: []types.Commit{
makeCommit("a", "a@x.com", time.Date(2024, 3, 15, 10, 0, 0, 0, time.UTC)),
makeCommit("b", "b@x.com", time.Date(2025, 6, 1, 12, 0, 0, 0, time.UTC)),
makeCommit("c", "c@x.com", time.Date(2024, 12, 31, 23, 59, 0, 0, time.UTC)),
makeCommit("d", "d@x.com", time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)),
},
}
filtered := cs.FilterByYear(2024)
if filtered.Year != 2024 {
t.Errorf("expected Year=2024, got %d", filtered.Year)
}
if len(filtered.Commits) != 2 {
t.Errorf("expected 2 commits for 2024, got %d", len(filtered.Commits))
}
filtered = cs.FilterByYear(2025)
if len(filtered.Commits) != 1 {
t.Errorf("expected 1 commit for 2025, got %d", len(filtered.Commits))
}
filtered = cs.FilterByYear(2000)
if len(filtered.Commits) != 0 {
t.Errorf("expected 0 commits for 2000, got %d", len(filtered.Commits))
}
}
func TestCommitSetFilterByAuthorRegex(t *testing.T) {
cs := CommitSet{
Commits: []types.Commit{
makeCommit("Alice", "alice@example.com", time.Now()),
makeCommit("Bob", "bob@example.com", time.Now()),
makeCommit("Charlie", "charlie@example.com", time.Now()),
},
}
// Filter by name regex
filtered, err := cs.FilterByAuthorRegex([]string{"^Ali"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(filtered.Commits) != 1 {
t.Errorf("expected 1 commit matching ^Ali, got %d", len(filtered.Commits))
}
// Filter by email regex
filtered, err = cs.FilterByAuthorRegex([]string{"bob@"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(filtered.Commits) != 1 {
t.Errorf("expected 1 commit matching bob@, got %d", len(filtered.Commits))
}
// Multiple patterns
filtered, err = cs.FilterByAuthorRegex([]string{"Alice", "Charlie"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(filtered.Commits) != 2 {
t.Errorf("expected 2 commits matching Alice|Charlie, got %d", len(filtered.Commits))
}
// Invalid regex
_, err = cs.FilterByAuthorRegex([]string{"[invalid"})
if err == nil {
t.Error("expected error for invalid regex, got nil")
}
}
func TestCommitSetToYearFreq(t *testing.T) {
// Regular year (2025)
cs := CommitSet{
Year: 2025,
Commits: []types.Commit{
makeCommit("a", "a@x.com", time.Date(2025, 1, 1, 10, 0, 0, 0, time.UTC)),
makeCommit("a", "a@x.com", time.Date(2025, 1, 1, 14, 0, 0, 0, time.UTC)),
makeCommit("b", "b@x.com", time.Date(2025, 3, 15, 12, 0, 0, 0, time.UTC)),
},
}
freq := cs.ToYearFreq()
if len(freq) != 365 {
t.Errorf("expected 365 days for 2025, got %d", len(freq))
}
if freq[0] != 2 {
t.Errorf("expected 2 commits on Jan 1, got %d", freq[0])
}
// March 15 = day 74 (31+28+15=74, index 73)
if freq[73] != 1 {
t.Errorf("expected 1 commit on Mar 15, got %d", freq[73])
}
// Leap year (2024)
csLeap := CommitSet{
Year: 2024,
Commits: []types.Commit{
makeCommit("a", "a@x.com", time.Date(2024, 12, 31, 10, 0, 0, 0, time.UTC)),
},
}
freqLeap := csLeap.ToYearFreq()
if len(freqLeap) != 366 {
t.Errorf("expected 366 days for 2024, got %d", len(freqLeap))
}
}
func TestYearFreqFromChan(t *testing.T) {
cc := make(chan types.Commit, 5)
cc <- makeCommit("a", "a@x.com", time.Date(2025, 1, 1, 10, 0, 0, 0, time.UTC))
cc <- makeCommit("a", "a@x.com", time.Date(2025, 1, 1, 14, 0, 0, 0, time.UTC))
cc <- makeCommit("b", "b@x.com", time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC))
close(cc)
freq := YearFreqFromChan(cc, 2025)
if len(freq) != 365 {
t.Errorf("expected 365, got %d", len(freq))
}
if freq[0] != 2 {
t.Errorf("expected 2 on Jan 1, got %d", freq[0])
}
}
func TestFreqFromChan(t *testing.T) {
cc := make(chan types.Commit, 5)
cc <- makeCommit("a", "a@x.com", time.Date(2025, 1, 1, 10, 0, 0, 0, time.UTC))
cc <- makeCommit("b", "b@x.com", time.Date(2024, 6, 15, 12, 0, 0, 0, time.UTC)) // wrong year
cc <- makeCommit("c", "c@x.com", time.Date(2025, 12, 31, 23, 0, 0, 0, time.UTC))
close(cc)
freq := FreqFromChan(cc, 2025)
if len(freq) != 365 {
t.Errorf("expected 365, got %d", len(freq))
}
if freq[0] != 1 {
t.Errorf("expected 1 on Jan 1 (skip wrong year), got %d", freq[0])
}
if freq[364] != 1 {
t.Errorf("expected 1 on Dec 31, got %d", freq[364])
}
}
func TestFilterCChanByYear(t *testing.T) {
in := make(chan types.Commit, 5)
in <- makeCommit("a", "a@x.com", time.Date(2025, 3, 1, 10, 0, 0, 0, time.UTC))
in <- makeCommit("b", "b@x.com", time.Date(2024, 3, 1, 10, 0, 0, 0, time.UTC))
in <- makeCommit("c", "c@x.com", time.Date(2025, 7, 4, 10, 0, 0, 0, time.UTC))
close(in)
out := FilterCChanByYear(in, 2025)
count := 0
for range out {
count++
}
if count != 2 {
t.Errorf("expected 2 commits for 2025, got %d", count)
}
}
func TestFilterCChanByAuthor(t *testing.T) {
in := make(chan types.Commit, 5)
in <- makeCommit("Alice", "alice@x.com", time.Now())
in <- makeCommit("Bob", "bob@x.com", time.Now())
in <- makeCommit("Charlie", "charlie@x.com", time.Now())
close(in)
out, err := FilterCChanByAuthor(in, []string{"Alice", "Charlie"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
count := 0
for range out {
count++
}
if count != 2 {
t.Errorf("expected 2 commits, got %d", count)
}
}
func TestOpenRepoNonExistent(t *testing.T) {
_, err := OpenRepo("/nonexistent/path")
if err == nil {
t.Error("expected error for nonexistent path")
}
}
func TestOpenRepoNonDirectory(t *testing.T) {
// Use a known file (not a directory)
_, err := OpenRepo("/dev/null")
if err == nil {
t.Error("expected error for non-directory path")
}
}

View File

@@ -8,10 +8,10 @@ import (
func TestMinMax(t *testing.T) {
tests := []struct {
name string
input []int
wantMin int
wantMax int
name string
input []int
wantMin int
wantMax int
}{
{
name: "normal values",