diff --git a/modules/messenger/cmd/messenger/adr505_test.go b/modules/messenger/cmd/messenger/adr505_test.go index 93396e5c..a1ffb487 100644 --- a/modules/messenger/cmd/messenger/adr505_test.go +++ b/modules/messenger/cmd/messenger/adr505_test.go @@ -370,3 +370,40 @@ func TestAWalkSaidAgainWithItsDeclaredMachinesActsOnce(t *testing.T) { t.Fatalf("its end was told %d times: %+v", n, dt.sends) } } + +// ADR 0505 §7: a message two walks first held, the sooner of which ends at its limit, is said exactly once — in that +// walk's end message — and not again on its own for the other walk; once per channel when it is urgent. +func TestAMessageTwoWalksFirstHeldIsSaidOnceAtTheSoonerLimit(t *testing.T) { + for _, sev := range []string{Warning, Urgent} { + h, tg, dt, c, _ := newHolder(t) + walk(t, h, "plan-a", WalkStarted, "", c.now(), 360, novox("gitea")) // its limit: 6 min and the settle + walk(t, h, "plan-b", WalkStarted, "", c.now(), 1200, novox("mesh-issues")) // its limit: 22 min + k := cond("machine.novox.network", sev, "novox lost its default route") + k.Raised = c.now() + h.Condition(EventRaised, k) + runFor(h, c, 7*time.Minute) + walk(t, h, "plan-a", WalkEnded, WalkDelivered, c.now(), 0) // settles until its limit, 8 min after the hold + runFor(h, c, 3*time.Minute) + count := func(ch *fakeChannel) int { + n := 0 + for _, m := range ch.sends { + if strings.Contains(m.Text(), headlineOf(k.Key)) { + n++ + } + } + return n + } + want := map[*fakeChannel]int{dt: 1, tg: 0} + if sev == Urgent { + want[tg] = 1 + } + for ch, n := range want { + if got := count(ch); got != n { + t.Fatalf("%s: said %d times on %s, want %d: %+v", sev, got, ch.name, n, ch.sends) + } + } + if !heard("Still open after the delivery of gitea to novox:", dt) { + t.Fatalf("%s: not told in the sooner walk's end message: %+v", sev, dt.sends) + } + } +} diff --git a/modules/messenger/cmd/messenger/walks.go b/modules/messenger/cmd/messenger/walks.go index a6bd83fc..ada2b575 100644 --- a/modules/messenger/cmd/messenger/walks.go +++ b/modules/messenger/cmd/messenger/walks.go @@ -598,6 +598,16 @@ func (h *Holder) settledWalks(now time.Time) { // due tells what it holds in its message instead. Called at each flush. func (h *Holder) overdueHolds(now time.Time) { h.mu.Lock() + // What a walk whose own end or bound is due holds is told in that walk's message, and nowhere else: one place + // says it, so a message two walks hold is never said twice. + tellsItself := map[string]bool{} + for _, w := range h.walks { + if !now.Before(w.holdEnd()) { + for key := range w.Held { + tellsItself[key] = true + } + } + } due := map[string]HeldMessage{} var touched []*Walk for _, w := range h.walks { @@ -607,7 +617,7 @@ func (h *Holder) overdueHolds(now time.Time) { } for key, hm := range w.Held { if hm.past(now) { - if prev, ok := due[key]; !ok || hm.At.Before(prev.At) { + if prev, ok := due[key]; !tellsItself[key] && (!ok || hm.At.Before(prev.At)) { due[key] = hm } delete(w.Held, key)