test(daemon): synchronize sync lifecycle deadlines
- commit
- 7d036d74be2e09d5da7a34560fddfff2a2dec447
- author
- brice · · 2026-07-11 22:20 UTC
- parent
- 7f0463c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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 | |||
| 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 | 164 | t.Helper() | |
| 165 | 165 | for _, timer := range timers { | |
| 166 | - | if timer.duration == duration { | |
| 167 | - | timer.ch <- time.Time{} | |
| 168 | - | return | |
| 166 | + | if timer.duration != duration { | |
| 167 | + | continue | |
| 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 | 181 | t.Fatalf("coordinator did not arm %s timer", duration) | |
| 172 | 182 | } | |
| @@ -178,7 +188,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) { | |||
| 178 | 188 | d, sess, ac, sends := newManualSessionWithPTYs(t, activePTY, inactivePTY) | |
| 179 | 189 | clock := newCoordinatorMockClock(t, 8) | |
| 180 | 190 | d.clock = clock.clock | |
| 181 | - | d.attachCoordinator(sess, nil, ac, true) | |
| 191 | + | rc := d.attachCoordinator(sess, nil, ac, true) | |
| 182 | 192 | ||
| 183 | 193 | d.sessWg.Add(1) | |
| 184 | 194 | go d.ptyReader(sess, sess.tabs[1], sess.tabs[1].focusedPane()) | |
| @@ -189,12 +199,12 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) { | |||
| 189 | 199 | sess.active = 1 | |
| 190 | 200 | sess.mu.Unlock() | |
| 191 | 201 | d.invalidateRender(sess, ac, true, "sync activation") | |
| 192 | - | fireCoordinatorTimer(t, drainCoordinatorTimers(clock), urgentRenderDeadline) | |
| 202 | + | fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline) | |
| 193 | 203 | requireNoCoordinatorOutputFrame(t, sends) | |
| 194 | 204 | ||
| 195 | 205 | inactiveSteps <- channelPTYStep{data: []byte(" complete\x1b[?2026l")} | |
| 196 | 206 | awaitPTYReadProcessed(t, inactiveProcessed) | |
| 197 | - | fireCoordinatorTimer(t, drainCoordinatorTimers(clock), urgentRenderDeadline) | |
| 207 | + | fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline) | |
| 198 | 208 | frame := awaitOutputFrameWithoutSleep(t, sends) | |
| 199 | 209 | output, err := ports.UnmarshalOutput(frame.Payload) | |
| 200 | 210 | require.NoError(t, err) | |
| @@ -214,7 +224,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) { | |||
| 214 | 224 | d, sess, ac, sends := newManualSessionWithPTYs(t, oldPTY, newPTY, parkedPTY) | |
| 215 | 225 | clock := newCoordinatorMockClock(t, 8) | |
| 216 | 226 | d.clock = clock.clock | |
| 217 | - | d.attachCoordinator(sess, nil, ac, true) | |
| 227 | + | rc := d.attachCoordinator(sess, nil, ac, true) | |
| 218 | 228 | ||
| 219 | 229 | d.sessWg.Add(2) | |
| 220 | 230 | go d.ptyReader(sess, sess.tabs[0], sess.tabs[0].focusedPane()) | |
| @@ -227,7 +237,7 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) { | |||
| 227 | 237 | sess.mu.Unlock() | |
| 228 | 238 | newSteps <- channelPTYStep{data: []byte("newly active")} | |
| 229 | 239 | awaitPTYReadProcessed(t, newProcessed) | |
| 230 | - | fireCoordinatorTimer(t, drainCoordinatorTimers(clock), minOutputRenderDeadline) | |
| 240 | + | fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), minOutputRenderDeadline) | |
| 231 | 241 | frame := awaitOutputFrameWithoutSleep(t, sends) | |
| 232 | 242 | output, err := ports.UnmarshalOutput(frame.Payload) | |
| 233 | 243 | require.NoError(t, err) | |
| @@ -275,7 +285,7 @@ func TestPTYReaderRepublishesSynchronizedCompletionAfterAttachmentLifecycle(t *t | |||
| 275 | 285 | steps <- channelPTYStep{data: []byte(" complete\x1b[?2026l")} | |
| 276 | 286 | awaitPTYReadProcessed(t, processed) | |
| 277 | 287 | ||
| 278 | - | fireCoordinatorTimer(t, drainCoordinatorTimers(clock), urgentRenderDeadline) | |
| 288 | + | fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), urgentRenderDeadline) | |
| 279 | 289 | wake := <-previews | |
| 280 | 290 | require.True(t, wake.urgent) | |
| 281 | 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 | 327 | awaitPTYReadProcessed(t, processed) | |
| 318 | 328 | requireNoWake(t, wakes) | |
| 319 | 329 | ||
| 320 | - | fireCoordinatorTimer(t, replacementTimers, urgentRenderDeadline) | |
| 330 | + | fireCoordinatorTimer(t, rc, replacementTimers, urgentRenderDeadline) | |
| 321 | 331 | wake := <-wakes | |
| 322 | 332 | require.True(t, wake.urgent) | |
| 323 | 333 | 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.clock | 190 | 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 = 1 | 199 | 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.clock | 226 | 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 := <-previews | 289 | 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 := <-wakes | 331 | 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") | ||