Skip to content
quarry Artifact [cae6119ba4]
Login

Artifact [cae6119ba4]

Artifact cae6119ba48bbd767f78dc4da410a30751e03155f56bcbea059e8bb8df1075e1:


package index

import (
	"testing"
)

// foldMerge records a check-in whose second parent is a branch tip —
// the shape zit writes when it lands a branch.
func foldMerge(t *testing.T, ix *Index, hash, branch, date, comment string, parents ...string) {
	t.Helper()
	if _, err := ix.db.Exec(`INSERT OR REPLACE INTO checkin (hash, date, user, comment, branch)
		VALUES (?, ?, 'dmestas', ?, ?)`, hash, date, comment, branch); err != nil {
		t.Fatal(err)
	}
	for i, p := range parents {
		if _, err := ix.db.Exec(`INSERT OR REPLACE INTO checkin_parent (child, parent, ord)
			VALUES (?, ?, ?)`, hash, p, i); err != nil {
			t.Fatal(err)
		}
	}
}

// landed builds an accepted landing with a branch tip and its merge.
func landed(t *testing.T, ix *Index, root, branch, title string) {
	t.Helper()
	foldForumBody(t, ix, root, "", title, "2026-01-01T10:00:00",
		landingBody(branch, "trunk", "please"))
	// The DAG first: these rows are written directly, not folded, so
	// nothing re-derives the record after them. The reply that follows
	// re-folds the thread and finds the merge already there — which is
	// also the order the forge sees when zit accepts.
	foldMerge(t, ix, "tip"+branch, branch, "2026-01-02T10:00:00", "work on "+branch)
	foldMerge(t, ix, "mrg"+branch, "trunk", "2026-01-02T12:00:00",
		"land "+branch, "prevtrunk", "tip"+branch)
	foldForumBody(t, ix, root+"ff", root, "", "2026-01-03T10:00:00",
		landingState("accepted", "landed"))
}

// landing.settled_by holds the reply that DECIDED the state — a forum
// post hash, not a commit — which was read as meaning nothing connects
// a landing to the check-in it produced. The answer was already in the
// DAG: a merge check-in lists its primary parent first and the branches
// it merged after, so ord > 0 IS the tip that landed.
func TestLandingResolvesToItsMergeCheckin(t *testing.T) {
	ix := openIndex(t)
	landed(t, ix, "aaaa1111", "feat-x", "Ship it")

	got, err := ix.MergesForLandings([]string{"aaaa1111"})
	if err != nil {
		t.Fatal(err)
	}
	m, ok := got["aaaa1111"]
	if !ok {
		t.Fatal("an accepted landing resolved to no check-in")
	}
	if m.Checkin != "mrgfeat-x" {
		t.Errorf("resolved to %q, want the merge on trunk", m.Checkin)
	}
	if m.Comment != "land feat-x" {
		t.Errorf("comment %q", m.Comment)
	}
}

// The merge lands BEFORE the artifact recording the verdict is written
// — one landing in this forge merged at 12:46:55 and was recorded at
// 12:49:51 — so bounding the search on the verdict silently loses
// exactly the check-ins this exists to find. A proposal cannot be
// landed before it is made; that is the bound that holds.
func TestMergeIsFoundEvenWhenItPrecedesTheVerdict(t *testing.T) {
	ix := openIndex(t)
	foldForumBody(t, ix, "aaaa1111", "", "Ship it", "2026-01-01T10:00:00",
		landingBody("feat-x", "trunk", "please"))
	foldMerge(t, ix, "tipfeat-x", "feat-x", "2026-01-02T12:46:55", "work")
	foldMerge(t, ix, "mrgfeat-x", "trunk", "2026-01-02T12:46:55", "land it",
		"prevtrunk", "tipfeat-x")
	// The verdict is filed three minutes AFTER the merge.
	foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-02T12:49:51",
		landingState("accepted", "landed"))

	got, err := ix.MergesForLandings([]string{"aaaa1111"})
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := got["aaaa1111"]; !ok {
		t.Fatal("the merge was lost because it preceded its own verdict record")
	}
}

// Open and rejected landings produced nothing, and must not borrow a
// merge that happens to touch the same branch.
// A landing nothing merged resolves to nothing, open or rejected. And
// the case this test used to assert the other way: a merge of the
// branch onto the target after the landing opened IS the landing's
// merge, whoever made it and whatever the thread says — RFC 0007's
// "the merge is the fact". The record settles once something re-reads
// it, which is what the refresh after every branch recompute does.
func TestUnsettledLandingsResolveToNothing(t *testing.T) {
	ix := openIndex(t)
	foldForumBody(t, ix, "aaaa1111", "", "Still open", "2026-01-01T10:00:00",
		landingBody("feat-x", "trunk", "please"))
	if got, err := ix.MergesForLandings([]string{"aaaa1111"}); err != nil || len(got) != 0 {
		t.Fatalf("an OPEN landing with no merge resolved to %v (err=%v)", got, err)
	}
	foldForumBody(t, ix, "bbbb2222", "", "Not this", "2026-01-01T10:00:00",
		landingBody("feat-y", "trunk", "please"))
	foldForumBody(t, ix, "cccc3333", "bbbb2222", "", "2026-01-03T10:00:00",
		landingState("rejected", "no"))
	if got, _ := ix.MergesForLandings([]string{"bbbb2222"}); len(got) != 0 {
		t.Errorf("a REJECTED landing with no merge resolved to %v", got)
	}

	// Merged by hand, from a clone, no reply: still landed.
	foldMerge(t, ix, "tipfeat-x", "feat-x", "2026-01-02T10:00:00", "work")
	foldMerge(t, ix, "mrgfeat-x", "trunk", "2026-01-02T12:00:00", "merged by hand",
		"prevtrunk", "tipfeat-x")
	tx, err := ix.db.Begin()
	if err != nil {
		t.Fatal(err)
	}
	if err := refreshLandingStates(tx); err != nil {
		t.Fatal(err)
	}
	if err := tx.Commit(); err != nil {
		t.Fatal(err)
	}
	got, _ := ix.MergesForLandings([]string{"aaaa1111"})
	if m, ok := got["aaaa1111"]; !ok || m.Checkin != "mrgfeat-x" {
		t.Errorf("a by-hand merge after opening did not settle the landing: %v", got)
	}
}

// A merge somebody made by hand carries no landing, and the timeline
// decoration's absence is what distinguishes the two.
func TestTimelineDecoratesOnlyLandingMerges(t *testing.T) {
	ix := openIndex(t)
	landed(t, ix, "aaaa1111", "feat-x", "Ship it")
	// An unrelated merge of a branch no landing proposed.
	foldMerge(t, ix, "tiphotfix", "hotfix", "2026-01-04T10:00:00", "urgent")
	foldMerge(t, ix, "mrghotfix", "trunk", "2026-01-04T12:00:00", "merge hotfix",
		"prevtrunk", "tiphotfix")

	rows := []TimelineEntry{
		{Kind: "ci", Hash: "mrgfeat-x", IsMerge: true},
		{Kind: "ci", Hash: "mrghotfix", IsMerge: true},
		{Kind: "ci", Hash: "tipfeat-x"},
	}
	if err := ix.attachLandings(rows); err != nil {
		t.Fatal(err)
	}
	if rows[0].Landing != "aaaa1111" || rows[0].LandingTitle != "Ship it" {
		t.Errorf("landing merge not decorated: %+v", rows[0])
	}
	if rows[0].LandingApprover != "dan" {
		t.Errorf("approver %q, want the author of the verdict", rows[0].LandingApprover)
	}
	if rows[1].Landing != "" {
		t.Errorf("a hand-made merge was decorated with %q", rows[1].Landing)
	}
	if rows[2].Landing != "" {
		t.Error("a non-merge check-in was decorated")
	}
}

// A check-in names the landing that produced it: the merge an accept
// wrote, and each check-in on the branch the landing carried. A hand
// merge and a check-in on the target belong to no landing.
func TestCheckinNamesItsLanding(t *testing.T) {
	ix := openIndex(t)
	landed(t, ix, "aaaa1111", "feat-x", "Ship it")
	foldMerge(t, ix, "tiphotfix", "hotfix", "2026-01-04T10:00:00", "urgent")
	foldMerge(t, ix, "mrghotfix", "trunk", "2026-01-04T12:00:00", "merge hotfix",
		"prevtrunk", "tiphotfix")
	foldMerge(t, ix, "plaintrunk", "trunk", "2026-01-05T12:00:00", "plain", "mrgfeat-x")

	for hash, want := range map[string]LandedBy{
		"mrgfeat-x": {ID: "aaaa1111", Title: "Ship it", State: "accepted", Role: "merge"},
		"tipfeat-x": {ID: "aaaa1111", Title: "Ship it", State: "accepted", Role: "branch"},
		"mrghotfix": {}, "tiphotfix": {}, "plaintrunk": {},
	} {
		got, err := ix.LandedByCheckin(hash)
		if err != nil {
			t.Fatal(err)
		}
		if got != want {
			t.Errorf("%s: got %+v, want %+v", hash, got, want)
		}
	}
}