Skip to content
quarry Artifact [074f651939]
Login

Artifact [074f651939]

Artifact 074f651939d7b7ba1b90b963bd05d77e4ec9537a543ff552f4fd15f599aea93a:


package index

import (
	"testing"

	"craftdesign.group/quarry/internal/artifact"
)

// countsFixture folds three check-ins the two ways the index learns
// them. c1 (crosslink) edits src/a.go +12 -3 and adds logo.png, a
// binary. c2 (crosslink, a merge) edits src/b.go +5 -0 and docs/x.md,
// which zit could not count. c3 (a live fold) edits src/a.go +1 -1.
func countsFixture(t *testing.T) *Index {
	t.Helper()
	ix := openIndex(t)
	if err := ix.FoldCheckins([]CheckinRecord{
		{Hash: "c1", Date: "2026-10-01T10:00:00", User: "u", Comment: "one",
			Changes: []string{"src/a.go", "logo.png"},
			Lines:   map[string]LineCount{"src/a.go": {12, 3}, "logo.png": {-1, -1}}},
		{Hash: "c2", Date: "2026-10-02T10:00:00", User: "u", Comment: "two", Parents: []string{"c1", "side"},
			Changes: []string{"src/b.go", "docs/x.md"},
			Lines:   map[string]LineCount{"src/b.go": {5, 0}}},
	}); err != nil {
		t.Fatal(err)
	}
	art := &artifact.Artifact{Type: artifact.Manifest, Date: "2026-10-03T10:00:00", User: "u", Comment: "three",
		Parents: []string{"c2"}}
	if err := ix.FoldNow([]Parsed{{Hash: "c3", Art: art, Changes: []string{"src/a.go"},
		Lines: map[string]LineCount{"src/a.go": {1, 1}}}}); err != nil {
		t.Fatal(err)
	}
	return ix
}

// A check-in's size is its files and, when all were counted, its lines;
// a binary file is a file with no lines, and one uncounted file makes
// the lines unknown rather than short.
func TestChangeStatsSizeACheckin(t *testing.T) {
	ix := countsFixture(t)
	got, err := ix.ChangeStats([]string{"c1", "c2", "c3", "nope"}, "")
	if err != nil {
		t.Fatal(err)
	}
	for h, want := range map[string]ChangeStat{
		"c1": {Files: 2, Added: 12, Removed: 3, Counted: true},
		"c2": {Files: 2, Counted: false},
		"c3": {Files: 1, Added: 1, Removed: 1, Counted: true},
	} {
		if got[h] != want {
			t.Errorf("FAIL: %s: %+v, want %+v", h, got[h], want)
		}
	}
	if _, ok := got["nope"]; ok {
		t.Error("FAIL: a check-in with no rows was sized")
	}
}

// A path's history counts that path alone, and a folder's everything
// below it.
func TestChangeStatsForAPath(t *testing.T) {
	ix := countsFixture(t)
	file, _ := ix.ChangeStats([]string{"c1", "c3"}, "src/a.go")
	if file["c1"] != (ChangeStat{Files: 1, Added: 12, Removed: 3, Counted: true}) {
		t.Errorf("FAIL: src/a.go in c1: %+v", file["c1"])
	}
	dir, _ := ix.ChangeStats([]string{"c1", "c2"}, "src")
	if dir["c1"] != (ChangeStat{Files: 1, Added: 12, Removed: 3, Counted: true}) ||
		dir["c2"] != (ChangeStat{Files: 1, Added: 5, Counted: true}) {
		t.Errorf("FAIL: src/ in c1 %+v, c2 %+v", dir["c1"], dir["c2"])
	}
	// "src" must not take in a sibling that merely starts the same.
	if err := ix.FoldCheckins([]CheckinRecord{{Hash: "c4", Date: "2026-10-04T10:00:00", Comment: "x",
		Changes: []string{"srcx/y.go"}, Lines: map[string]LineCount{"srcx/y.go": {9, 9}}}}); err != nil {
		t.Fatal(err)
	}
	if got, _ := ix.ChangeStats([]string{"c4"}, "src"); len(got) != 0 {
		t.Errorf("FAIL: srcx/ counted as part of src/: %+v", got)
	}
}

// The backfill fills rows that have no counts and leaves counted ones
// alone; a re-fold without counts does not erase them.
func TestFillLineCountsOnlyFills(t *testing.T) {
	ix := countsFixture(t)
	if err := ix.FillLineCounts(map[string]map[string]LineCount{
		"c2": {"docs/x.md": {4, 4}, "src/b.go": {99, 99}},
	}); err != nil {
		t.Fatal(err)
	}
	got, _ := ix.ChangeStats([]string{"c2"}, "")
	if got["c2"] != (ChangeStat{Files: 2, Added: 9, Removed: 4, Counted: true}) {
		t.Errorf("FAIL: after the fill c2 is %+v", got["c2"])
	}
	if err := ix.FoldCheckins([]CheckinRecord{{Hash: "c1", Date: "2026-10-01T10:00:00", User: "u", Comment: "one",
		Changes: []string{"src/a.go", "logo.png"}}}); err != nil {
		t.Fatal(err)
	}
	if got, _ := ix.ChangeStats([]string{"c1"}, ""); !got["c1"].Counted {
		t.Error("FAIL: a re-fold without counts erased them")
	}
	if ix.LineCountsDone() {
		t.Error("FAIL: a fresh index claims complete counts")
	}
	if err := ix.MarkLineCountsDone(); err != nil || !ix.LineCountsDone() {
		t.Errorf("FAIL: marking the counts done: %v", err)
	}
}