1package wayland
2
3import (
4 "context"
5 "testing"
6 "time"
7
8 "github.com/bnema/neferwl/internal/adapters/sessionsecurity"
9 "github.com/bnema/neferwl/internal/logging"
10 "github.com/bnema/neferwl/internal/ports"
11 "github.com/bnema/wlturbo"
12 lockclient "github.com/bnema/wlturbo/protocol/extsessionlock"
13)
14
15func lockManagerServer(t *testing.T) (*Server, *sessionsecurity.Gate, chan ports.SecurityState, chan ports.SecurityBackendEvent, string) {
16 t.Helper()
17 gate := &sessionsecurity.Gate{}
18 changes := make(chan ports.SecurityState, 1)
19 events := make(chan ports.SecurityBackendEvent, 16)
20 dir := t.TempDir()
21 s, err := New(Options{RuntimeDir: dir, Outputs: testOutputs, Security: gate}, Channels{SecurityChanges: changes, SecurityEvents: events}, logging.For(context.Background(), "wayland"))
22 if err != nil {
23 t.Fatal(err)
24 }
25 ctx, cancel := context.WithCancel(context.Background())
26 done := make(chan error, 1)
27 go func() { done <- s.Run(ctx) }()
28 t.Cleanup(func() {
29 cancel()
30 select {
31 case err := <-done:
32 if err != nil {
33 t.Error(err)
34 }
35 case <-time.After(3 * time.Second):
36 t.Error("session-lock server did not stop")
37 }
38 })
39 return s, gate, changes, events, dir
40}
41
42func bindLockManager(t *testing.T, c *wlturbo.Display) *lockclient.ExtSessionLockManager {
43 t.Helper()
44 manager := lockclient.NewExtSessionLockManager(c.Context())
45 g, ok := c.Registry().FindGlobal(lockclient.ExtSessionLockManagerInterface)
46 if !ok {
47 t.Fatal("missing test lock manager")
48 }
49 if err := c.Registry().Bind(g.Name, g.Interface, 1, manager); err != nil {
50 t.Fatal(err)
51 }
52 return manager
53}
54
55func TestSessionLockWaitsForEveryBarrierAndOwnerDeathStaysProtected(t *testing.T) {
56 s, gate, changes, _, dir := lockManagerServer(t)
57 c := protocolClient(t, s, dir)
58 manager := bindLockManager(t, c)
59 lock, err := manager.Lock()
60 if err != nil {
61 t.Fatal(err)
62 }
63 locked := 0
64 lock.OnLocked(func() { locked++ })
65 if err := c.Roundtrip(); err != nil {
66 t.Fatal(err)
67 }
68 var state ports.SecurityState
69 select {
70 case state = <-changes:
71 case <-time.After(2 * time.Second):
72 t.Fatal("no backend transition")
73 }
74 if !state.Protected || !gate.Snapshot().Protected || locked != 0 {
75 t.Fatalf("early lock %+v events %d", state, locked)
76 }
77 if !s.display.Do(func() {
78 s.applySecurityEvent(ports.SecurityOutputAdded{Instance: 1, Output: "OUT-1"})
79 s.applySecurityEvent(ports.SecurityOutputAdded{Instance: 2, Output: "OUT-2"})
80 s.applySecurityEvent(ports.SecurityBackendBarrier{Generation: state.Generation})
81 s.applySecurityEvent(ports.SecurityOutputProof{Proof: ports.OutputProtection{Generation: state.Generation, Instance: 1, Kind: ports.ProtectionProtectedFrame}})
82 }) {
83 t.Fatal("display stopped")
84 }
85 if err := c.Roundtrip(); err != nil {
86 t.Fatal(err)
87 }
88 if locked != 0 {
89 t.Fatal("locked without second output proof")
90 }
91 if !s.display.Do(func() {
92 s.applySecurityEvent(ports.SecurityOutputProof{Proof: ports.OutputProtection{Generation: state.Generation, Instance: 2, Kind: ports.ProtectionInactiveOutput}})
93 }) {
94 t.Fatal("display stopped")
95 }
96 if err := c.Roundtrip(); err != nil {
97 t.Fatal(err)
98 }
99 if locked != 1 {
100 t.Fatalf("locked events %d, want 1", locked)
101 }
102 // Closing the owner connection never opens the session.
103 if err := c.Close(); err != nil {
104 t.Fatal(err)
105 }
106 if !s.display.Do(func() {}) {
107 t.Fatal("display stopped")
108 }
109 if !gate.Snapshot().Protected {
110 t.Fatal("owner death unlocked")
111 }
112}