processor/history_test.go GO 218 lines View on github.com → Search inside
1// SPDX-License-Identifier: MIT23package processor45import (6	"os"7	"path/filepath"8	"testing"9	"time"1011	"github.com/go-git/go-git/v5"12	"github.com/go-git/go-git/v5/plumbing/object"13)1415// makeFixtureRepo initialises a real on-disk repo in a temp dir and lets the16// caller commit a sequence of (path -> content) snapshots. Returns the repo17// path so tests can pass it to runHistory.18//19// We use PlainInit (not an in-memory storer) because scc's classifier needs20// to detect languages from file names — same as a normal scc run — and the21// engine itself only talks to go-git, so no shell-out happens.22func makeFixtureRepo(t *testing.T, commits []map[string]string) string {23	t.Helper()24	ProcessConstants()25	dir := t.TempDir()2627	repo, err := git.PlainInit(dir, false)28	if err != nil {29		t.Fatalf("init repo: %v", err)30	}31	wt, err := repo.Worktree()32	if err != nil {33		t.Fatalf("worktree: %v", err)34	}3536	when := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC)37	for i, snap := range commits {38		for path, content := range snap {39			full := filepath.Join(dir, path)40			if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {41				t.Fatalf("mkdir %s: %v", full, err)42			}43			if err := os.WriteFile(full, []byte(content), 0o644); err != nil {44				t.Fatalf("write %s: %v", full, err)45			}46			if _, err := wt.Add(path); err != nil {47				t.Fatalf("add %s: %v", path, err)48			}49		}50		_, err := wt.Commit("commit "+itoa(i), &git.CommitOptions{51			Author: &object.Signature{52				Name:  "Author " + itoa(i%2),53				Email: "author" + itoa(i%2) + "@example.com",54				When:  when.Add(time.Duration(i) * time.Hour),55			},56		})57		if err != nil {58			t.Fatalf("commit %d: %v", i, err)59		}60	}61	return dir62}6364// captureObserver records observe calls so tests can assert on them.65type captureObserver struct {66	commits  []CommitInfo67	changes  [][]FileChange68	window   HistoryWindow69	snapshot HeadSnapshot70}7172func (c *captureObserver) Observe(info CommitInfo, changes []FileChange) {73	c.commits = append(c.commits, info)74	c.changes = append(c.changes, changes)75}7677func (c *captureObserver) Finalise(w HistoryWindow, s HeadSnapshot) {78	c.window = w79	c.snapshot = s80}8182func TestRunHistoryWalksOldestFirstAndCollectsChanges(t *testing.T) {83	// Set depth/flags to defaults this test cares about.84	saveDepth := HistoryDepth85	HistoryDepth = 10086	t.Cleanup(func() { HistoryDepth = saveDepth })8788	dir := makeFixtureRepo(t, []map[string]string{89		{"a.go": "package a\n\nfunc A() {}\n"},90		{"a.go": "package a\n\nfunc A() {}\nfunc B() {}\n"},91		{"a.go": "package a\n\nfunc A() {}\nfunc B() {}\nfunc C() {}\n"},92	})9394	cap := &captureObserver{}95	window, err := runHistory(dir, cap)96	if err != nil {97		t.Fatalf("runHistory: %v", err)98	}99100	if want := 3; window.Commits != want {101		t.Fatalf("window.Commits = %d, want %d", window.Commits, want)102	}103	if len(cap.commits) != 3 {104		t.Fatalf("observed %d commits, want 3", len(cap.commits))105	}106	// Oldest-first ordering: each commit's When should be >= previous.107	for i := 1; i < len(cap.commits); i++ {108		if cap.commits[i].When.Before(cap.commits[i-1].When) {109			t.Fatalf("commits not oldest-first: commit %d %s before %s",110				i, cap.commits[i].When, cap.commits[i-1].When)111		}112	}113	// Snapshot should contain a.go in HEAD.114	if _, ok := cap.snapshot.Files["a.go"]; !ok {115		t.Fatalf("HEAD snapshot missing a.go; got %v", cap.snapshot.Files)116	}117}118119func TestRunHistoryDepthCap(t *testing.T) {120	saveDepth := HistoryDepth121	HistoryDepth = 2122	t.Cleanup(func() { HistoryDepth = saveDepth })123124	dir := makeFixtureRepo(t, []map[string]string{125		{"a.go": "package a\nfunc A() {}\n"},126		{"a.go": "package a\nfunc A() {}\nfunc B() {}\n"},127		{"a.go": "package a\nfunc A() {}\nfunc B() {}\nfunc C() {}\n"},128		{"a.go": "package a\nfunc A() {}\nfunc B() {}\nfunc C() {}\nfunc D() {}\n"},129	})130131	cap := &captureObserver{}132	if _, err := runHistory(dir, cap); err != nil {133		t.Fatalf("runHistory: %v", err)134	}135	if len(cap.commits) != 2 {136		t.Fatalf("with --depth=2 observed %d commits, want 2", len(cap.commits))137	}138}139140func TestRunHistorySkipsBinaryAndUnknown(t *testing.T) {141	saveDepth := HistoryDepth142	HistoryDepth = 10143	t.Cleanup(func() { HistoryDepth = saveDepth })144145	dir := makeFixtureRepo(t, []map[string]string{146		{147			"main.go":            "package main\nfunc main() {}\n",148			"weird-extension.xx": "no language for this\n",149			"data.bin":           "\x00\x01\x02\x03\x00binary",150		},151	})152153	cap := &captureObserver{}154	if _, err := runHistory(dir, cap); err != nil {155		t.Fatalf("runHistory: %v", err)156	}157	if len(cap.changes) != 1 {158		t.Fatalf("expected one Observe call, got %d", len(cap.changes))159	}160	// Only main.go should survive.161	paths := []string{}162	for _, fc := range cap.changes[0] {163		paths = append(paths, fc.Path)164	}165	if len(paths) != 1 || paths[0] != "main.go" {166		t.Fatalf("expected only main.go, got %v", paths)167	}168}169170// TestRunHistoryWithoutGitInPath confirms the engine has no shell-out to the171// git binary by running the walk with PATH stripped to /nonexistent.172func TestRunHistoryWithoutGitInPath(t *testing.T) {173	savePath := os.Getenv("PATH")174	saveDepth := HistoryDepth175	HistoryDepth = 10176	t.Cleanup(func() {177		_ = os.Setenv("PATH", savePath)178		HistoryDepth = saveDepth179	})180181	dir := makeFixtureRepo(t, []map[string]string{182		{"a.go": "package a\nfunc A() {}\n"},183		{"a.go": "package a\nfunc A() { if true {} }\n"},184	})185186	// Strip PATH so any accidental exec.Command would fail.187	if err := os.Setenv("PATH", "/nonexistent"); err != nil {188		t.Fatalf("setenv: %v", err)189	}190191	cap := &captureObserver{}192	if _, err := runHistory(dir, cap); err != nil {193		t.Fatalf("runHistory: %v", err)194	}195	if len(cap.commits) != 2 {196		t.Fatalf("expected 2 commits, got %d", len(cap.commits))197	}198}199200func TestLineCountHelper(t *testing.T) {201	cases := []struct {202		in   string203		want int204	}{205		{"", 0},206		{"a", 1},207		{"a\n", 1},208		{"a\nb", 2},209		{"a\nb\n", 2},210		{"\n", 1},211	}212	for _, c := range cases {213		if got := lineCount(c.in); got != c.want {214			t.Errorf("lineCount(%q) = %d, want %d", c.in, got, c.want)215		}216	}217}

Code quality findings 6

Blank identifier discarding results; verify intentional ignoring of return values
warning correctness blank-identifier-discard
_ = os.Setenv("PATH", savePath)
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, snap := range commits {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for path, content := range snap {
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
full := filepath.Join(dir, path)
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
if err := os.WriteFile(full, []byte(content), 0o644); err != nil {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
paths = append(paths, fc.Path)

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.