Artifact 69a78834de3e44f9efcc99230879a85a7e79d6251f5e5ef2368d47ceb0bf8956:
- File
internal/index/landing_test.go
— part of check-in
[4832508615]
at
2026-08-26 23:13:58
on branch protect-3
— RFC 0007 phase 3: the merge is the fact; accept asks MayJoin
The fold reads accepted from the DAG, for every landing: the earliest target check-in after opening whose non-primary parent is on the proposed branch. A reply asserting acceptance with nothing merged leaves the record open and is recorded as a claim; the page says so, the timeline files it as conversation. Rejection has no DAG fact and stays the reply's to say. Re-derived after every branch recompute — which now also invalidates the count memo, since it can settle a landing — so a merge pushed from a clone with no reply settles.
landing.accept refuses a non-owner on a protected target before writing, naming the owners. landing.get reports MayLand beside CanAccept so the page shows a disabled control with the reason.
Fourteen existing tests settled records by reply alone, or asserted a by-hand merge does not count; each now carries the merge it announces or asserts the RFC's reading. Giving the web fixture real check-ins exposed checkin.diff dereferencing a nil store; it now refuses, after the lookups so not-found still wins. (user: dmestas, size: 9382) [annotate] [blame] [check-ins using] [more...]
package index import ( "testing" ) // A landing record's root body, as zit mints it (zit#127). func landingBody(branch, target, msg string) string { return "zit-landing: 1\nbranch: " + branch + "\ntarget: " + target + "\n\n" + msg + "\n" } func landingState(state, msg string) string { return "zit-landing-state: " + state + "\n\n" + msg + "\n" } func onlyLanding(t *testing.T, ix *Index, state string) LandingSummary { t.Helper() got, err := ix.Landings(state, 0) if err != nil { t.Fatal(err) } if len(got) != 1 { t.Fatalf("Landings(%q) returned %d rows, want 1: %+v", state, len(got), got) } return got[0] } func TestLandingRootFoldsIntoARecord(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "land the x work", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready for review")) l := onlyLanding(t, ix, "") if l.Branch != "feat/x" || l.Target != "trunk" { t.Fatalf("branch/target = %q/%q, want feat/x/trunk", l.Branch, l.Target) } if l.State != "open" { t.Fatalf("state = %q, want open — a record with no transition is open by absence", l.State) } if l.Title != "land the x work" || l.Author != "dan" { t.Fatalf("title/author = %q/%q", l.Title, l.Author) } if l.SettledBy != "" { t.Fatalf("settled_by = %q, want empty while open", l.SettledBy) } } func TestLandingStateIsLatestTransitionWins(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) // A plain comment must not settle anything. foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-01T11:00:00", "looks good\n") if l := onlyLanding(t, ix, ""); l.State != "open" { t.Fatalf("a comment settled the record: state = %q", l.State) } foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-02T10:00:00", landingState("rejected", "not yet")) if l := onlyLanding(t, ix, ""); l.State != "rejected" || l.SettledBy != "cccc3333" { t.Fatalf("state/settled_by = %q/%q, want rejected/cccc3333", l.State, l.SettledBy) } // Later wins, so a reopened-then-accepted thread reads accepted. // RFC 0007: the merge is the fact. The accept reply settles the // record because the DAG holds the merge it announces. ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base") ci(t, ix, "f1", "feat/x", "2026-01-01T09:30:00", "work", "t1") ci(t, ix, "m1", "trunk", "2026-01-03T09:59:00", "land feat/x", "t1", "f1") foldForumBody(t, ix, "dddd4444", "aaaa1111", "", "2026-01-03T10:00:00", landingState("accepted", "landed")) l := onlyLanding(t, ix, "") if l.State != "accepted" || l.SettledBy != "dddd4444" { t.Fatalf("state/settled_by = %q/%q, want accepted/dddd4444", l.State, l.SettledBy) } if l.Updated != "2026-01-03T10:00:00" { t.Fatalf("updated = %q, want the newest post's date", l.Updated) } if l.Opened != "2026-01-01T10:00:00" { t.Fatalf("opened = %q, want the root's date", l.Opened) } } // The fold must not depend on arrival order: sync delivers artifacts in // store order, which is not thread order, and a reply can be folded before // the root it belongs to. func TestLandingFoldIsOrderIndependent(t *testing.T) { ix := openIndex(t) ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base") ci(t, ix, "f1", "feat/x", "2026-01-01T09:30:00", "work", "t1") ci(t, ix, "m1", "trunk", "2026-01-02T09:59:00", "land feat/x", "t1", "f1") foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-02T10:00:00", landingState("accepted", "landed")) // The root has not arrived; nothing can be known yet, and that must not // be an error or a half-written row. if got, err := ix.Landings("", 0); err != nil || len(got) != 0 { t.Fatalf("Landings before the root: %d rows, err=%v — want 0, nil", len(got), err) } foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) l := onlyLanding(t, ix, "") if l.State != "accepted" || l.SettledBy != "cccc3333" { t.Fatalf("out-of-order fold converged on %q/%q, want accepted/cccc3333", l.State, l.SettledBy) } } func TestOrdinaryForumThreadIsNotALanding(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "Broken build", "2026-01-01T10:00:00", "the build is broken on aarch64\n") foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-01T11:00:00", "fixed\n") got, err := ix.Landings("", 0) if err != nil { t.Fatal(err) } if len(got) != 0 { t.Fatalf("an ordinary thread indexed as %d landing(s): %+v", len(got), got) } } // Refusing to guess: a header line this code does not know means the body // speaks a dialect we only think we understand, so it is not read at all. func TestLandingWithUnknownHeaderLineIsRefused(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "odd", "2026-01-01T10:00:00", "zit-landing: 1\nbranch: feat/x\ntarget: trunk\nreviewers: someone\n\nbody\n") if got, _ := ix.Landings("", 0); len(got) != 0 { t.Fatalf("an unknown header line was indexed anyway: %+v", got) } // Same for a missing required field. foldForumBody(t, ix, "bbbb2222", "", "odd", "2026-01-01T10:00:00", "zit-landing: 1\nbranch: feat/x\n\nbody\n") if got, _ := ix.Landings("", 0); len(got) != 0 { t.Fatalf("a record with no target was indexed: %+v", got) } } // An unknown state must never settle a record — a reply naming one is a // comment with an odd first line. func TestUnknownLandingStateDoesNotSettle(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00", "zit-landing-state: merged\n\nwhatever\n") if l := onlyLanding(t, ix, ""); l.State != "open" { t.Fatalf("an unknown state settled the record as %q", l.State) } } func TestLandingsFilterAndLookup(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "one", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) foldForumBody(t, ix, "bbbb2222", "", "two", "2026-01-02T10:00:00", landingBody("feat/y", "trunk", "ready")) ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base") ci(t, ix, "g1", "feat/y", "2026-01-02T11:00:00", "work", "t1") ci(t, ix, "m2", "trunk", "2026-01-03T09:59:00", "land feat/y", "t1", "g1") foldForumBody(t, ix, "cccc3333", "bbbb2222", "", "2026-01-03T10:00:00", landingState("accepted", "landed")) open, err := ix.Landings("open", 0) if err != nil { t.Fatal(err) } if len(open) != 1 || open[0].Branch != "feat/x" { t.Fatalf("open landings = %+v, want just feat/x", open) } all, err := ix.Landings("", 0) if err != nil { t.Fatal(err) } if len(all) != 2 { t.Fatalf("all landings = %d, want 2", len(all)) } // Newest activity first. if all[0].ID != "bbbb2222" { t.Fatalf("ordering put %q first, want bbbb2222 (newest updated)", all[0].ID) } l, ok, err := ix.Landing("bbbb2222") if err != nil || !ok || l.State != "accepted" { t.Fatalf("Landing(bbbb2222) = %+v ok=%v err=%v", l, ok, err) } if _, ok, err := ix.Landing("nope"); ok || err != nil { t.Fatalf("Landing(absent) = ok=%v err=%v, want false nil", ok, err) } } // An index whose forum_post rows predate this table must not show an empty // landing list until someone reindexes — the same guarantee backfillTickets // makes, and for the same reason. func TestBackfillLandingsPopulatesAnUpgradedIndex(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base") ci(t, ix, "f1", "feat/x", "2026-01-01T11:00:00", "work", "t1") ci(t, ix, "m1", "trunk", "2026-01-02T09:59:00", "land feat/x", "t1", "f1") foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00", landingState("accepted", "landed")) // Simulate the upgrade: the posts are indexed, the table is not. if _, err := ix.db.Exec(`DELETE FROM landing`); err != nil { t.Fatal(err) } if got, _ := ix.Landings("", 0); len(got) != 0 { t.Fatal("setup failed: landing table not empty") } if err := ix.backfillLandings(); err != nil { t.Fatal(err) } l := onlyLanding(t, ix, "") if l.Branch != "feat/x" || l.State != "accepted" { t.Fatalf("backfilled %+v, want feat/x accepted", l) } // A populated table is left alone rather than recomputed at every open. if _, err := ix.db.Exec(`UPDATE landing SET branch = 'sentinel'`); err != nil { t.Fatal(err) } if err := ix.backfillLandings(); err != nil { t.Fatal(err) } if l := onlyLanding(t, ix, ""); l.Branch != "sentinel" { t.Fatal("backfill overwrote a populated table") } } // A thread that stops being a landing must not leave a record behind. func TestLandingRowIsDroppedWhenTheRootIsNotALanding(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) if got, _ := ix.Landings("", 0); len(got) != 1 { t.Fatal("setup failed") } // Refolding the same hash with an ordinary body (a corrected mirror, a // shunned-and-replaced record) must retract the row. foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00", "never mind\n") if got, _ := ix.Landings("", 0); len(got) != 0 { t.Fatalf("a retracted record survived: %+v", got) } }