detached d264a5c

test(daemon): cover atomic sync publication

commit
d264a5c81d7b63ed81afbed1876780a3590918d8
author
brice · · 2026-07-11 19:28 UTC
parent
96e9b63
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +41 -0
M internal/usecase/daemon/render_coordinator_test.go +41 -0
@@ -570,6 +570,47 @@ func TestRenderCoordinatorSyncGateFollowsDynamicRenderability(t *testing.T) {
570570 })
571571 }
572572
573+func TestRenderCoordinatorSyncGateRetriesWhenBatchPublishesDuringPredicate(t *testing.T) {
574+ h := newCoordinatorHarness(t)
575+ first, second := &pane{}, &pane{}
576+ gateEvaluated := make(chan struct{})
577+ releaseGate := make(chan struct{})
578+ var hookOnce sync.Once
579+ h.rc.opts.afterSyncGateEvaluated = func() {
580+ hookOnce.Do(func() {
581+ close(gateEvaluated)
582+ <-releaseGate
583+ })
584+ }
585+ h.rc.noteSyncBeginWithRenderability(first, 1, func() bool { return false })
586+ h.rc.invalidate(renderInvalidation{class: invalidateUrgent, producer: "test"})
587+
588+ fireDone := make(chan struct{})
589+ go func() {
590+ h.rc.fireCurrent(false)
591+ close(fireDone)
592+ }()
593+ <-gateEvaluated
594+ // This publication is deliberately between an open predicate result and
595+ // pending consumption. A stale gate result must be rejected and retried.
596+ h.rc.noteSyncBeginWithRenderability(second, 2, func() bool { return true })
597+ close(releaseGate)
598+ for range 4096 {
599+ select {
600+ case <-fireDone:
601+ goto fired
602+ default:
603+ runtime.Gosched()
604+ }
605+ }
606+ t.Fatal("fire did not finish after retrying the registry snapshot")
607+
608+fired:
609+ requireNoWake(t, h.wakes)
610+ h.rc.noteSyncEnd(second, 2)
611+ require.Equal(t, renderWake{urgent: true, coalesced: 1}, awaitWake(t, h.wakes))
612+}
613+
573614 func TestRenderCoordinatorSynchronizedOutput(t *testing.T) {
574615 t.Run("completion flushes pending state in one wake", func(t *testing.T) {
575616 h := newCoordinatorHarness(t)
@@ -570,6 +570,47 @@ func TestRenderCoordinatorSyncGateFollowsDynamicRenderability(t *testing.T) {
570 })570 })
571 }571 }
572 572
573+func TestRenderCoordinatorSyncGateRetriesWhenBatchPublishesDuringPredicate(t *testing.T) {
574+ h := newCoordinatorHarness(t)
575+ first, second := &pane{}, &pane{}
576+ gateEvaluated := make(chan struct{})
577+ releaseGate := make(chan struct{})
578+ var hookOnce sync.Once
579+ h.rc.opts.afterSyncGateEvaluated = func() {
580+ hookOnce.Do(func() {
581+ close(gateEvaluated)
582+ <-releaseGate
583+ })
584+ }
585+ h.rc.noteSyncBeginWithRenderability(first, 1, func() bool { return false })
586+ h.rc.invalidate(renderInvalidation{class: invalidateUrgent, producer: "test"})
587+
588+ fireDone := make(chan struct{})
589+ go func() {
590+ h.rc.fireCurrent(false)
591+ close(fireDone)
592+ }()
593+ <-gateEvaluated
594+ // This publication is deliberately between an open predicate result and
595+ // pending consumption. A stale gate result must be rejected and retried.
596+ h.rc.noteSyncBeginWithRenderability(second, 2, func() bool { return true })
597+ close(releaseGate)
598+ for range 4096 {
599+ select {
600+ case <-fireDone:
601+ goto fired
602+ default:
603+ runtime.Gosched()
604+ }
605+ }
606+ t.Fatal("fire did not finish after retrying the registry snapshot")
607+
608+fired:
609+ requireNoWake(t, h.wakes)
610+ h.rc.noteSyncEnd(second, 2)
611+ require.Equal(t, renderWake{urgent: true, coalesced: 1}, awaitWake(t, h.wakes))
612+}
613+
573 func TestRenderCoordinatorSynchronizedOutput(t *testing.T) {614 func TestRenderCoordinatorSynchronizedOutput(t *testing.T) {
574 t.Run("completion flushes pending state in one wake", func(t *testing.T) {615 t.Run("completion flushes pending state in one wake", func(t *testing.T) {
575 h := newCoordinatorHarness(t)616 h := newCoordinatorHarness(t)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close