test(daemon): keep the real handshake budget in notifyClock (#302)
* test(daemon): keep the real handshake budget in notifyClock notifyClock shortened every non-debounce timer to 5ms, including the 15s handshake deadline. On a slow runner the handshake expired before Welcome, so TestServeReturnsDespiteWedgedClientOnShutdown timed out. Only the detach-notify timer is shortened now. * test(daemon): document notifyClock on its type
- commit
- b90b5f22cab856aecb1f7c9dcace04baece88e88
- author
- bnema · · 2026-09-29 18:31 UTC
- committer
- GitHub · · 2026-09-29 18:31 UTC
- parent
- 46d259b
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
2 files · +11 -5
M internal/usecase/daemon/daemon_test.go +11 -2
| @@ -661,14 +661,23 @@ func rowText(row []renderer.Cell) string { | |||
| 661 | 661 | return string(runes) | |
| 662 | 662 | } | |
| 663 | 663 | ||
| 664 | + | // notifyClock stubs debounceInterval so schedulers park, and shortens | |
| 665 | + | // detachNotifyTimeout to 5ms so a wedged client's Detached send is | |
| 666 | + | // force-closed quickly. Every other budget, including the handshake deadline, | |
| 667 | + | // runs at its real length. Timers are matched by duration value, so another | |
| 668 | + | // budget equal to one of these constants gets the same treatment. | |
| 664 | 669 | type notifyClock struct{} | |
| 665 | 670 | ||
| 666 | 671 | func (notifyClock) Now() time.Time { return time.Now() } | |
| 667 | 672 | func (notifyClock) NewTimer(d time.Duration) ports.Timer { | |
| 668 | - | if d == debounceInterval { | |
| 673 | + | switch d { | |
| 674 | + | case debounceInterval: | |
| 669 | 675 | return stubTimer{} | |
| 676 | + | case detachNotifyTimeout: | |
| 677 | + | return realTimer{t: time.NewTimer(5 * time.Millisecond)} | |
| 678 | + | default: | |
| 679 | + | return realTimer{t: time.NewTimer(d)} | |
| 670 | 680 | } | |
| 671 | - | return realTimer{t: time.NewTimer(5 * time.Millisecond)} | |
| 672 | 681 | } | |
| 673 | 682 | ||
| 674 | 683 | type realTimer struct{ t *time.Timer } | |
| @@ -661,14 +661,23 @@ func rowText(row []renderer.Cell) string { | |||||
| 661 | return string(runes) | 661 | return string(runes) | ||
| 662 | } | 662 | } | ||
| 663 | 663 | ||||
| 664 | + | // notifyClock stubs debounceInterval so schedulers park, and shortens | |||
| 665 | + | // detachNotifyTimeout to 5ms so a wedged client's Detached send is | |||
| 666 | + | // force-closed quickly. Every other budget, including the handshake deadline, | |||
| 667 | + | // runs at its real length. Timers are matched by duration value, so another | |||
| 668 | + | // budget equal to one of these constants gets the same treatment. | |||
| 664 | type notifyClock struct{} | 669 | type notifyClock struct{} | ||
| 665 | 670 | ||||
| 666 | func (notifyClock) Now() time.Time { return time.Now() } | 671 | func (notifyClock) Now() time.Time { return time.Now() } | ||
| 667 | func (notifyClock) NewTimer(d time.Duration) ports.Timer { | 672 | func (notifyClock) NewTimer(d time.Duration) ports.Timer { | ||
| 668 | - | if d == debounceInterval { | 673 | + | switch d { |
| 674 | + | case debounceInterval: | |||
| 669 | return stubTimer{} | 675 | return stubTimer{} | ||
| 676 | + | case detachNotifyTimeout: | |||
| 677 | + | return realTimer{t: time.NewTimer(5 * time.Millisecond)} | |||
| 678 | + | default: | |||
| 679 | + | return realTimer{t: time.NewTimer(d)} | |||
| 670 | } | 680 | } | ||
| 671 | - | return realTimer{t: time.NewTimer(5 * time.Millisecond)} | |||
| 672 | } | 681 | } | ||
| 673 | 682 | ||||
| 674 | type realTimer struct{ t *time.Timer } | 683 | type realTimer struct{ t *time.Timer } | ||
M internal/usecase/daemon/session_test.go +0 -3
| @@ -739,9 +739,6 @@ func TestHelloAfterExplicitShutdownIsRejected(t *testing.T) { | |||
| 739 | 739 | ||
| 740 | 740 | // --- wedged-client teardown --------------------------------------------------- | |
| 741 | 741 | ||
| 742 | - | // notifyClock drives the daemon with a stub debounce timer (schedulers park) | |
| 743 | - | // but a short real timer for the detach-notify deadline, so a wedged client's | |
| 744 | - | ||
| 745 | 742 | func TestServeReturnsDespiteWedgedClientOnShutdown(t *testing.T) { | |
| 746 | 743 | p, _ := newBlockingPTY(t) // Close unblocks the parked reader | |
| 747 | 744 | ||
| @@ -739,9 +739,6 @@ func TestHelloAfterExplicitShutdownIsRejected(t *testing.T) { | |||||
| 739 | 739 | ||||
| 740 | // --- wedged-client teardown --------------------------------------------------- | 740 | // --- wedged-client teardown --------------------------------------------------- | ||
| 741 | 741 | ||||
| 742 | - | // notifyClock drives the daemon with a stub debounce timer (schedulers park) | |||
| 743 | - | // but a short real timer for the detach-notify deadline, so a wedged client's | |||
| 744 | - | ||||
| 745 | func TestServeReturnsDespiteWedgedClientOnShutdown(t *testing.T) { | 742 | func TestServeReturnsDespiteWedgedClientOnShutdown(t *testing.T) { | ||
| 746 | p, _ := newBlockingPTY(t) // Close unblocks the parked reader | 743 | p, _ := newBlockingPTY(t) // Close unblocks the parked reader | ||
| 747 | 744 | ||||