diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/adr548_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/adr548_test.go index 05a44104..89d97b1b 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/adr548_test.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/adr548_test.go @@ -164,6 +164,45 @@ func TestOneCheckPerDeliveryAtTheGate(t *testing.T) { t.Fatalf("merges %v; the turns were %q, want %q", w.forge.merges, turns.said(), want) } }, + // §8: an approved delivery whose trunk moved goes back to proposed, waits for its carried verdict rather than being + // handed on the old one, and merges on it with no second ask. + "an approved merge whose trunk moved merges on its carried verdict with no second ask": func(t *testing.T) { + w := newWorld(t) + putCheckerVerdict(w, "running", headOf(oneRepo, 9, otherHead)) // holds the approved merge (§2) + id := approvedLone(w) + w.settleAll() + if w.op.count() != 1 || len(w.forge.merges) != 0 { + t.Fatalf("asked %d times, merges %v; want approved and held", w.op.count(), w.forge.merges) + } + w.h.PullMerged(merged(oneRepo, 5, otherHead, oneTrunk2)) // a merge at the forge moves the trunk + putCheckerVerdict(w, "pass", headOf(oneRepo, 9, otherHead)) + w.settleAll() + if len(w.forge.merges) != 0 { + t.Fatalf("merges %v: handed on a verdict judged on the old trunk", w.forge.merges) + } + d := w.delivery(id) + if d.Ready == nil || !strings.Contains(d.Ready.Waits, "being carried") { + t.Fatalf("after the trunk moved it waits for %q, want its verdict carried", d.Ready.Waits) + } + // The checker's carried line: the head on the new trunk, carried from the old one. + carried := checkStatus(oneRepo, oneHead, "success", oneTrunk2) + carried.Description = "pass " + oneHead[:12] + " on " + oneTrunk2[:12] + " | carried from " + oneTrunk[:12] + " | unit 0 run, 4 kept" + if head, trunk := checkLine(carried.Description); head != oneHead[:12] || trunk != oneTrunk2[:12] { + t.Fatalf("the carried line reads as %s on %s", head, trunk) + } + if head, trunk := checkLine("failed: a test"); head != "" || trunk != "" { + t.Fatalf("a line that names no commits gave %q %q", head, trunk) + } + putCheckerVerdict(w, "pass", headOf(oneRepo, 1, oneHead)) + w.h.CommitStatus(carried) + w.settleAll() + if w.op.count() != 1 { + t.Errorf("the Approve was asked %d times, want once: it stands through the carry", w.op.count()) + } + if d := w.delivery(id); d.Check == nil || d.Check.Trunk != oneTrunk2[:12] || len(w.forge.merges) != 1 { + t.Errorf("after the carry: verdict %+v, merges %v; want merged on the carried verdict", d.Check, w.forge.merges) + } + }, } { t.Run(name, run) } diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/onestatus_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/onestatus_test.go index e2b909a7..18b02448 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/onestatus_test.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/onestatus_test.go @@ -199,14 +199,3 @@ func TestAClosedPullRequestAsksNoForgetChecks(t *testing.T) { w.h.PullUpdated(p) w.h.PullClosed(p) } - -// 1302: the mesh/check line gives the head and the trunk it judged. -func TestTheCheckLineGivesHeadAndTrunk(t *testing.T) { - head, trunk := checkLine("pass " + oneHead[:12] + " on " + oneTrunk[:12] + " | unit 4 run, 0 kept | group g of 2 pull requests") - if head != oneHead[:12] || trunk != oneTrunk[:12] { - t.Fatalf("head %q trunk %q", head, trunk) - } - if head, trunk = checkLine("failed: a test"); head != "" || trunk != "" { - t.Fatalf("a line that names no commits gave %q %q", head, trunk) - } -} diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/ready.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/ready.go index a141e803..0fba9e14 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/ready.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/ready.go @@ -387,6 +387,9 @@ func (h *Holder) readyWaits(d *Delivery) string { case d.State == Proposed && d.outOfDate() != "" && d.AskedAgain != nil: return "its check against the trunk's head is asked and has not answered" case d.State == Proposed && d.outOfDate() != "" && d.Check.Passes() && h.covering(d) != nil: + // Approved, and its trunk moved: the checker carries its verdict to the trunk's head by its package digests (ADR 0548 + // §5, §8). The Approve stands and is not asked again; the merge waits for the carried verdict, not a fresh check. + return "approved; its verdict is being carried to the trunk's head " + shortOf(d.TrunkNow, 12) + " (" + d.outOfDate() + ")" case d.State == Proposed && d.outOfDate() != "": return "its verdict is out of date: its check against the trunk's head is asked before the operator is" case d.State == Proposed && d.AskedAgain != nil: