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}