main ece7682

resume_wake_incarnation_test.go RawHistoryFolder

1package daemon
2
3import (
4	"testing"
5
6	"github.com/stretchr/testify/require"
7
8	"github.com/bnema/vev/internal/domain"
9	"github.com/bnema/vev/internal/protocol"
10)
11
12// A wake can have passed coordinator scheduling before a link loss parks its
13// attachment. Resume deliberately reuses that attachment object, so pointer
14// identity alone must not let the old wake paint the new transport before its
15// Welcome frame.
16func TestParkedResumeRejectsDispatchedWakeFromPriorAttachment(t *testing.T) {
17	pty, releasePTY := newBlockingPTY(t)
18	defer releasePTY()
19	d := newTestDaemon(t, newFactorySeq(t, pty), stubClock{})
20
21	oldTransport := &closeTrackingTransport{}
22	sess, ac, err := d.route(helloResumeCapable(protocol.IntentNew, "work", 0), oldTransport)
23	require.NoError(t, err)
24	token := ac.resumeToken
25	rc := sess.renderCoordinator()
26	require.NotNil(t, rc)
27	require.True(t, rc.markAttachmentReady(rc.attachmentLease(ac)))
28
29	// Pause after dispatch but before the coordinator's normal wake callback
30	// reaches paint/sendMu. This is the precise stale-wake window.
31	originalWake := rc.opts.wake
32	wakeDispatched := make(chan struct{})
33	releaseWake := make(chan struct{})
34	rc.opts.wake = func(w renderWake) {
35		close(wakeDispatched)
36		<-releaseWake
37		originalWake(w)
38	}
39	wakeDone := make(chan struct{})
40	go func() {
41		d.invalidateRenderNow(sess, ac, true, "resume-wake-test")
42		close(wakeDone)
43	}()
44	<-wakeDispatched
45
46	d.clientGone(sess, ac, oldTransport, false)
47	newTransport := &closeTrackingTransport{}
48	resumedSess, resumedAC, ok, err := d.resumeParked(helloResumeCapable(protocol.IntentResume, sess.name, token), newTransport, domain.Size{Cols: 80, Rows: 24})
49	require.NoError(t, err)
50	require.True(t, ok)
51	require.Same(t, sess, resumedSess)
52	require.Same(t, ac, resumedAC)
53
54	close(releaseWake)
55	<-wakeDone
56	require.Empty(t, newTransport.Sends(), "a stale pre-resume wake must not emit Output before Welcome")
57	ac.sendMu.Lock()
58	staleWakeComposed := ac.render.cache.valid
59	ac.sendMu.Unlock()
60	require.False(t, staleWakeComposed, "a stale wake must not mutate the resumed attachment shadow")
61
62	// The new attachment incarnation accepts the required paint only after its
63	// replacement transport has completed Welcome.
64	rc.opts.wake = originalWake
65	require.True(t, rc.markAttachmentReady(rc.attachmentLease(ac)))
66	d.firstPaint(sess, ac)
67	require.NotEmpty(t, newTransport.Sends())
68}
69
70func TestSameAttachmentRebindRejectsQueuedWakeAndFansOutFreshGeneration(t *testing.T) {
71	d, sess, ac, sends := newManualSessionWithPTYs(t, newQuietPTY())
72	rc := d.attachCoordinator(sess, nil, ac, true)
73	oldLease := rc.attachmentLease(ac)
74	require.NotNil(t, oldLease)
75
76	originalWake := rc.opts.wake
77	wakeDispatched := make(chan struct{})
78	releaseWake := make(chan struct{})
79	rc.opts.wake = func(w renderWake) {
80		close(wakeDispatched)
81		<-releaseWake
82		originalWake(w)
83	}
84	wakeDone := make(chan struct{})
85	go func() {
86		d.invalidateRenderNow(sess, ac, true, "same-attachment-rebind")
87		close(wakeDone)
88	}()
89	<-wakeDispatched
90
91	oldGeneration := ac.lifecycle.generationValue()
92	result, err := d.transitionAttachment(attachmentTransitionRequest{
93		source: sess, target: sess, next: ac,
94
95		expectedTransport: ac.transportSnapshot(), ready: true,
96	})
97	require.NoError(t, err)
98	d.deferAttachmentTransitionCleanups(result)
99	require.Greater(t, ac.lifecycle.generationValue(), oldGeneration)
100	freshLease := rc.attachmentLease(ac)
101	require.NotSame(t, oldLease, freshLease)
102
103	close(releaseWake)
104	<-wakeDone
105	require.Empty(t, sends, "a queued wake from the prior generation must not paint the rebound transport")
106
107	rc.opts.wake = originalWake
108	d.invalidateRenderNow(sess, ac, true, "fresh-generation-fanout")
109	require.Greater(t, len(sends), 0, "the current generation must receive fanout output")
110}

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