attachment_lifecycle_test.go RawHistoryFolder

1package daemon
2
3import (
4	"testing"
5
6	"github.com/stretchr/testify/require"
7)
8
9func (ac *attachedClient) installTestAttachmentCapability(capability attachmentCapability) {
10	ac.lifecycle.mu.Lock()
11	defer ac.lifecycle.mu.Unlock()
12	ac.lifecycle.initLocked()
13	if ac.lifecycle.phase != attachmentEffectsStable || ac.lifecycle.inFlight != 0 {
14		panic("daemon: installing an active test attachment capability")
15	}
16	ac.lifecycle.capability = capability
17	ac.lifecycle.failedTransport = transportSnapshot{}
18}
19
20func (s *session) detachIfCurrent(ac *attachedClient) bool {
21	s.mu.Lock()
22	defer s.mu.Unlock()
23	if !attachmentRegisteredLocked(s, ac) || ac.currentAttachmentSession() != s {
24		return false
25	}
26	s.unregisterAttachmentLocked(ac)
27	ac.setSession(nil)
28	ac.invalidateRetiredAttachmentCapability()
29	return true
30}
31
32func TestAttachmentCapabilityRejectsIndependentIdentityChanges(t *testing.T) {
33	tests := []struct {
34		name   string
35		change func(*session, *attachedClient, *renderCoordinator)
36	}{
37		{
38			name: "session membership removal",
39			change: func(sess *session, ac *attachedClient, _ *renderCoordinator) {
40				sess.mu.Lock()
41				delete(sess.attachments, ac)
42				sess.mu.Unlock()
43			},
44		},
45		{
46			name: "attachment session replacement",
47			change: func(_ *session, ac *attachedClient, _ *renderCoordinator) {
48				ac.setSession(&session{})
49			},
50		},
51		{
52			name: "connection generation increment",
53			change: func(_ *session, ac *attachedClient, _ *renderCoordinator) {
54				ac.lifecycle.generation.Add(1)
55			},
56		},
57		{
58			name: "transport replacement",
59			change: func(_ *session, ac *attachedClient, _ *renderCoordinator) {
60				ac.replaceTransport(&closeTrackingTransport{})
61			},
62		},
63		{
64			name: "transport incarnation increment",
65			change: func(_ *session, ac *attachedClient, _ *renderCoordinator) {
66				ac.replaceTransport(ac.transport())
67			},
68		},
69		{
70			name: "render lease rebind",
71			change: func(_ *session, ac *attachedClient, rc *renderCoordinator) {
72				rc.mu.Lock()
73				rc.rebindAttachmentWithReadinessLocked(ac, true)
74				rc.mu.Unlock()
75			},
76		},
77	}
78
79	for _, tt := range tests {
80		t.Run(tt.name, func(t *testing.T) {
81			d, sess, ac, _ := newManualSessionWithPTYs(t, newQuietPTY())
82			transport := &closeTrackingTransport{}
83			ac.replaceTransport(transport)
84			rc := d.attachCoordinator(sess, nil, ac, true)
85			capability := sess.captureAttachmentCapability(ac, transport)
86			capability.lease = rc.attachmentLease(ac)
87			ac.installTestAttachmentCapability(capability)
88
89			require.True(t, capability.current())
90			tt.change(sess, ac, rc)
91			require.False(t, capability.current())
92			effect, admitted := ac.beginAttachmentEffect(capability)
93			require.False(t, admitted)
94			require.Nil(t, effect)
95		})
96	}
97}
98
99func TestAttachmentCapabilityInvalidationRejectsNewEffects(t *testing.T) {
100	d, sess, ac, _ := newManualSessionWithPTYs(t, newQuietPTY())
101	transport := &closeTrackingTransport{}
102	ac.replaceTransport(transport)
103	rc := d.attachCoordinator(sess, nil, ac, true)
104	capability := sess.captureAttachmentCapability(ac, transport)
105	capability.lease = rc.attachmentLease(ac)
106	ac.installTestAttachmentCapability(capability)
107
108	frozen := freezeAttachmentEffectGates(ac)
109	require.True(t, frozen.acquired)
110	require.True(t, frozen.drained)
111	ac.invalidateFrozenAttachmentCapability()
112	frozen.unfreeze()
113
114	require.False(t, capability.current())
115	_, admitted := ac.beginAttachmentEffect(capability)
116	require.False(t, admitted)
117}

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