1package neferclient_test
2
3import (
4 "context"
5 "testing"
6 "time"
7
8 "github.com/stretchr/testify/mock"
9 "github.com/stretchr/testify/require"
10
11 "github.com/bnema/neferclient"
12 neferclientmocks "github.com/bnema/neferclient/mocks"
13)
14
15func connectHeadless(t *testing.T) *neferclient.Conn {
16 t.Helper()
17 socket := startHeadless(t)
18 ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
19 defer cancel()
20 for {
21 c, err := neferclient.Connect(ctx, socket)
22 if err == nil {
23 t.Cleanup(func() { _ = c.Close() })
24 for len(c.Outputs()) == 0 { // the virtual output may arrive late
25 select {
26 case <-c.Wake():
27 require.NoError(t, c.Dispatch(nil))
28 case <-ctx.Done():
29 t.Fatal("no output announced")
30 }
31 }
32 return c
33 }
34 if ctx.Err() != nil {
35 t.Fatal(err)
36 }
37 time.Sleep(5 * time.Millisecond)
38 }
39}
40
41func TestHeadlessLayerSurface(t *testing.T) {
42 c := connectHeadless(t)
43 s, err := c.NewLayerSurface(neferclient.LayerConfig{
44 Level: neferclient.LayerTop, Anchors: neferclient.AnchorTop | neferclient.AnchorLeft,
45 Width: 200, Height: 50,
46 })
47 require.NoError(t, err)
48
49 var configured, feedback bool
50 var cw, ch int32
51 h := neferclientmocks.NewMockHandler(t)
52 h.EXPECT().Configure(s.ID(), mock.Anything, mock.Anything).Run(func(_ neferclient.SurfaceID, w, hh int32) {
53 configured, cw, ch = true, w, hh
54 }).Return().Maybe()
55 h.EXPECT().FeedbackDone(s.ID()).Run(func(neferclient.SurfaceID) { feedback = true }).Return().Maybe()
56 h.EXPECT().Scale(s.ID(), mock.Anything).Return().Maybe()
57 dispatchUntil(t, c, h, func() bool { return configured && feedback })
58
59 require.Equal(t, int32(200), cw)
60 require.Equal(t, int32(50), ch)
61 fb := s.Feedback()
62 require.NotNil(t, fb)
63 require.NotZero(t, fb.MainDevice)
64 require.NotEmpty(t, fb.Formats)
65 t.Logf("main device %#x, %d formats", fb.MainDevice, len(fb.Formats))
66 pw, ph, err := s.PhysicalSize()
67 require.NoError(t, err)
68 require.Positive(t, pw)
69 require.Positive(t, ph)
70 require.NoError(t, s.SetInputRegion([]neferclient.Rect{{X: 0, Y: 0, Width: 10, Height: 10}}))
71 require.NoError(t, s.Close())
72 require.NoError(t, c.Roundtrip())
73}
74
75func TestHeadlessLockUnlock(t *testing.T) {
76 c := connectHeadless(t)
77 l, err := c.Lock()
78 require.NoError(t, err)
79 require.Error(t, l.Unlock(), "unlock before locked must be refused")
80
81 var locked bool
82 var surfaces []*neferclient.Surface
83 for _, o := range c.Outputs() {
84 s, err := l.NewSurface(o.Global)
85 require.NoError(t, err)
86 surfaces = append(surfaces, s)
87 }
88 h := neferclientmocks.NewMockHandler(t)
89 h.EXPECT().Locked().Run(func() { locked = true }).Return().Maybe()
90 h.EXPECT().Configure(mock.Anything, mock.Anything, mock.Anything).Return().Maybe()
91 h.EXPECT().FeedbackDone(mock.Anything).Return().Maybe()
92 h.EXPECT().Scale(mock.Anything, mock.Anything).Return().Maybe()
93 dispatchUntil(t, c, h, func() bool { return locked })
94 require.True(t, l.Locked())
95 for _, s := range surfaces {
96 w, hh, _ := s.Size()
97 t.Logf("lock surface %d: %dx%d", s.ID(), w, hh)
98 }
99 require.Error(t, l.Close(), "a locked session is not closed, only unlocked")
100 require.NoError(t, l.Unlock())
101 require.False(t, l.Locked())
102 for _, s := range surfaces {
103 require.NoError(t, s.Close())
104 }
105 require.NoError(t, c.Roundtrip())
106}
107
108// TestHeadlessSeat binds the seat against a real compositor and drives it
109// through a surface: the seat bind, capability handling and cursor request
110// must not fail, whatever devices the headless backend offers.
111func TestHeadlessSeat(t *testing.T) {
112 c := connectHeadless(t)
113 s, err := c.NewLayerSurface(neferclient.LayerConfig{
114 Level: neferclient.LayerTop, Anchors: neferclient.AnchorTop | neferclient.AnchorLeft,
115 Width: 100, Height: 40, Keyboard: neferclient.KeyboardOnDemand,
116 })
117 require.NoError(t, err)
118 seat := c.Seat()
119 require.NotNil(t, seat)
120 seat.SetSecret(neferclient.NewSecretBuffer(16))
121 seat.SetSecret(nil)
122
123 var configured bool
124 h := neferclientmocks.NewMockHandler(t)
125 h.EXPECT().Configure(s.ID(), mock.Anything, mock.Anything).Run(func(neferclient.SurfaceID, int32, int32) { configured = true }).Return().Maybe()
126 h.EXPECT().FeedbackDone(mock.Anything).Return().Maybe()
127 h.EXPECT().Scale(mock.Anything, mock.Anything).Return().Maybe()
128 h.EXPECT().Pointer(mock.Anything).Return().Maybe()
129 h.EXPECT().Key(mock.Anything).Return().Maybe()
130 h.EXPECT().KeyboardFocus(mock.Anything, mock.Anything).Return().Maybe()
131 h.EXPECT().SecretChanged(mock.Anything).Return().Maybe()
132 dispatchUntil(t, c, h, func() bool { return configured })
133 require.NoError(t, c.Roundtrip())
134 require.NoError(t, c.Dispatch(h))
135 require.NoError(t, seat.SetCursor(neferclient.CursorPointer))
136 require.NoError(t, s.Close())
137}