detached 7d036d7

test(daemon): synchronize sync lifecycle deadlines

commit
7d036d74be2e09d5da7a34560fddfff2a2dec447
author
brice · · 2026-07-11 22:20 UTC
parent
7f0463c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +21 -11
M internal/usecase/daemon/render_test.go +21 -11
@@ -160,13 +160,23 @@ func drainCoordinatorTimers(clock *coordinatorMockClock) []*coordinatorMockTimer
160160 }
161161 }
162162
163-func fireCoordinatorTimer(t *testing.T, timers []*coordinatorMockTimer, duration time.Duration) {
163+func fireCoordinatorTimer(t *testing.T, rc *renderCoordinator, timers []*coordinatorMockTimer, duration time.Duration) {
164164 t.Helper()
165165 for _, timer := range timers {
166- if timer.duration == duration {
167- timer.ch <- time.Time{}
168- return
166+ if timer.duration != duration {
167+ continue
169168 }
169+ var done <-chan struct{}
170+ rc.mu.Lock()
171+ if rc.normalTimer == timer.mock {
172+ done = rc.normalWorkerDone
173+ }
174+ rc.mu.Unlock()
175+ timer.ch <- time.Time{}
176+ if done != nil {
177+ <-done
178+ }
179+ return
170180 }
171181 t.Fatalf("coordinator did not arm %s timer", duration)
172182 }
@@ -178,7 +188,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
178188 d, sess, ac, sends := newManualSessionWithPTYs(t, activePTY, inactivePTY)
179189 clock := newCoordinatorMockClock(t, 8)
180190 d.clock = clock.clock
181- d.attachCoordinator(sess, nil, ac, true)
191+ rc := d.attachCoordinator(sess, nil, ac, true)
182192
183193 d.sessWg.Add(1)
184194 go d.ptyReader(sess, sess.tabs[1], sess.tabs[1].focusedPane())
@@ -189,12 +199,12 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
189199 sess.active = 1
190200 sess.mu.Unlock()
191201 d.invalidateRender(sess, ac, true, "sync activation")
192- fireCoordinatorTimer(t, drainCoordinatorTimers(clock), urgentRenderDeadline)
202+ fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline)
193203 requireNoCoordinatorOutputFrame(t, sends)
194204
195205 inactiveSteps <- channelPTYStep{data: []byte(" complete\x1b[?2026l")}
196206 awaitPTYReadProcessed(t, inactiveProcessed)
197- fireCoordinatorTimer(t, drainCoordinatorTimers(clock), urgentRenderDeadline)
207+ fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline)
198208 frame := awaitOutputFrameWithoutSleep(t, sends)
199209 output, err := ports.UnmarshalOutput(frame.Payload)
200210 require.NoError(t, err)
@@ -214,7 +224,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
214224 d, sess, ac, sends := newManualSessionWithPTYs(t, oldPTY, newPTY, parkedPTY)
215225 clock := newCoordinatorMockClock(t, 8)
216226 d.clock = clock.clock
217- d.attachCoordinator(sess, nil, ac, true)
227+ rc := d.attachCoordinator(sess, nil, ac, true)
218228
219229 d.sessWg.Add(2)
220230 go d.ptyReader(sess, sess.tabs[0], sess.tabs[0].focusedPane())
@@ -227,7 +237,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
227237 sess.mu.Unlock()
228238 newSteps <- channelPTYStep{data: []byte("newly active")}
229239 awaitPTYReadProcessed(t, newProcessed)
230- fireCoordinatorTimer(t, drainCoordinatorTimers(clock), minOutputRenderDeadline)
240+ fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), minOutputRenderDeadline)
231241 frame := awaitOutputFrameWithoutSleep(t, sends)
232242 output, err := ports.UnmarshalOutput(frame.Payload)
233243 require.NoError(t, err)
@@ -275,7 +285,7 @@ func TestPTYReaderRepublishesSynchronizedCompletionAfterAttachmentLifecycle(t *t
275285 steps <- channelPTYStep{data: []byte(" complete\x1b[?2026l")}
276286 awaitPTYReadProcessed(t, processed)
277287
278- fireCoordinatorTimer(t, drainCoordinatorTimers(clock), urgentRenderDeadline)
288+ fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline)
279289 wake := <-previews
280290 require.True(t, wake.urgent)
281291 require.Equal(t, 2, wake.coalesced, "completion retains coordinator coalescing across the cleared attachment work")
@@ -317,7 +327,7 @@ func TestPTYReaderRepublishesSynchronizedCompletionAfterAttachmentLifecycle(t *t
317327 awaitPTYReadProcessed(t, processed)
318328 requireNoWake(t, wakes)
319329
320- fireCoordinatorTimer(t, replacementTimers, urgentRenderDeadline)
330+ fireCoordinatorTimer(t, rc, replacementTimers, urgentRenderDeadline)
321331 wake := <-wakes
322332 require.True(t, wake.urgent)
323333 require.True(t, wake.reset, "the replacement's cleared batch must repaint a complete frame")
@@ -160,13 +160,23 @@ func drainCoordinatorTimers(clock *coordinatorMockClock) []*coordinatorMockTimer
160 }160 }
161 }161 }
162 162
163-func fireCoordinatorTimer(t *testing.T, timers []*coordinatorMockTimer, duration time.Duration) {163+func fireCoordinatorTimer(t *testing.T, rc *renderCoordinator, timers []*coordinatorMockTimer, duration time.Duration) {
164 t.Helper()164 t.Helper()
165 for _, timer := range timers {165 for _, timer := range timers {
166- if timer.duration == duration {166+ if timer.duration != duration {
167- timer.ch <- time.Time{}167+ continue
168- return
169 }168 }
169+ var done <-chan struct{}
170+ rc.mu.Lock()
171+ if rc.normalTimer == timer.mock {
172+ done = rc.normalWorkerDone
173+ }
174+ rc.mu.Unlock()
175+ timer.ch <- time.Time{}
176+ if done != nil {
177+ <-done
178+ }
179+ return
170 }180 }
171 t.Fatalf("coordinator did not arm %s timer", duration)181 t.Fatalf("coordinator did not arm %s timer", duration)
172 }182 }
@@ -178,7 +188,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
178 d, sess, ac, sends := newManualSessionWithPTYs(t, activePTY, inactivePTY)188 d, sess, ac, sends := newManualSessionWithPTYs(t, activePTY, inactivePTY)
179 clock := newCoordinatorMockClock(t, 8)189 clock := newCoordinatorMockClock(t, 8)
180 d.clock = clock.clock190 d.clock = clock.clock
181- d.attachCoordinator(sess, nil, ac, true)191+ rc := d.attachCoordinator(sess, nil, ac, true)
182 192
183 d.sessWg.Add(1)193 d.sessWg.Add(1)
184 go d.ptyReader(sess, sess.tabs[1], sess.tabs[1].focusedPane())194 go d.ptyReader(sess, sess.tabs[1], sess.tabs[1].focusedPane())
@@ -189,12 +199,12 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
189 sess.active = 1199 sess.active = 1
190 sess.mu.Unlock()200 sess.mu.Unlock()
191 d.invalidateRender(sess, ac, true, "sync activation")201 d.invalidateRender(sess, ac, true, "sync activation")
192- fireCoordinatorTimer(t, drainCoordinatorTimers(clock), urgentRenderDeadline)202+ fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline)
193 requireNoCoordinatorOutputFrame(t, sends)203 requireNoCoordinatorOutputFrame(t, sends)
194 204
195 inactiveSteps <- channelPTYStep{data: []byte(" complete\x1b[?2026l")}205 inactiveSteps <- channelPTYStep{data: []byte(" complete\x1b[?2026l")}
196 awaitPTYReadProcessed(t, inactiveProcessed)206 awaitPTYReadProcessed(t, inactiveProcessed)
197- fireCoordinatorTimer(t, drainCoordinatorTimers(clock), urgentRenderDeadline)207+ fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline)
198 frame := awaitOutputFrameWithoutSleep(t, sends)208 frame := awaitOutputFrameWithoutSleep(t, sends)
199 output, err := ports.UnmarshalOutput(frame.Payload)209 output, err := ports.UnmarshalOutput(frame.Payload)
200 require.NoError(t, err)210 require.NoError(t, err)
@@ -214,7 +224,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
214 d, sess, ac, sends := newManualSessionWithPTYs(t, oldPTY, newPTY, parkedPTY)224 d, sess, ac, sends := newManualSessionWithPTYs(t, oldPTY, newPTY, parkedPTY)
215 clock := newCoordinatorMockClock(t, 8)225 clock := newCoordinatorMockClock(t, 8)
216 d.clock = clock.clock226 d.clock = clock.clock
217- d.attachCoordinator(sess, nil, ac, true)227+ rc := d.attachCoordinator(sess, nil, ac, true)
218 228
219 d.sessWg.Add(2)229 d.sessWg.Add(2)
220 go d.ptyReader(sess, sess.tabs[0], sess.tabs[0].focusedPane())230 go d.ptyReader(sess, sess.tabs[0], sess.tabs[0].focusedPane())
@@ -227,7 +237,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
227 sess.mu.Unlock()237 sess.mu.Unlock()
228 newSteps <- channelPTYStep{data: []byte("newly active")}238 newSteps <- channelPTYStep{data: []byte("newly active")}
229 awaitPTYReadProcessed(t, newProcessed)239 awaitPTYReadProcessed(t, newProcessed)
230- fireCoordinatorTimer(t, drainCoordinatorTimers(clock), minOutputRenderDeadline)240+ fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), minOutputRenderDeadline)
231 frame := awaitOutputFrameWithoutSleep(t, sends)241 frame := awaitOutputFrameWithoutSleep(t, sends)
232 output, err := ports.UnmarshalOutput(frame.Payload)242 output, err := ports.UnmarshalOutput(frame.Payload)
233 require.NoError(t, err)243 require.NoError(t, err)
@@ -275,7 +285,7 @@ func TestPTYReaderRepublishesSynchronizedCompletionAfterAttachmentLifecycle(t *t
275 steps <- channelPTYStep{data: []byte(" complete\x1b[?2026l")}285 steps <- channelPTYStep{data: []byte(" complete\x1b[?2026l")}
276 awaitPTYReadProcessed(t, processed)286 awaitPTYReadProcessed(t, processed)
277 287
278- fireCoordinatorTimer(t, drainCoordinatorTimers(clock), urgentRenderDeadline)288+ fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline)
279 wake := <-previews289 wake := <-previews
280 require.True(t, wake.urgent)290 require.True(t, wake.urgent)
281 require.Equal(t, 2, wake.coalesced, "completion retains coordinator coalescing across the cleared attachment work")291 require.Equal(t, 2, wake.coalesced, "completion retains coordinator coalescing across the cleared attachment work")
@@ -317,7 +327,7 @@ func TestPTYReaderRepublishesSynchronizedCompletionAfterAttachmentLifecycle(t *t
317 awaitPTYReadProcessed(t, processed)327 awaitPTYReadProcessed(t, processed)
318 requireNoWake(t, wakes)328 requireNoWake(t, wakes)
319 329
320- fireCoordinatorTimer(t, replacementTimers, urgentRenderDeadline)330+ fireCoordinatorTimer(t, rc, replacementTimers, urgentRenderDeadline)
321 wake := <-wakes331 wake := <-wakes
322 require.True(t, wake.urgent)332 require.True(t, wake.urgent)
323 require.True(t, wake.reset, "the replacement's cleared batch must repaint a complete frame")333 require.True(t, wake.reset, "the replacement's cleared batch must repaint a complete frame")

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