detached 71357f8

test: cover present ordering, feedback parsing, lock rules, headless surfaces and present allocations

commit
71357f814bd2a0ea7300dcab04fbd8a9364e35c0
author
brice · · 2026-10-01 16:36 UTC
parent
d08c2b9
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
9 files · +1053 -44
M .mockery.yml +5 -0
@@ -13,3 +13,8 @@ packages:
1313 filename: '{{.InterfaceFile | base | trimSuffix ".go"}}_mock.go'
1414 interfaces:
1515 Handler:
16+ surfaceRequests:
17+ config:
18+ dir: .
19+ pkgname: neferclient
20+ filename: surface_mock_test.go
@@ -13,3 +13,8 @@ packages:
13 filename: '{{.InterfaceFile | base | trimSuffix ".go"}}_mock.go'13 filename: '{{.InterfaceFile | base | trimSuffix ".go"}}_mock.go'
14 interfaces:14 interfaces:
15 Handler:15 Handler:
16+ surfaceRequests:
17+ config:
18+ dir: .
19+ pkgname: neferclient
20+ filename: surface_mock_test.go
M alloc_test.go +88 -3
@@ -1,9 +1,11 @@
11 package neferclient_test
22
33 import (
4+ "encoding/binary"
45 "runtime"
56 "testing"
67
8+ "github.com/bnema/go-wayland-bindings/client/wayland"
79 "github.com/stretchr/testify/require"
810 "golang.org/x/sys/unix"
911
@@ -14,11 +16,12 @@ import (
1416 // only counts, so the test sees the cost of the library alone.
1517 type countingHandler struct {
1618 neferclient.NopHandler
17- fdReady, errs int
19+ fdReady, errs, frames int
1820 }
1921
20-func (h *countingHandler) FDReady(uint64) { h.fdReady++ }
21-func (h *countingHandler) Error(error) { h.errs++ }
22+func (h *countingHandler) FDReady(uint64) { h.fdReady++ }
23+func (h *countingHandler) Error(error) { h.errs++ }
24+func (h *countingHandler) Frame(neferclient.SurfaceID) { h.frames++ }
2225
2326 // TestAllocDispatch measures the whole steady-state pipeline over a real
2427 // socketpair: wire frames in, reader decode, queue, Dispatch (output state
@@ -65,3 +68,85 @@ func TestAllocDispatch(t *testing.T) {
6568 require.Positive(t, h.fdReady)
6669 require.Zero(t, h.errs)
6770 }
71+
72+// TestAllocPresent measures the steady-state present loop over a real
73+// socketpair: Present (all requests, including the frame callback), the peer
74+// answering the callback, reader decode, queue and Dispatch applying Frame.
75+// The peer parses and discards everything else.
76+func TestAllocPresent(t *testing.T) {
77+ skipUnderRace(t)
78+ globals := []wireGlobal{{1, wayland.CompositorInterface, 6}, {2, "zwp_linux_dmabuf_v1", 4},
79+ {3, "wp_linux_drm_syncobj_manager_v1", 1}, {4, "wp_viewporter", 1}, {5, "xdg_wm_base", 6}}
80+ c, srv := connectWire(t, globals, nil)
81+ s, err := c.NewToplevel("alloc", 64, 48)
82+ require.NoError(t, err)
83+ s.MarkPresentable(64, 48)
84+
85+ surfaceID := s.SurfaceObjectID()
86+ srv.mu.Lock()
87+ srv.hook = func(obj uint32, op uint16, body []byte) {
88+ if obj == surfaceID && op == 3 && len(body) == 4 { // wl_surface.frame(new_id)
89+ srv.write(frame(binary.LittleEndian.Uint32(body), 0, appendU32(nil, 1)))
90+ srv.write(frame(1, 1, body)) // wl_display.delete_id: the id is recycled
91+ }
92+ }
93+ srv.mu.Unlock()
94+
95+ memfd, err := unix.MemfdCreate("alloc", unix.MFD_CLOEXEC)
96+ require.NoError(t, err)
97+ defer unix.Close(memfd)
98+ require.NoError(t, s.ImportBuffer(1, &neferclient.Buffer{Width: 64, Height: 48, FourCC: 0x34325258,
99+ Planes: [4]neferclient.Plane{{FD: memfd, Stride: 256}}, PlaneCount: 1}))
100+ require.NoError(t, s.ImportTimeline(1, memfd))
101+ require.NoError(t, s.ImportTimeline(2, memfd))
102+
103+ var point uint64
104+ damage := []neferclient.Rect{{X: 0, Y: 0, Width: 16, Height: 16}}
105+ present := func() {
106+ point++
107+ if err := s.Present(&neferclient.Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2,
108+ AcquirePoint: point, ReleasePoint: point, Damage: damage, Opaque: true}); err != nil {
109+ t.Fatal(err)
110+ }
111+ }
112+
113+ // The request path: every request Present sends, over the real socket,
114+ // without the frame callback.
115+ s.SkipFrameRequests()
116+ requests := func() {
117+ s.ResetFrame()
118+ present()
119+ }
120+ for range 4 { // warm-up beyond AllocsPerRun's own
121+ requests()
122+ }
123+ allocs := testing.AllocsPerRun(100, requests)
124+ t.Logf("allocs per Present (requests only): %v", allocs)
125+ require.Zero(t, allocs)
126+
127+ // The complete cycle adds the frame callback. wlturbo registers each
128+ // server-created wl_callback with two sync.Map stores (Context.proxies and
129+ // Display.objects) and a sync.Map store always allocates a node, so two
130+ // allocations per frame are inherent to the transport (wlturbo is out of
131+ // scope for this change). They are pinned so a regression above that shows.
132+ s.UseWireRequests()
133+ h := &countingHandler{}
134+ cycle := func() {
135+ present()
136+ for c.QueueLen() < 1 {
137+ runtime.Gosched()
138+ }
139+ if err := c.Dispatch(h); err != nil {
140+ t.Fatal(err)
141+ }
142+ }
143+ s.ResetFrame()
144+ for range 4 {
145+ cycle()
146+ }
147+ full := testing.AllocsPerRun(100, cycle)
148+ t.Logf("allocs per Present+frame round trip: %v (wlturbo callback registration)", full)
149+ require.LessOrEqual(t, full, 2.0)
150+ require.Positive(t, h.frames)
151+ require.Zero(t, h.errs)
152+}
@@ -1,9 +1,11 @@
1 package neferclient_test1 package neferclient_test
2 2
3 import (3 import (
4+ "encoding/binary"
4 "runtime"5 "runtime"
5 "testing"6 "testing"
6 7
8+ "github.com/bnema/go-wayland-bindings/client/wayland"
7 "github.com/stretchr/testify/require"9 "github.com/stretchr/testify/require"
8 "golang.org/x/sys/unix"10 "golang.org/x/sys/unix"
9 11
@@ -14,11 +16,12 @@ import (
14 // only counts, so the test sees the cost of the library alone.16 // only counts, so the test sees the cost of the library alone.
15 type countingHandler struct {17 type countingHandler struct {
16 neferclient.NopHandler18 neferclient.NopHandler
17- fdReady, errs int19+ fdReady, errs, frames int
18 }20 }
19 21
20-func (h *countingHandler) FDReady(uint64) { h.fdReady++ }22+func (h *countingHandler) FDReady(uint64) { h.fdReady++ }
21-func (h *countingHandler) Error(error) { h.errs++ }23+func (h *countingHandler) Error(error) { h.errs++ }
24+func (h *countingHandler) Frame(neferclient.SurfaceID) { h.frames++ }
22 25
23 // TestAllocDispatch measures the whole steady-state pipeline over a real26 // TestAllocDispatch measures the whole steady-state pipeline over a real
24 // socketpair: wire frames in, reader decode, queue, Dispatch (output state27 // socketpair: wire frames in, reader decode, queue, Dispatch (output state
@@ -65,3 +68,85 @@ func TestAllocDispatch(t *testing.T) {
65 require.Positive(t, h.fdReady)68 require.Positive(t, h.fdReady)
66 require.Zero(t, h.errs)69 require.Zero(t, h.errs)
67 }70 }
71+
72+// TestAllocPresent measures the steady-state present loop over a real
73+// socketpair: Present (all requests, including the frame callback), the peer
74+// answering the callback, reader decode, queue and Dispatch applying Frame.
75+// The peer parses and discards everything else.
76+func TestAllocPresent(t *testing.T) {
77+ skipUnderRace(t)
78+ globals := []wireGlobal{{1, wayland.CompositorInterface, 6}, {2, "zwp_linux_dmabuf_v1", 4},
79+ {3, "wp_linux_drm_syncobj_manager_v1", 1}, {4, "wp_viewporter", 1}, {5, "xdg_wm_base", 6}}
80+ c, srv := connectWire(t, globals, nil)
81+ s, err := c.NewToplevel("alloc", 64, 48)
82+ require.NoError(t, err)
83+ s.MarkPresentable(64, 48)
84+
85+ surfaceID := s.SurfaceObjectID()
86+ srv.mu.Lock()
87+ srv.hook = func(obj uint32, op uint16, body []byte) {
88+ if obj == surfaceID && op == 3 && len(body) == 4 { // wl_surface.frame(new_id)
89+ srv.write(frame(binary.LittleEndian.Uint32(body), 0, appendU32(nil, 1)))
90+ srv.write(frame(1, 1, body)) // wl_display.delete_id: the id is recycled
91+ }
92+ }
93+ srv.mu.Unlock()
94+
95+ memfd, err := unix.MemfdCreate("alloc", unix.MFD_CLOEXEC)
96+ require.NoError(t, err)
97+ defer unix.Close(memfd)
98+ require.NoError(t, s.ImportBuffer(1, &neferclient.Buffer{Width: 64, Height: 48, FourCC: 0x34325258,
99+ Planes: [4]neferclient.Plane{{FD: memfd, Stride: 256}}, PlaneCount: 1}))
100+ require.NoError(t, s.ImportTimeline(1, memfd))
101+ require.NoError(t, s.ImportTimeline(2, memfd))
102+
103+ var point uint64
104+ damage := []neferclient.Rect{{X: 0, Y: 0, Width: 16, Height: 16}}
105+ present := func() {
106+ point++
107+ if err := s.Present(&neferclient.Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2,
108+ AcquirePoint: point, ReleasePoint: point, Damage: damage, Opaque: true}); err != nil {
109+ t.Fatal(err)
110+ }
111+ }
112+
113+ // The request path: every request Present sends, over the real socket,
114+ // without the frame callback.
115+ s.SkipFrameRequests()
116+ requests := func() {
117+ s.ResetFrame()
118+ present()
119+ }
120+ for range 4 { // warm-up beyond AllocsPerRun's own
121+ requests()
122+ }
123+ allocs := testing.AllocsPerRun(100, requests)
124+ t.Logf("allocs per Present (requests only): %v", allocs)
125+ require.Zero(t, allocs)
126+
127+ // The complete cycle adds the frame callback. wlturbo registers each
128+ // server-created wl_callback with two sync.Map stores (Context.proxies and
129+ // Display.objects) and a sync.Map store always allocates a node, so two
130+ // allocations per frame are inherent to the transport (wlturbo is out of
131+ // scope for this change). They are pinned so a regression above that shows.
132+ s.UseWireRequests()
133+ h := &countingHandler{}
134+ cycle := func() {
135+ present()
136+ for c.QueueLen() < 1 {
137+ runtime.Gosched()
138+ }
139+ if err := c.Dispatch(h); err != nil {
140+ t.Fatal(err)
141+ }
142+ }
143+ s.ResetFrame()
144+ for range 4 {
145+ cycle()
146+ }
147+ full := testing.AllocsPerRun(100, cycle)
148+ t.Logf("allocs per Present+frame round trip: %v (wlturbo callback registration)", full)
149+ require.LessOrEqual(t, full, 2.0)
150+ require.Positive(t, h.frames)
151+ require.Zero(t, h.errs)
152+}
M export_test.go +18 -0
@@ -15,3 +15,21 @@ func ConnectConn(ctx context.Context, sock net.Conn) (*Conn, error) { return con
1515 func SocketPath(name string) (string, error) { return socketPath(name) }
1616
1717 func (c *Conn) QueueLen() int { return c.q.length() }
18+
19+// MarkPresentable stands in for the configure and feedback rounds, which the
20+// allocation test does not drive over the wire.
21+func (s *Surface) MarkPresentable(w, h int32) {
22+ s.width, s.height = w, h
23+ s.configured, s.frameReady, s.fbDone = true, true, true
24+}
25+
26+func (s *Surface) SurfaceObjectID() uint32 { return s.surf.ID() }
27+
28+// SkipFrameRequests makes Present omit wl_surface.frame.
29+func (s *Surface) SkipFrameRequests() { s.req = wireRequests{s: s, skipFrame: true} }
30+
31+// ResetFrame re-arms Present when frame requests are skipped.
32+func (s *Surface) ResetFrame() { s.frameReady = true }
33+
34+// UseWireRequests restores the full request path.
35+func (s *Surface) UseWireRequests() { s.req = wireRequests{s: s} }
@@ -15,3 +15,21 @@ func ConnectConn(ctx context.Context, sock net.Conn) (*Conn, error) { return con
15 func SocketPath(name string) (string, error) { return socketPath(name) }15 func SocketPath(name string) (string, error) { return socketPath(name) }
16 16
17 func (c *Conn) QueueLen() int { return c.q.length() }17 func (c *Conn) QueueLen() int { return c.q.length() }
18+
19+// MarkPresentable stands in for the configure and feedback rounds, which the
20+// allocation test does not drive over the wire.
21+func (s *Surface) MarkPresentable(w, h int32) {
22+ s.width, s.height = w, h
23+ s.configured, s.frameReady, s.fbDone = true, true, true
24+}
25+
26+func (s *Surface) SurfaceObjectID() uint32 { return s.surf.ID() }
27+
28+// SkipFrameRequests makes Present omit wl_surface.frame.
29+func (s *Surface) SkipFrameRequests() { s.req = wireRequests{s: s, skipFrame: true} }
30+
31+// ResetFrame re-arms Present when frame requests are skipped.
32+func (s *Surface) ResetFrame() { s.frameReady = true }
33+
34+// UseWireRequests restores the full request path.
35+func (s *Surface) UseWireRequests() { s.req = wireRequests{s: s} }
A headless_surface_test.go +106 -0
@@ -0,0 +1,106 @@
1+package neferclient_test
2+
3+import (
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+
15+func 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+
41+func 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+
75+func 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+}
@@ -0,0 +1,106 @@
1+package neferclient_test
2+
3+import (
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+
15+func 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+
41+func 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+
75+func 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+}
M present.go +82 -38
@@ -133,94 +133,138 @@ func (s *Surface) Present(p *Present) error {
133133 if !s.frameReady {
134134 return errors.New("neferclient: frame callback pending")
135135 }
136- buf, ok := s.buffers[p.Buffer]
137- if !ok {
136+ if _, ok := s.buffers[p.Buffer]; !ok {
138137 return fmt.Errorf("neferclient: unknown buffer %d", p.Buffer)
139138 }
140- acq, ok := s.timelines[p.AcquireTimeline]
141- if !ok {
142- return fmt.Errorf("neferclient: unknown timeline %d", p.AcquireTimeline)
143- }
144- rel, ok := s.timelines[p.ReleaseTimeline]
145- if !ok {
146- return fmt.Errorf("neferclient: unknown timeline %d", p.ReleaseTimeline)
139+ for _, id := range [...]uint64{p.AcquireTimeline, p.ReleaseTimeline} {
140+ if _, ok := s.timelines[id]; !ok {
141+ return fmt.Errorf("neferclient: unknown timeline %d", id)
142+ }
147143 }
148144 pw, ph, err := s.PhysicalSize()
149145 if err != nil {
150146 return err
151147 }
148+ r := s.req
152149 if s.viewport != nil {
153150 if s.sentDestW != s.width || s.sentDestH != s.height {
154- if err = s.viewport.SetDestination(s.width, s.height); err != nil {
151+ if err = r.setDestination(s.width, s.height); err != nil {
155152 return err
156153 }
157154 s.sentDestW, s.sentDestH = s.width, s.height
158155 }
159156 } else if s.scale > 1 && s.sentBufScale != int32(s.scale) {
160- if err = s.surf.SetBufferScale(int32(s.scale)); err != nil {
157+ if err = r.setBufferScale(int32(s.scale)); err != nil {
161158 return err
162159 }
163160 s.sentBufScale = int32(s.scale)
164161 }
165- if err = s.setOpaque(p.Opaque); err != nil {
166- return err
162+ if p.Opaque {
163+ if !s.opaqueSet || s.opaqueW != s.width || s.opaqueH != s.height {
164+ if err = r.setOpaqueRegion(s.width, s.height, true); err != nil {
165+ return err
166+ }
167+ s.opaqueSet, s.opaqueW, s.opaqueH = true, s.width, s.height
168+ }
169+ } else if s.opaqueSet {
170+ if err = r.setOpaqueRegion(0, 0, false); err != nil {
171+ return err
172+ }
173+ s.opaqueSet = false
167174 }
168- if err = s.sync.SetAcquirePoint(acq, uint32(p.AcquirePoint>>32), uint32(p.AcquirePoint)); err != nil {
175+ if err = r.setAcquire(p.AcquireTimeline, p.AcquirePoint); err != nil {
169176 return err
170177 }
171- if err = s.sync.SetReleasePoint(rel, uint32(p.ReleasePoint>>32), uint32(p.ReleasePoint)); err != nil {
178+ if err = r.setRelease(p.ReleaseTimeline, p.ReleasePoint); err != nil {
172179 return err
173180 }
174- if err = s.surf.Attach(buf, 0, 0); err != nil {
181+ if err = r.attach(p.Buffer); err != nil {
175182 return err
176183 }
177184 if len(p.Damage) == 0 {
178- err = s.surf.DamageBuffer(0, 0, pw, ph)
185+ err = r.damageBuffer(Rect{0, 0, pw, ph})
179186 }
180- for _, r := range p.Damage {
187+ for _, d := range p.Damage {
181188 if err != nil {
182189 break
183190 }
184- err = s.surf.DamageBuffer(r.X, r.Y, r.Width, r.Height)
191+ err = r.damageBuffer(d)
185192 }
186193 if err != nil {
187194 return err
188195 }
189- // The one frame callback object is reused: only one is ever pending.
190- if err = s.c.wlctx.RequestArgs(wl.Request{Proxy: s.surf, Opcode: 3, Name: "wl_surface.frame", Child: s.cb}, wl.ArgObject(s.cb)); err != nil {
196+ if err = r.frame(); err != nil {
191197 return err
192198 }
193- if err = s.surf.Commit(); err != nil {
199+ if err = r.commit(); err != nil {
194200 return err
195201 }
196202 s.frameReady = false
197203 return nil
198204 }
199205
200-// setOpaque keeps the opaque region equal to the full logical surface when
201-// opaque, and empty otherwise, sending requests only on change.
202-func (s *Surface) setOpaque(opaque bool) error {
206+// surfaceRequests is the seam between the Present ordering rules and the
207+// wire: the production implementation sends the protocol requests.
208+type surfaceRequests interface {
209+ setDestination(w, h int32) error
210+ setBufferScale(n int32) error
211+ setOpaqueRegion(w, h int32, opaque bool) error
212+ setAcquire(timeline, point uint64) error
213+ setRelease(timeline, point uint64) error
214+ attach(buffer uint64) error
215+ damageBuffer(r Rect) error
216+ frame() error
217+ commit() error
218+}
219+
220+// wireRequests sends the requests. skipFrame is set by tests that isolate the
221+// request path from the frame callback registration.
222+type wireRequests struct {
223+ s *Surface
224+ skipFrame bool
225+}
226+
227+func (r wireRequests) setDestination(w, h int32) error { return r.s.viewport.SetDestination(w, h) }
228+func (r wireRequests) setBufferScale(n int32) error { return r.s.surf.SetBufferScale(n) }
229+
230+func (r wireRequests) setOpaqueRegion(w, h int32, opaque bool) error {
231+ s := r.s
203232 if !opaque {
204- if s.opaqueSet {
205- s.opaqueSet = false
206- return s.surf.SetOpaqueRegion(nil)
207- }
208- return nil
209- }
210- if s.opaqueSet && s.opaqueW == s.width && s.opaqueH == s.height {
211- return nil
233+ return s.surf.SetOpaqueRegion(nil)
212234 }
213235 region, err := s.c.g.compositor.CreateRegion()
214236 if err != nil {
215237 return err
216238 }
217- if err = region.Add(0, 0, s.width, s.height); err == nil {
239+ if err = region.Add(0, 0, w, h); err == nil {
218240 err = s.surf.SetOpaqueRegion(region)
219241 }
220242 _ = region.Destroy()
221- if err != nil {
222- return err
243+ return err
244+}
245+
246+func (r wireRequests) setAcquire(timeline, point uint64) error {
247+ return r.s.sync.SetAcquirePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point))
248+}
249+
250+func (r wireRequests) setRelease(timeline, point uint64) error {
251+ return r.s.sync.SetReleasePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point))
252+}
253+
254+func (r wireRequests) attach(buffer uint64) error { return r.s.surf.Attach(r.s.buffers[buffer], 0, 0) }
255+
256+func (r wireRequests) damageBuffer(d Rect) error {
257+ return r.s.surf.DamageBuffer(d.X, d.Y, d.Width, d.Height)
258+}
259+
260+// frame requests the frame callback on the surface's single reusable
261+// callback object: only one is ever pending.
262+func (r wireRequests) frame() error {
263+ s := r.s
264+ if r.skipFrame {
265+ return nil
223266 }
224- s.opaqueSet, s.opaqueW, s.opaqueH = true, s.width, s.height
225- return nil
267+ return s.c.wlctx.RequestArgs(wl.Request{Proxy: s.surf, Opcode: 3, Name: "wl_surface.frame", Child: s.cb}, wl.ArgObject(s.cb))
226268 }
269+
270+func (r wireRequests) commit() error { return r.s.surf.Commit() }
@@ -133,94 +133,138 @@ func (s *Surface) Present(p *Present) error {
133 if !s.frameReady {133 if !s.frameReady {
134 return errors.New("neferclient: frame callback pending")134 return errors.New("neferclient: frame callback pending")
135 }135 }
136- buf, ok := s.buffers[p.Buffer]136+ if _, ok := s.buffers[p.Buffer]; !ok {
137- if !ok {
138 return fmt.Errorf("neferclient: unknown buffer %d", p.Buffer)137 return fmt.Errorf("neferclient: unknown buffer %d", p.Buffer)
139 }138 }
140- acq, ok := s.timelines[p.AcquireTimeline]139+ for _, id := range [...]uint64{p.AcquireTimeline, p.ReleaseTimeline} {
141- if !ok {140+ if _, ok := s.timelines[id]; !ok {
142- return fmt.Errorf("neferclient: unknown timeline %d", p.AcquireTimeline)141+ return fmt.Errorf("neferclient: unknown timeline %d", id)
143- }142+ }
144- rel, ok := s.timelines[p.ReleaseTimeline]
145- if !ok {
146- return fmt.Errorf("neferclient: unknown timeline %d", p.ReleaseTimeline)
147 }143 }
148 pw, ph, err := s.PhysicalSize()144 pw, ph, err := s.PhysicalSize()
149 if err != nil {145 if err != nil {
150 return err146 return err
151 }147 }
148+ r := s.req
152 if s.viewport != nil {149 if s.viewport != nil {
153 if s.sentDestW != s.width || s.sentDestH != s.height {150 if s.sentDestW != s.width || s.sentDestH != s.height {
154- if err = s.viewport.SetDestination(s.width, s.height); err != nil {151+ if err = r.setDestination(s.width, s.height); err != nil {
155 return err152 return err
156 }153 }
157 s.sentDestW, s.sentDestH = s.width, s.height154 s.sentDestW, s.sentDestH = s.width, s.height
158 }155 }
159 } else if s.scale > 1 && s.sentBufScale != int32(s.scale) {156 } else if s.scale > 1 && s.sentBufScale != int32(s.scale) {
160- if err = s.surf.SetBufferScale(int32(s.scale)); err != nil {157+ if err = r.setBufferScale(int32(s.scale)); err != nil {
161 return err158 return err
162 }159 }
163 s.sentBufScale = int32(s.scale)160 s.sentBufScale = int32(s.scale)
164 }161 }
165- if err = s.setOpaque(p.Opaque); err != nil {162+ if p.Opaque {
166- return err163+ if !s.opaqueSet || s.opaqueW != s.width || s.opaqueH != s.height {
164+ if err = r.setOpaqueRegion(s.width, s.height, true); err != nil {
165+ return err
166+ }
167+ s.opaqueSet, s.opaqueW, s.opaqueH = true, s.width, s.height
168+ }
169+ } else if s.opaqueSet {
170+ if err = r.setOpaqueRegion(0, 0, false); err != nil {
171+ return err
172+ }
173+ s.opaqueSet = false
167 }174 }
168- if err = s.sync.SetAcquirePoint(acq, uint32(p.AcquirePoint>>32), uint32(p.AcquirePoint)); err != nil {175+ if err = r.setAcquire(p.AcquireTimeline, p.AcquirePoint); err != nil {
169 return err176 return err
170 }177 }
171- if err = s.sync.SetReleasePoint(rel, uint32(p.ReleasePoint>>32), uint32(p.ReleasePoint)); err != nil {178+ if err = r.setRelease(p.ReleaseTimeline, p.ReleasePoint); err != nil {
172 return err179 return err
173 }180 }
174- if err = s.surf.Attach(buf, 0, 0); err != nil {181+ if err = r.attach(p.Buffer); err != nil {
175 return err182 return err
176 }183 }
177 if len(p.Damage) == 0 {184 if len(p.Damage) == 0 {
178- err = s.surf.DamageBuffer(0, 0, pw, ph)185+ err = r.damageBuffer(Rect{0, 0, pw, ph})
179 }186 }
180- for _, r := range p.Damage {187+ for _, d := range p.Damage {
181 if err != nil {188 if err != nil {
182 break189 break
183 }190 }
184- err = s.surf.DamageBuffer(r.X, r.Y, r.Width, r.Height)191+ err = r.damageBuffer(d)
185 }192 }
186 if err != nil {193 if err != nil {
187 return err194 return err
188 }195 }
189- // The one frame callback object is reused: only one is ever pending.196+ if err = r.frame(); err != nil {
190- if err = s.c.wlctx.RequestArgs(wl.Request{Proxy: s.surf, Opcode: 3, Name: "wl_surface.frame", Child: s.cb}, wl.ArgObject(s.cb)); err != nil {
191 return err197 return err
192 }198 }
193- if err = s.surf.Commit(); err != nil {199+ if err = r.commit(); err != nil {
194 return err200 return err
195 }201 }
196 s.frameReady = false202 s.frameReady = false
197 return nil203 return nil
198 }204 }
199 205
200-// setOpaque keeps the opaque region equal to the full logical surface when206+// surfaceRequests is the seam between the Present ordering rules and the
201-// opaque, and empty otherwise, sending requests only on change.207+// wire: the production implementation sends the protocol requests.
202-func (s *Surface) setOpaque(opaque bool) error {208+type surfaceRequests interface {
209+ setDestination(w, h int32) error
210+ setBufferScale(n int32) error
211+ setOpaqueRegion(w, h int32, opaque bool) error
212+ setAcquire(timeline, point uint64) error
213+ setRelease(timeline, point uint64) error
214+ attach(buffer uint64) error
215+ damageBuffer(r Rect) error
216+ frame() error
217+ commit() error
218+}
219+
220+// wireRequests sends the requests. skipFrame is set by tests that isolate the
221+// request path from the frame callback registration.
222+type wireRequests struct {
223+ s *Surface
224+ skipFrame bool
225+}
226+
227+func (r wireRequests) setDestination(w, h int32) error { return r.s.viewport.SetDestination(w, h) }
228+func (r wireRequests) setBufferScale(n int32) error { return r.s.surf.SetBufferScale(n) }
229+
230+func (r wireRequests) setOpaqueRegion(w, h int32, opaque bool) error {
231+ s := r.s
203 if !opaque {232 if !opaque {
204- if s.opaqueSet {233+ return s.surf.SetOpaqueRegion(nil)
205- s.opaqueSet = false
206- return s.surf.SetOpaqueRegion(nil)
207- }
208- return nil
209- }
210- if s.opaqueSet && s.opaqueW == s.width && s.opaqueH == s.height {
211- return nil
212 }234 }
213 region, err := s.c.g.compositor.CreateRegion()235 region, err := s.c.g.compositor.CreateRegion()
214 if err != nil {236 if err != nil {
215 return err237 return err
216 }238 }
217- if err = region.Add(0, 0, s.width, s.height); err == nil {239+ if err = region.Add(0, 0, w, h); err == nil {
218 err = s.surf.SetOpaqueRegion(region)240 err = s.surf.SetOpaqueRegion(region)
219 }241 }
220 _ = region.Destroy()242 _ = region.Destroy()
221- if err != nil {243+ return err
222- return err244+}
245+
246+func (r wireRequests) setAcquire(timeline, point uint64) error {
247+ return r.s.sync.SetAcquirePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point))
248+}
249+
250+func (r wireRequests) setRelease(timeline, point uint64) error {
251+ return r.s.sync.SetReleasePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point))
252+}
253+
254+func (r wireRequests) attach(buffer uint64) error { return r.s.surf.Attach(r.s.buffers[buffer], 0, 0) }
255+
256+func (r wireRequests) damageBuffer(d Rect) error {
257+ return r.s.surf.DamageBuffer(d.X, d.Y, d.Width, d.Height)
258+}
259+
260+// frame requests the frame callback on the surface's single reusable
261+// callback object: only one is ever pending.
262+func (r wireRequests) frame() error {
263+ s := r.s
264+ if r.skipFrame {
265+ return nil
223 }266 }
224- s.opaqueSet, s.opaqueW, s.opaqueH = true, s.width, s.height267+ return s.c.wlctx.RequestArgs(wl.Request{Proxy: s.surf, Opcode: 3, Name: "wl_surface.frame", Child: s.cb}, wl.ArgObject(s.cb))
225- return nil
226 }268 }
269+
270+func (r wireRequests) commit() error { return r.s.surf.Commit() }
M surface.go +3 -1
@@ -118,7 +118,8 @@ type Surface struct {
118118 opaque *wayland.Region
119119 opaqueW, opaqueH int32
120120
121- cb *frameCallback
121+ cb *frameCallback
122+ req surfaceRequests
122123
123124 inputRects []Rect
124125 inputCustom bool
@@ -270,6 +271,7 @@ func (c *Conn) newSurface(role surfaceRole, w, h int32) (*Surface, error) {
270271 // The role objects come next (callers); sync surface and feedback after.
271272 s.cb = &frameCallback{q: q, id: s.id}
272273 s.cb.SetContext(c.wlctx)
274+ s.req = wireRequests{s: s}
273275 c.surfaces[s.id] = s
274276 ok = true
275277 return s, nil
@@ -118,7 +118,8 @@ type Surface struct {
118 opaque *wayland.Region118 opaque *wayland.Region
119 opaqueW, opaqueH int32119 opaqueW, opaqueH int32
120 120
121- cb *frameCallback121+ cb *frameCallback
122+ req surfaceRequests
122 123
123 inputRects []Rect124 inputRects []Rect
124 inputCustom bool125 inputCustom bool
@@ -270,6 +271,7 @@ func (c *Conn) newSurface(role surfaceRole, w, h int32) (*Surface, error) {
270 // The role objects come next (callers); sync surface and feedback after.271 // The role objects come next (callers); sync surface and feedback after.
271 s.cb = &frameCallback{q: q, id: s.id}272 s.cb = &frameCallback{q: q, id: s.id}
272 s.cb.SetContext(c.wlctx)273 s.cb.SetContext(c.wlctx)
274+ s.req = wireRequests{s: s}
273 c.surfaces[s.id] = s275 c.surfaces[s.id] = s
274 ok = true276 ok = true
275 return s, nil277 return s, nil
A surface_internal_test.go +217 -0
@@ -0,0 +1,217 @@
1+package neferclient
2+
3+import (
4+ "encoding/binary"
5+ "testing"
6+
7+ "github.com/bnema/go-wayland-bindings/client/linuxdrmsyncobj"
8+ "github.com/bnema/go-wayland-bindings/client/viewporter"
9+ "github.com/bnema/go-wayland-bindings/client/wayland"
10+ "github.com/stretchr/testify/mock"
11+ "github.com/stretchr/testify/require"
12+ "golang.org/x/sys/unix"
13+)
14+
15+// presentable returns a configured surface that sends through req.
16+func presentable(req surfaceRequests, scale float64) *Surface {
17+ return &Surface{id: 1, req: req, width: 100, height: 50, scale: scale, configured: true, frameReady: true, fbDone: true,
18+ buffers: map[uint64]*wayland.Buffer{1: nil},
19+ timelines: map[uint64]*linuxdrmsyncobj.WpLinuxDrmSyncobjTimeline{1: nil, 2: nil},
20+ viewport: &viewporter.WpViewport{}}
21+}
22+
23+func TestPresentOrdering(t *testing.T) {
24+ m := newMocksurfaceRequests(t)
25+ s := presentable(m, 1.5)
26+ // NotBefore makes testify fail a call that arrives before its predecessor.
27+ dest := m.EXPECT().setDestination(int32(100), int32(50)).Return(nil).Once()
28+ opaque := m.EXPECT().setOpaqueRegion(int32(100), int32(50), true).Return(nil).Once().NotBefore(dest)
29+ acquire := m.EXPECT().setAcquire(uint64(1), uint64(7)).Return(nil).Once().NotBefore(opaque)
30+ release := m.EXPECT().setRelease(uint64(2), uint64(8)).Return(nil).Once().NotBefore(acquire)
31+ attach := m.EXPECT().attach(uint64(1)).Return(nil).Once().NotBefore(release)
32+ damage := m.EXPECT().damageBuffer(Rect{1, 2, 3, 4}).Return(nil).Once().NotBefore(attach)
33+ frame := m.EXPECT().frame().Return(nil).Once().NotBefore(damage)
34+ m.EXPECT().commit().Return(nil).Once().NotBefore(frame)
35+
36+ p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2, AcquirePoint: 7, ReleasePoint: 8,
37+ Damage: []Rect{{1, 2, 3, 4}}, Opaque: true}
38+ require.NoError(t, s.Present(p))
39+ require.False(t, s.frameReady)
40+ require.ErrorContains(t, s.Present(p), "frame callback pending") // no further request: strict mock
41+}
42+
43+func TestPresentSkipsUnchangedState(t *testing.T) {
44+ m := newMocksurfaceRequests(t)
45+ s := presentable(m, 2)
46+ m.EXPECT().setDestination(int32(100), int32(50)).Return(nil).Once()
47+ m.EXPECT().setOpaqueRegion(int32(100), int32(50), true).Return(nil).Once()
48+ m.EXPECT().setAcquire(mock.Anything, mock.Anything).Return(nil).Twice()
49+ m.EXPECT().setRelease(mock.Anything, mock.Anything).Return(nil).Twice()
50+ m.EXPECT().attach(uint64(1)).Return(nil).Twice()
51+ m.EXPECT().damageBuffer(Rect{0, 0, 200, 100}).Return(nil).Twice() // no damage: whole physical buffer
52+ m.EXPECT().frame().Return(nil).Twice()
53+ m.EXPECT().commit().Return(nil).Twice()
54+ p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2, Opaque: true}
55+ require.NoError(t, s.Present(p))
56+ s.frameReady = true
57+ require.NoError(t, s.Present(p)) // destination and opaque region are not resent
58+}
59+
60+func TestPresentRefusals(t *testing.T) {
61+ m := newMocksurfaceRequests(t) // strict: a refused Present sends nothing
62+ p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2}
63+ for name, mutate := range map[string]func(*Surface){
64+ "before configure": func(s *Surface) { s.configured = false },
65+ "before feedback": func(s *Surface) { s.fbDone = false },
66+ "unknown buffer": func(s *Surface) { delete(s.buffers, 1) },
67+ "unknown timeline": func(s *Surface) { delete(s.timelines, 2) },
68+ "closed": func(s *Surface) { s.closed = true },
69+ "bad geometry": func(s *Surface) { s.scale = 0 },
70+ "fractional without viewporter": func(s *Surface) {
71+ s.scale, s.viewport = 1.5, nil
72+ },
73+ } {
74+ s := presentable(m, 1)
75+ mutate(s)
76+ require.Error(t, s.Present(p), name)
77+ }
78+}
79+
80+func TestPhysicalSize(t *testing.T) {
81+ s := presentable(nil, 1.25)
82+ w, h, err := s.PhysicalSize()
83+ require.NoError(t, err)
84+ require.Equal(t, [2]int32{125, 63}, [2]int32{w, h}) // 62.5 rounds up
85+}
86+
87+// feedbackHandler is the measured input, not a double: it keeps what the
88+// owner-side feedback parsing produced.
89+type feedbackHandler struct {
90+ NopHandler
91+ done int
92+ errs []error
93+}
94+
95+func (h *feedbackHandler) FeedbackDone(SurfaceID) { h.done++ }
96+func (h *feedbackHandler) Error(err error) { h.errs = append(h.errs, err) }
97+
98+func tableFD(t *testing.T, entries []Format) int {
99+ t.Helper()
100+ fd, err := unix.MemfdCreate("table", unix.MFD_CLOEXEC)
101+ require.NoError(t, err)
102+ var b []byte
103+ for _, e := range entries {
104+ b = binary.NativeEndian.AppendUint32(b, e.FourCC)
105+ b = binary.NativeEndian.AppendUint32(b, 0)
106+ b = binary.NativeEndian.AppendUint64(b, e.Modifier)
107+ }
108+ _, err = unix.Write(fd, b)
109+ require.NoError(t, err)
110+ return fd
111+}
112+
113+func postRound(c *Conn, s *Surface, fd int, size uint32, device uint64, indices ...uint16) {
114+ id := uint32(s.id)
115+ c.q.post(&event{kind: evFeedbackTable, id: id, fd: int32(fd), flags: size})
116+ c.q.post(&event{kind: evFeedbackMainDevice, id: id, dev: device})
117+ e := event{kind: evFeedbackFormats, id: id}
118+ for _, i := range indices {
119+ binary.NativeEndian.PutUint16(e.name[e.nameLen:], i)
120+ e.nameLen += 2
121+ }
122+ c.q.post(&e)
123+ c.q.post(&event{kind: evFeedbackDone, id: id})
124+}
125+
126+func newOwnerConn() (*Conn, *Surface) {
127+ c := &Conn{q: newQueue(false), surfaces: map[SurfaceID]*Surface{}, watched: map[int32]uint64{}, efd: -1, epfd: -1}
128+ s := presentable(nil, 1)
129+ c.surfaces[s.id], s.c, s.fbDone = s, c, false
130+ return c, s
131+}
132+
133+func TestFeedbackParsedOnOwner(t *testing.T) {
134+ c, s := newOwnerConn()
135+ table := []Format{{0x34325258, 0}, {0x34325241, 7}, {0x34325258, 1 << 40}}
136+ postRound(c, s, tableFD(t, table), 16*3, 0xe280, 2, 0)
137+ h := &feedbackHandler{}
138+ require.NoError(t, c.Dispatch(h))
139+ require.Empty(t, h.errs)
140+ require.Equal(t, 1, h.done)
141+ fb := s.Feedback()
142+ require.NotNil(t, fb)
143+ require.Equal(t, uint64(0xe280), fb.MainDevice)
144+ require.Equal(t, []Format{table[2], table[0]}, fb.Formats, "tranche order")
145+
146+ // A second round replaces the first without leaking the first one's formats.
147+ postRound(c, s, tableFD(t, table), 16*3, 0xe281, 1)
148+ require.NoError(t, c.Dispatch(h))
149+ require.Equal(t, &Feedback{MainDevice: 0xe281, Formats: []Format{table[1]}}, s.Feedback())
150+}
151+
152+func TestFeedbackBadIndexAndSize(t *testing.T) {
153+ c, s := newOwnerConn()
154+ postRound(c, s, tableFD(t, []Format{{1, 1}}), 16, 5, 3)
155+ postRound(c, s, tableFD(t, nil), 15, 5)
156+ h := &feedbackHandler{}
157+ require.NoError(t, c.Dispatch(h))
158+ require.Len(t, h.errs, 2)
159+ require.ErrorContains(t, h.errs[0], "out of range")
160+ require.ErrorContains(t, h.errs[1], "invalid dmabuf format table size")
161+}
162+
163+func fdOpen(fd int) bool { _, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0); return err == nil }
164+
165+func TestCloseClosesUndrainedFeedbackFDs(t *testing.T) {
166+ c, s := newOwnerConn()
167+ ringed := tableFD(t, nil)
168+ c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(ringed)})
169+ held := tableFD(t, nil)
170+ c.q.pausing = true
171+ c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(held)}) // parked in held
172+ require.True(t, fdOpen(ringed))
173+ require.True(t, fdOpen(held))
174+ c.q.closeFDs()
175+ require.False(t, fdOpen(ringed), "ring event's descriptor")
176+ require.False(t, fdOpen(held), "parked event's descriptor")
177+
178+ // An event that is drained owns its descriptor no more: closeFDs must not
179+ // touch a number the process may have reused.
180+ drained := tableFD(t, nil)
181+ c.q.post(&event{kind: evFeedbackTable, id: 99, fd: int32(drained)}) // pausing: held
182+ c.q.pausing = false
183+ c.q.endPause()
184+ var ev event
185+ for c.q.pop(&ev) { // drain everything: the earlier events and this one
186+ }
187+ c.q.closeFDs()
188+ require.True(t, fdOpen(drained))
189+ _ = unix.Close(drained)
190+}
191+
192+func TestEventForClosedSurfaceDropsFD(t *testing.T) {
193+ c, s := newOwnerConn()
194+ fd := tableFD(t, nil)
195+ s.closed = true
196+ c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(fd)})
197+ require.NoError(t, c.Dispatch(nil))
198+ require.False(t, fdOpen(fd))
199+}
200+
201+func TestLockRules(t *testing.T) {
202+ c := &Conn{q: newQueue(false), surfaces: map[SurfaceID]*Surface{}}
203+ l := &Lock{c: c, gen: 1}
204+ c.curLock = l
205+ require.ErrorContains(t, l.Unlock(), "before the locked event")
206+ c.q.post(&event{kind: evLocked, id: 2}) // another generation: ignored
207+ require.NoError(t, c.Dispatch(nil))
208+ require.False(t, l.Locked())
209+ c.q.post(&event{kind: evLocked, id: 1})
210+ require.NoError(t, c.Dispatch(nil))
211+ require.True(t, l.Locked())
212+ require.ErrorContains(t, l.Close(), "use Unlock")
213+ c.q.post(&event{kind: evLockFinished, id: 1})
214+ require.NoError(t, c.Dispatch(nil))
215+ require.False(t, l.Locked())
216+ require.ErrorContains(t, l.Unlock(), "before the locked event")
217+}
@@ -0,0 +1,217 @@
1+package neferclient
2+
3+import (
4+ "encoding/binary"
5+ "testing"
6+
7+ "github.com/bnema/go-wayland-bindings/client/linuxdrmsyncobj"
8+ "github.com/bnema/go-wayland-bindings/client/viewporter"
9+ "github.com/bnema/go-wayland-bindings/client/wayland"
10+ "github.com/stretchr/testify/mock"
11+ "github.com/stretchr/testify/require"
12+ "golang.org/x/sys/unix"
13+)
14+
15+// presentable returns a configured surface that sends through req.
16+func presentable(req surfaceRequests, scale float64) *Surface {
17+ return &Surface{id: 1, req: req, width: 100, height: 50, scale: scale, configured: true, frameReady: true, fbDone: true,
18+ buffers: map[uint64]*wayland.Buffer{1: nil},
19+ timelines: map[uint64]*linuxdrmsyncobj.WpLinuxDrmSyncobjTimeline{1: nil, 2: nil},
20+ viewport: &viewporter.WpViewport{}}
21+}
22+
23+func TestPresentOrdering(t *testing.T) {
24+ m := newMocksurfaceRequests(t)
25+ s := presentable(m, 1.5)
26+ // NotBefore makes testify fail a call that arrives before its predecessor.
27+ dest := m.EXPECT().setDestination(int32(100), int32(50)).Return(nil).Once()
28+ opaque := m.EXPECT().setOpaqueRegion(int32(100), int32(50), true).Return(nil).Once().NotBefore(dest)
29+ acquire := m.EXPECT().setAcquire(uint64(1), uint64(7)).Return(nil).Once().NotBefore(opaque)
30+ release := m.EXPECT().setRelease(uint64(2), uint64(8)).Return(nil).Once().NotBefore(acquire)
31+ attach := m.EXPECT().attach(uint64(1)).Return(nil).Once().NotBefore(release)
32+ damage := m.EXPECT().damageBuffer(Rect{1, 2, 3, 4}).Return(nil).Once().NotBefore(attach)
33+ frame := m.EXPECT().frame().Return(nil).Once().NotBefore(damage)
34+ m.EXPECT().commit().Return(nil).Once().NotBefore(frame)
35+
36+ p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2, AcquirePoint: 7, ReleasePoint: 8,
37+ Damage: []Rect{{1, 2, 3, 4}}, Opaque: true}
38+ require.NoError(t, s.Present(p))
39+ require.False(t, s.frameReady)
40+ require.ErrorContains(t, s.Present(p), "frame callback pending") // no further request: strict mock
41+}
42+
43+func TestPresentSkipsUnchangedState(t *testing.T) {
44+ m := newMocksurfaceRequests(t)
45+ s := presentable(m, 2)
46+ m.EXPECT().setDestination(int32(100), int32(50)).Return(nil).Once()
47+ m.EXPECT().setOpaqueRegion(int32(100), int32(50), true).Return(nil).Once()
48+ m.EXPECT().setAcquire(mock.Anything, mock.Anything).Return(nil).Twice()
49+ m.EXPECT().setRelease(mock.Anything, mock.Anything).Return(nil).Twice()
50+ m.EXPECT().attach(uint64(1)).Return(nil).Twice()
51+ m.EXPECT().damageBuffer(Rect{0, 0, 200, 100}).Return(nil).Twice() // no damage: whole physical buffer
52+ m.EXPECT().frame().Return(nil).Twice()
53+ m.EXPECT().commit().Return(nil).Twice()
54+ p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2, Opaque: true}
55+ require.NoError(t, s.Present(p))
56+ s.frameReady = true
57+ require.NoError(t, s.Present(p)) // destination and opaque region are not resent
58+}
59+
60+func TestPresentRefusals(t *testing.T) {
61+ m := newMocksurfaceRequests(t) // strict: a refused Present sends nothing
62+ p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2}
63+ for name, mutate := range map[string]func(*Surface){
64+ "before configure": func(s *Surface) { s.configured = false },
65+ "before feedback": func(s *Surface) { s.fbDone = false },
66+ "unknown buffer": func(s *Surface) { delete(s.buffers, 1) },
67+ "unknown timeline": func(s *Surface) { delete(s.timelines, 2) },
68+ "closed": func(s *Surface) { s.closed = true },
69+ "bad geometry": func(s *Surface) { s.scale = 0 },
70+ "fractional without viewporter": func(s *Surface) {
71+ s.scale, s.viewport = 1.5, nil
72+ },
73+ } {
74+ s := presentable(m, 1)
75+ mutate(s)
76+ require.Error(t, s.Present(p), name)
77+ }
78+}
79+
80+func TestPhysicalSize(t *testing.T) {
81+ s := presentable(nil, 1.25)
82+ w, h, err := s.PhysicalSize()
83+ require.NoError(t, err)
84+ require.Equal(t, [2]int32{125, 63}, [2]int32{w, h}) // 62.5 rounds up
85+}
86+
87+// feedbackHandler is the measured input, not a double: it keeps what the
88+// owner-side feedback parsing produced.
89+type feedbackHandler struct {
90+ NopHandler
91+ done int
92+ errs []error
93+}
94+
95+func (h *feedbackHandler) FeedbackDone(SurfaceID) { h.done++ }
96+func (h *feedbackHandler) Error(err error) { h.errs = append(h.errs, err) }
97+
98+func tableFD(t *testing.T, entries []Format) int {
99+ t.Helper()
100+ fd, err := unix.MemfdCreate("table", unix.MFD_CLOEXEC)
101+ require.NoError(t, err)
102+ var b []byte
103+ for _, e := range entries {
104+ b = binary.NativeEndian.AppendUint32(b, e.FourCC)
105+ b = binary.NativeEndian.AppendUint32(b, 0)
106+ b = binary.NativeEndian.AppendUint64(b, e.Modifier)
107+ }
108+ _, err = unix.Write(fd, b)
109+ require.NoError(t, err)
110+ return fd
111+}
112+
113+func postRound(c *Conn, s *Surface, fd int, size uint32, device uint64, indices ...uint16) {
114+ id := uint32(s.id)
115+ c.q.post(&event{kind: evFeedbackTable, id: id, fd: int32(fd), flags: size})
116+ c.q.post(&event{kind: evFeedbackMainDevice, id: id, dev: device})
117+ e := event{kind: evFeedbackFormats, id: id}
118+ for _, i := range indices {
119+ binary.NativeEndian.PutUint16(e.name[e.nameLen:], i)
120+ e.nameLen += 2
121+ }
122+ c.q.post(&e)
123+ c.q.post(&event{kind: evFeedbackDone, id: id})
124+}
125+
126+func newOwnerConn() (*Conn, *Surface) {
127+ c := &Conn{q: newQueue(false), surfaces: map[SurfaceID]*Surface{}, watched: map[int32]uint64{}, efd: -1, epfd: -1}
128+ s := presentable(nil, 1)
129+ c.surfaces[s.id], s.c, s.fbDone = s, c, false
130+ return c, s
131+}
132+
133+func TestFeedbackParsedOnOwner(t *testing.T) {
134+ c, s := newOwnerConn()
135+ table := []Format{{0x34325258, 0}, {0x34325241, 7}, {0x34325258, 1 << 40}}
136+ postRound(c, s, tableFD(t, table), 16*3, 0xe280, 2, 0)
137+ h := &feedbackHandler{}
138+ require.NoError(t, c.Dispatch(h))
139+ require.Empty(t, h.errs)
140+ require.Equal(t, 1, h.done)
141+ fb := s.Feedback()
142+ require.NotNil(t, fb)
143+ require.Equal(t, uint64(0xe280), fb.MainDevice)
144+ require.Equal(t, []Format{table[2], table[0]}, fb.Formats, "tranche order")
145+
146+ // A second round replaces the first without leaking the first one's formats.
147+ postRound(c, s, tableFD(t, table), 16*3, 0xe281, 1)
148+ require.NoError(t, c.Dispatch(h))
149+ require.Equal(t, &Feedback{MainDevice: 0xe281, Formats: []Format{table[1]}}, s.Feedback())
150+}
151+
152+func TestFeedbackBadIndexAndSize(t *testing.T) {
153+ c, s := newOwnerConn()
154+ postRound(c, s, tableFD(t, []Format{{1, 1}}), 16, 5, 3)
155+ postRound(c, s, tableFD(t, nil), 15, 5)
156+ h := &feedbackHandler{}
157+ require.NoError(t, c.Dispatch(h))
158+ require.Len(t, h.errs, 2)
159+ require.ErrorContains(t, h.errs[0], "out of range")
160+ require.ErrorContains(t, h.errs[1], "invalid dmabuf format table size")
161+}
162+
163+func fdOpen(fd int) bool { _, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0); return err == nil }
164+
165+func TestCloseClosesUndrainedFeedbackFDs(t *testing.T) {
166+ c, s := newOwnerConn()
167+ ringed := tableFD(t, nil)
168+ c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(ringed)})
169+ held := tableFD(t, nil)
170+ c.q.pausing = true
171+ c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(held)}) // parked in held
172+ require.True(t, fdOpen(ringed))
173+ require.True(t, fdOpen(held))
174+ c.q.closeFDs()
175+ require.False(t, fdOpen(ringed), "ring event's descriptor")
176+ require.False(t, fdOpen(held), "parked event's descriptor")
177+
178+ // An event that is drained owns its descriptor no more: closeFDs must not
179+ // touch a number the process may have reused.
180+ drained := tableFD(t, nil)
181+ c.q.post(&event{kind: evFeedbackTable, id: 99, fd: int32(drained)}) // pausing: held
182+ c.q.pausing = false
183+ c.q.endPause()
184+ var ev event
185+ for c.q.pop(&ev) { // drain everything: the earlier events and this one
186+ }
187+ c.q.closeFDs()
188+ require.True(t, fdOpen(drained))
189+ _ = unix.Close(drained)
190+}
191+
192+func TestEventForClosedSurfaceDropsFD(t *testing.T) {
193+ c, s := newOwnerConn()
194+ fd := tableFD(t, nil)
195+ s.closed = true
196+ c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(fd)})
197+ require.NoError(t, c.Dispatch(nil))
198+ require.False(t, fdOpen(fd))
199+}
200+
201+func TestLockRules(t *testing.T) {
202+ c := &Conn{q: newQueue(false), surfaces: map[SurfaceID]*Surface{}}
203+ l := &Lock{c: c, gen: 1}
204+ c.curLock = l
205+ require.ErrorContains(t, l.Unlock(), "before the locked event")
206+ c.q.post(&event{kind: evLocked, id: 2}) // another generation: ignored
207+ require.NoError(t, c.Dispatch(nil))
208+ require.False(t, l.Locked())
209+ c.q.post(&event{kind: evLocked, id: 1})
210+ require.NoError(t, c.Dispatch(nil))
211+ require.True(t, l.Locked())
212+ require.ErrorContains(t, l.Close(), "use Unlock")
213+ c.q.post(&event{kind: evLockFinished, id: 1})
214+ require.NoError(t, c.Dispatch(nil))
215+ require.False(t, l.Locked())
216+ require.ErrorContains(t, l.Unlock(), "before the locked event")
217+}
A surface_mock_test.go +520 -0
@@ -0,0 +1,520 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package neferclient
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// newMocksurfaceRequests creates a new instance of mocksurfaceRequests. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func newMocksurfaceRequests(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *mocksurfaceRequests {
17+ if helper, ok := t.(interface{ Helper() }); ok {
18+ helper.Helper()
19+ }
20+
21+ mock := &mocksurfaceRequests{}
22+ mock.Mock.Test(t)
23+
24+ t.Cleanup(func() {
25+ if helper, ok := t.(interface{ Helper() }); ok {
26+ helper.Helper()
27+ }
28+ mock.AssertExpectations(t)
29+ })
30+
31+ return mock
32+}
33+
34+// mocksurfaceRequests is an autogenerated mock type for the surfaceRequests type
35+type mocksurfaceRequests struct {
36+ mock.Mock
37+}
38+
39+type mocksurfaceRequests_Expecter struct {
40+ mock *mock.Mock
41+}
42+
43+func (_m *mocksurfaceRequests) EXPECT() *mocksurfaceRequests_Expecter {
44+ return &mocksurfaceRequests_Expecter{mock: &_m.Mock}
45+}
46+
47+// attach provides a mock function for the type mocksurfaceRequests
48+func (_mock *mocksurfaceRequests) attach(buffer uint64) error {
49+ ret := _mock.Called(buffer)
50+
51+ if len(ret) == 0 {
52+ panic("no return value specified for attach")
53+ }
54+
55+ var r0 error
56+ if returnFunc, ok := ret.Get(0).(func(uint64) error); ok {
57+ r0 = returnFunc(buffer)
58+ } else {
59+ r0 = ret.Error(0)
60+ }
61+ return r0
62+}
63+
64+// mocksurfaceRequests_attach_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'attach'
65+type mocksurfaceRequests_attach_Call struct {
66+ *mock.Call
67+}
68+
69+// attach is a helper method to define mock.On call
70+// - buffer uint64
71+func (_e *mocksurfaceRequests_Expecter) attach(buffer any) *mocksurfaceRequests_attach_Call {
72+ return &mocksurfaceRequests_attach_Call{Call: _e.mock.On("attach", buffer)}
73+}
74+
75+func (_c *mocksurfaceRequests_attach_Call) Run(run func(buffer uint64)) *mocksurfaceRequests_attach_Call {
76+ _c.Call.Run(func(args mock.Arguments) {
77+ var arg0 uint64
78+ if args[0] != nil {
79+ arg0 = args[0].(uint64)
80+ }
81+ run(
82+ arg0,
83+ )
84+ })
85+ return _c
86+}
87+
88+func (_c *mocksurfaceRequests_attach_Call) Return(err error) *mocksurfaceRequests_attach_Call {
89+ _c.Call.Return(err)
90+ return _c
91+}
92+
93+func (_c *mocksurfaceRequests_attach_Call) RunAndReturn(run func(buffer uint64) error) *mocksurfaceRequests_attach_Call {
94+ _c.Call.Return(run)
95+ return _c
96+}
97+
98+// commit provides a mock function for the type mocksurfaceRequests
99+func (_mock *mocksurfaceRequests) commit() error {
100+ ret := _mock.Called()
101+
102+ if len(ret) == 0 {
103+ panic("no return value specified for commit")
104+ }
105+
106+ var r0 error
107+ if returnFunc, ok := ret.Get(0).(func() error); ok {
108+ r0 = returnFunc()
109+ } else {
110+ r0 = ret.Error(0)
111+ }
112+ return r0
113+}
114+
115+// mocksurfaceRequests_commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'commit'
116+type mocksurfaceRequests_commit_Call struct {
117+ *mock.Call
118+}
119+
120+// commit is a helper method to define mock.On call
121+func (_e *mocksurfaceRequests_Expecter) commit() *mocksurfaceRequests_commit_Call {
122+ return &mocksurfaceRequests_commit_Call{Call: _e.mock.On("commit")}
123+}
124+
125+func (_c *mocksurfaceRequests_commit_Call) Run(run func()) *mocksurfaceRequests_commit_Call {
126+ _c.Call.Run(func(args mock.Arguments) {
127+ run()
128+ })
129+ return _c
130+}
131+
132+func (_c *mocksurfaceRequests_commit_Call) Return(err error) *mocksurfaceRequests_commit_Call {
133+ _c.Call.Return(err)
134+ return _c
135+}
136+
137+func (_c *mocksurfaceRequests_commit_Call) RunAndReturn(run func() error) *mocksurfaceRequests_commit_Call {
138+ _c.Call.Return(run)
139+ return _c
140+}
141+
142+// damageBuffer provides a mock function for the type mocksurfaceRequests
143+func (_mock *mocksurfaceRequests) damageBuffer(r Rect) error {
144+ ret := _mock.Called(r)
145+
146+ if len(ret) == 0 {
147+ panic("no return value specified for damageBuffer")
148+ }
149+
150+ var r0 error
151+ if returnFunc, ok := ret.Get(0).(func(Rect) error); ok {
152+ r0 = returnFunc(r)
153+ } else {
154+ r0 = ret.Error(0)
155+ }
156+ return r0
157+}
158+
159+// mocksurfaceRequests_damageBuffer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'damageBuffer'
160+type mocksurfaceRequests_damageBuffer_Call struct {
161+ *mock.Call
162+}
163+
164+// damageBuffer is a helper method to define mock.On call
165+// - r Rect
166+func (_e *mocksurfaceRequests_Expecter) damageBuffer(r any) *mocksurfaceRequests_damageBuffer_Call {
167+ return &mocksurfaceRequests_damageBuffer_Call{Call: _e.mock.On("damageBuffer", r)}
168+}
169+
170+func (_c *mocksurfaceRequests_damageBuffer_Call) Run(run func(r Rect)) *mocksurfaceRequests_damageBuffer_Call {
171+ _c.Call.Run(func(args mock.Arguments) {
172+ var arg0 Rect
173+ if args[0] != nil {
174+ arg0 = args[0].(Rect)
175+ }
176+ run(
177+ arg0,
178+ )
179+ })
180+ return _c
181+}
182+
183+func (_c *mocksurfaceRequests_damageBuffer_Call) Return(err error) *mocksurfaceRequests_damageBuffer_Call {
184+ _c.Call.Return(err)
185+ return _c
186+}
187+
188+func (_c *mocksurfaceRequests_damageBuffer_Call) RunAndReturn(run func(r Rect) error) *mocksurfaceRequests_damageBuffer_Call {
189+ _c.Call.Return(run)
190+ return _c
191+}
192+
193+// frame provides a mock function for the type mocksurfaceRequests
194+func (_mock *mocksurfaceRequests) frame() error {
195+ ret := _mock.Called()
196+
197+ if len(ret) == 0 {
198+ panic("no return value specified for frame")
199+ }
200+
201+ var r0 error
202+ if returnFunc, ok := ret.Get(0).(func() error); ok {
203+ r0 = returnFunc()
204+ } else {
205+ r0 = ret.Error(0)
206+ }
207+ return r0
208+}
209+
210+// mocksurfaceRequests_frame_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'frame'
211+type mocksurfaceRequests_frame_Call struct {
212+ *mock.Call
213+}
214+
215+// frame is a helper method to define mock.On call
216+func (_e *mocksurfaceRequests_Expecter) frame() *mocksurfaceRequests_frame_Call {
217+ return &mocksurfaceRequests_frame_Call{Call: _e.mock.On("frame")}
218+}
219+
220+func (_c *mocksurfaceRequests_frame_Call) Run(run func()) *mocksurfaceRequests_frame_Call {
221+ _c.Call.Run(func(args mock.Arguments) {
222+ run()
223+ })
224+ return _c
225+}
226+
227+func (_c *mocksurfaceRequests_frame_Call) Return(err error) *mocksurfaceRequests_frame_Call {
228+ _c.Call.Return(err)
229+ return _c
230+}
231+
232+func (_c *mocksurfaceRequests_frame_Call) RunAndReturn(run func() error) *mocksurfaceRequests_frame_Call {
233+ _c.Call.Return(run)
234+ return _c
235+}
236+
237+// setAcquire provides a mock function for the type mocksurfaceRequests
238+func (_mock *mocksurfaceRequests) setAcquire(timeline uint64, point uint64) error {
239+ ret := _mock.Called(timeline, point)
240+
241+ if len(ret) == 0 {
242+ panic("no return value specified for setAcquire")
243+ }
244+
245+ var r0 error
246+ if returnFunc, ok := ret.Get(0).(func(uint64, uint64) error); ok {
247+ r0 = returnFunc(timeline, point)
248+ } else {
249+ r0 = ret.Error(0)
250+ }
251+ return r0
252+}
253+
254+// mocksurfaceRequests_setAcquire_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setAcquire'
255+type mocksurfaceRequests_setAcquire_Call struct {
256+ *mock.Call
257+}
258+
259+// setAcquire is a helper method to define mock.On call
260+// - timeline uint64
261+// - point uint64
262+func (_e *mocksurfaceRequests_Expecter) setAcquire(timeline any, point any) *mocksurfaceRequests_setAcquire_Call {
263+ return &mocksurfaceRequests_setAcquire_Call{Call: _e.mock.On("setAcquire", timeline, point)}
264+}
265+
266+func (_c *mocksurfaceRequests_setAcquire_Call) Run(run func(timeline uint64, point uint64)) *mocksurfaceRequests_setAcquire_Call {
267+ _c.Call.Run(func(args mock.Arguments) {
268+ var arg0 uint64
269+ if args[0] != nil {
270+ arg0 = args[0].(uint64)
271+ }
272+ var arg1 uint64
273+ if args[1] != nil {
274+ arg1 = args[1].(uint64)
275+ }
276+ run(
277+ arg0,
278+ arg1,
279+ )
280+ })
281+ return _c
282+}
283+
284+func (_c *mocksurfaceRequests_setAcquire_Call) Return(err error) *mocksurfaceRequests_setAcquire_Call {
285+ _c.Call.Return(err)
286+ return _c
287+}
288+
289+func (_c *mocksurfaceRequests_setAcquire_Call) RunAndReturn(run func(timeline uint64, point uint64) error) *mocksurfaceRequests_setAcquire_Call {
290+ _c.Call.Return(run)
291+ return _c
292+}
293+
294+// setBufferScale provides a mock function for the type mocksurfaceRequests
295+func (_mock *mocksurfaceRequests) setBufferScale(n int32) error {
296+ ret := _mock.Called(n)
297+
298+ if len(ret) == 0 {
299+ panic("no return value specified for setBufferScale")
300+ }
301+
302+ var r0 error
303+ if returnFunc, ok := ret.Get(0).(func(int32) error); ok {
304+ r0 = returnFunc(n)
305+ } else {
306+ r0 = ret.Error(0)
307+ }
308+ return r0
309+}
310+
311+// mocksurfaceRequests_setBufferScale_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setBufferScale'
312+type mocksurfaceRequests_setBufferScale_Call struct {
313+ *mock.Call
314+}
315+
316+// setBufferScale is a helper method to define mock.On call
317+// - n int32
318+func (_e *mocksurfaceRequests_Expecter) setBufferScale(n any) *mocksurfaceRequests_setBufferScale_Call {
319+ return &mocksurfaceRequests_setBufferScale_Call{Call: _e.mock.On("setBufferScale", n)}
320+}
321+
322+func (_c *mocksurfaceRequests_setBufferScale_Call) Run(run func(n int32)) *mocksurfaceRequests_setBufferScale_Call {
323+ _c.Call.Run(func(args mock.Arguments) {
324+ var arg0 int32
325+ if args[0] != nil {
326+ arg0 = args[0].(int32)
327+ }
328+ run(
329+ arg0,
330+ )
331+ })
332+ return _c
333+}
334+
335+func (_c *mocksurfaceRequests_setBufferScale_Call) Return(err error) *mocksurfaceRequests_setBufferScale_Call {
336+ _c.Call.Return(err)
337+ return _c
338+}
339+
340+func (_c *mocksurfaceRequests_setBufferScale_Call) RunAndReturn(run func(n int32) error) *mocksurfaceRequests_setBufferScale_Call {
341+ _c.Call.Return(run)
342+ return _c
343+}
344+
345+// setDestination provides a mock function for the type mocksurfaceRequests
346+func (_mock *mocksurfaceRequests) setDestination(w int32, h int32) error {
347+ ret := _mock.Called(w, h)
348+
349+ if len(ret) == 0 {
350+ panic("no return value specified for setDestination")
351+ }
352+
353+ var r0 error
354+ if returnFunc, ok := ret.Get(0).(func(int32, int32) error); ok {
355+ r0 = returnFunc(w, h)
356+ } else {
357+ r0 = ret.Error(0)
358+ }
359+ return r0
360+}
361+
362+// mocksurfaceRequests_setDestination_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setDestination'
363+type mocksurfaceRequests_setDestination_Call struct {
364+ *mock.Call
365+}
366+
367+// setDestination is a helper method to define mock.On call
368+// - w int32
369+// - h int32
370+func (_e *mocksurfaceRequests_Expecter) setDestination(w any, h any) *mocksurfaceRequests_setDestination_Call {
371+ return &mocksurfaceRequests_setDestination_Call{Call: _e.mock.On("setDestination", w, h)}
372+}
373+
374+func (_c *mocksurfaceRequests_setDestination_Call) Run(run func(w int32, h int32)) *mocksurfaceRequests_setDestination_Call {
375+ _c.Call.Run(func(args mock.Arguments) {
376+ var arg0 int32
377+ if args[0] != nil {
378+ arg0 = args[0].(int32)
379+ }
380+ var arg1 int32
381+ if args[1] != nil {
382+ arg1 = args[1].(int32)
383+ }
384+ run(
385+ arg0,
386+ arg1,
387+ )
388+ })
389+ return _c
390+}
391+
392+func (_c *mocksurfaceRequests_setDestination_Call) Return(err error) *mocksurfaceRequests_setDestination_Call {
393+ _c.Call.Return(err)
394+ return _c
395+}
396+
397+func (_c *mocksurfaceRequests_setDestination_Call) RunAndReturn(run func(w int32, h int32) error) *mocksurfaceRequests_setDestination_Call {
398+ _c.Call.Return(run)
399+ return _c
400+}
401+
402+// setOpaqueRegion provides a mock function for the type mocksurfaceRequests
403+func (_mock *mocksurfaceRequests) setOpaqueRegion(w int32, h int32, opaque bool) error {
404+ ret := _mock.Called(w, h, opaque)
405+
406+ if len(ret) == 0 {
407+ panic("no return value specified for setOpaqueRegion")
408+ }
409+
410+ var r0 error
411+ if returnFunc, ok := ret.Get(0).(func(int32, int32, bool) error); ok {
412+ r0 = returnFunc(w, h, opaque)
413+ } else {
414+ r0 = ret.Error(0)
415+ }
416+ return r0
417+}
418+
419+// mocksurfaceRequests_setOpaqueRegion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setOpaqueRegion'
420+type mocksurfaceRequests_setOpaqueRegion_Call struct {
421+ *mock.Call
422+}
423+
424+// setOpaqueRegion is a helper method to define mock.On call
425+// - w int32
426+// - h int32
427+// - opaque bool
428+func (_e *mocksurfaceRequests_Expecter) setOpaqueRegion(w any, h any, opaque any) *mocksurfaceRequests_setOpaqueRegion_Call {
429+ return &mocksurfaceRequests_setOpaqueRegion_Call{Call: _e.mock.On("setOpaqueRegion", w, h, opaque)}
430+}
431+
432+func (_c *mocksurfaceRequests_setOpaqueRegion_Call) Run(run func(w int32, h int32, opaque bool)) *mocksurfaceRequests_setOpaqueRegion_Call {
433+ _c.Call.Run(func(args mock.Arguments) {
434+ var arg0 int32
435+ if args[0] != nil {
436+ arg0 = args[0].(int32)
437+ }
438+ var arg1 int32
439+ if args[1] != nil {
440+ arg1 = args[1].(int32)
441+ }
442+ var arg2 bool
443+ if args[2] != nil {
444+ arg2 = args[2].(bool)
445+ }
446+ run(
447+ arg0,
448+ arg1,
449+ arg2,
450+ )
451+ })
452+ return _c
453+}
454+
455+func (_c *mocksurfaceRequests_setOpaqueRegion_Call) Return(err error) *mocksurfaceRequests_setOpaqueRegion_Call {
456+ _c.Call.Return(err)
457+ return _c
458+}
459+
460+func (_c *mocksurfaceRequests_setOpaqueRegion_Call) RunAndReturn(run func(w int32, h int32, opaque bool) error) *mocksurfaceRequests_setOpaqueRegion_Call {
461+ _c.Call.Return(run)
462+ return _c
463+}
464+
465+// setRelease provides a mock function for the type mocksurfaceRequests
466+func (_mock *mocksurfaceRequests) setRelease(timeline uint64, point uint64) error {
467+ ret := _mock.Called(timeline, point)
468+
469+ if len(ret) == 0 {
470+ panic("no return value specified for setRelease")
471+ }
472+
473+ var r0 error
474+ if returnFunc, ok := ret.Get(0).(func(uint64, uint64) error); ok {
475+ r0 = returnFunc(timeline, point)
476+ } else {
477+ r0 = ret.Error(0)
478+ }
479+ return r0
480+}
481+
482+// mocksurfaceRequests_setRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setRelease'
483+type mocksurfaceRequests_setRelease_Call struct {
484+ *mock.Call
485+}
486+
487+// setRelease is a helper method to define mock.On call
488+// - timeline uint64
489+// - point uint64
490+func (_e *mocksurfaceRequests_Expecter) setRelease(timeline any, point any) *mocksurfaceRequests_setRelease_Call {
491+ return &mocksurfaceRequests_setRelease_Call{Call: _e.mock.On("setRelease", timeline, point)}
492+}
493+
494+func (_c *mocksurfaceRequests_setRelease_Call) Run(run func(timeline uint64, point uint64)) *mocksurfaceRequests_setRelease_Call {
495+ _c.Call.Run(func(args mock.Arguments) {
496+ var arg0 uint64
497+ if args[0] != nil {
498+ arg0 = args[0].(uint64)
499+ }
500+ var arg1 uint64
501+ if args[1] != nil {
502+ arg1 = args[1].(uint64)
503+ }
504+ run(
505+ arg0,
506+ arg1,
507+ )
508+ })
509+ return _c
510+}
511+
512+func (_c *mocksurfaceRequests_setRelease_Call) Return(err error) *mocksurfaceRequests_setRelease_Call {
513+ _c.Call.Return(err)
514+ return _c
515+}
516+
517+func (_c *mocksurfaceRequests_setRelease_Call) RunAndReturn(run func(timeline uint64, point uint64) error) *mocksurfaceRequests_setRelease_Call {
518+ _c.Call.Return(run)
519+ return _c
520+}
@@ -0,0 +1,520 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package neferclient
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// newMocksurfaceRequests creates a new instance of mocksurfaceRequests. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func newMocksurfaceRequests(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *mocksurfaceRequests {
17+ if helper, ok := t.(interface{ Helper() }); ok {
18+ helper.Helper()
19+ }
20+
21+ mock := &mocksurfaceRequests{}
22+ mock.Mock.Test(t)
23+
24+ t.Cleanup(func() {
25+ if helper, ok := t.(interface{ Helper() }); ok {
26+ helper.Helper()
27+ }
28+ mock.AssertExpectations(t)
29+ })
30+
31+ return mock
32+}
33+
34+// mocksurfaceRequests is an autogenerated mock type for the surfaceRequests type
35+type mocksurfaceRequests struct {
36+ mock.Mock
37+}
38+
39+type mocksurfaceRequests_Expecter struct {
40+ mock *mock.Mock
41+}
42+
43+func (_m *mocksurfaceRequests) EXPECT() *mocksurfaceRequests_Expecter {
44+ return &mocksurfaceRequests_Expecter{mock: &_m.Mock}
45+}
46+
47+// attach provides a mock function for the type mocksurfaceRequests
48+func (_mock *mocksurfaceRequests) attach(buffer uint64) error {
49+ ret := _mock.Called(buffer)
50+
51+ if len(ret) == 0 {
52+ panic("no return value specified for attach")
53+ }
54+
55+ var r0 error
56+ if returnFunc, ok := ret.Get(0).(func(uint64) error); ok {
57+ r0 = returnFunc(buffer)
58+ } else {
59+ r0 = ret.Error(0)
60+ }
61+ return r0
62+}
63+
64+// mocksurfaceRequests_attach_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'attach'
65+type mocksurfaceRequests_attach_Call struct {
66+ *mock.Call
67+}
68+
69+// attach is a helper method to define mock.On call
70+// - buffer uint64
71+func (_e *mocksurfaceRequests_Expecter) attach(buffer any) *mocksurfaceRequests_attach_Call {
72+ return &mocksurfaceRequests_attach_Call{Call: _e.mock.On("attach", buffer)}
73+}
74+
75+func (_c *mocksurfaceRequests_attach_Call) Run(run func(buffer uint64)) *mocksurfaceRequests_attach_Call {
76+ _c.Call.Run(func(args mock.Arguments) {
77+ var arg0 uint64
78+ if args[0] != nil {
79+ arg0 = args[0].(uint64)
80+ }
81+ run(
82+ arg0,
83+ )
84+ })
85+ return _c
86+}
87+
88+func (_c *mocksurfaceRequests_attach_Call) Return(err error) *mocksurfaceRequests_attach_Call {
89+ _c.Call.Return(err)
90+ return _c
91+}
92+
93+func (_c *mocksurfaceRequests_attach_Call) RunAndReturn(run func(buffer uint64) error) *mocksurfaceRequests_attach_Call {
94+ _c.Call.Return(run)
95+ return _c
96+}
97+
98+// commit provides a mock function for the type mocksurfaceRequests
99+func (_mock *mocksurfaceRequests) commit() error {
100+ ret := _mock.Called()
101+
102+ if len(ret) == 0 {
103+ panic("no return value specified for commit")
104+ }
105+
106+ var r0 error
107+ if returnFunc, ok := ret.Get(0).(func() error); ok {
108+ r0 = returnFunc()
109+ } else {
110+ r0 = ret.Error(0)
111+ }
112+ return r0
113+}
114+
115+// mocksurfaceRequests_commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'commit'
116+type mocksurfaceRequests_commit_Call struct {
117+ *mock.Call
118+}
119+
120+// commit is a helper method to define mock.On call
121+func (_e *mocksurfaceRequests_Expecter) commit() *mocksurfaceRequests_commit_Call {
122+ return &mocksurfaceRequests_commit_Call{Call: _e.mock.On("commit")}
123+}
124+
125+func (_c *mocksurfaceRequests_commit_Call) Run(run func()) *mocksurfaceRequests_commit_Call {
126+ _c.Call.Run(func(args mock.Arguments) {
127+ run()
128+ })
129+ return _c
130+}
131+
132+func (_c *mocksurfaceRequests_commit_Call) Return(err error) *mocksurfaceRequests_commit_Call {
133+ _c.Call.Return(err)
134+ return _c
135+}
136+
137+func (_c *mocksurfaceRequests_commit_Call) RunAndReturn(run func() error) *mocksurfaceRequests_commit_Call {
138+ _c.Call.Return(run)
139+ return _c
140+}
141+
142+// damageBuffer provides a mock function for the type mocksurfaceRequests
143+func (_mock *mocksurfaceRequests) damageBuffer(r Rect) error {
144+ ret := _mock.Called(r)
145+
146+ if len(ret) == 0 {
147+ panic("no return value specified for damageBuffer")
148+ }
149+
150+ var r0 error
151+ if returnFunc, ok := ret.Get(0).(func(Rect) error); ok {
152+ r0 = returnFunc(r)
153+ } else {
154+ r0 = ret.Error(0)
155+ }
156+ return r0
157+}
158+
159+// mocksurfaceRequests_damageBuffer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'damageBuffer'
160+type mocksurfaceRequests_damageBuffer_Call struct {
161+ *mock.Call
162+}
163+
164+// damageBuffer is a helper method to define mock.On call
165+// - r Rect
166+func (_e *mocksurfaceRequests_Expecter) damageBuffer(r any) *mocksurfaceRequests_damageBuffer_Call {
167+ return &mocksurfaceRequests_damageBuffer_Call{Call: _e.mock.On("damageBuffer", r)}
168+}
169+
170+func (_c *mocksurfaceRequests_damageBuffer_Call) Run(run func(r Rect)) *mocksurfaceRequests_damageBuffer_Call {
171+ _c.Call.Run(func(args mock.Arguments) {
172+ var arg0 Rect
173+ if args[0] != nil {
174+ arg0 = args[0].(Rect)
175+ }
176+ run(
177+ arg0,
178+ )
179+ })
180+ return _c
181+}
182+
183+func (_c *mocksurfaceRequests_damageBuffer_Call) Return(err error) *mocksurfaceRequests_damageBuffer_Call {
184+ _c.Call.Return(err)
185+ return _c
186+}
187+
188+func (_c *mocksurfaceRequests_damageBuffer_Call) RunAndReturn(run func(r Rect) error) *mocksurfaceRequests_damageBuffer_Call {
189+ _c.Call.Return(run)
190+ return _c
191+}
192+
193+// frame provides a mock function for the type mocksurfaceRequests
194+func (_mock *mocksurfaceRequests) frame() error {
195+ ret := _mock.Called()
196+
197+ if len(ret) == 0 {
198+ panic("no return value specified for frame")
199+ }
200+
201+ var r0 error
202+ if returnFunc, ok := ret.Get(0).(func() error); ok {
203+ r0 = returnFunc()
204+ } else {
205+ r0 = ret.Error(0)
206+ }
207+ return r0
208+}
209+
210+// mocksurfaceRequests_frame_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'frame'
211+type mocksurfaceRequests_frame_Call struct {
212+ *mock.Call
213+}
214+
215+// frame is a helper method to define mock.On call
216+func (_e *mocksurfaceRequests_Expecter) frame() *mocksurfaceRequests_frame_Call {
217+ return &mocksurfaceRequests_frame_Call{Call: _e.mock.On("frame")}
218+}
219+
220+func (_c *mocksurfaceRequests_frame_Call) Run(run func()) *mocksurfaceRequests_frame_Call {
221+ _c.Call.Run(func(args mock.Arguments) {
222+ run()
223+ })
224+ return _c
225+}
226+
227+func (_c *mocksurfaceRequests_frame_Call) Return(err error) *mocksurfaceRequests_frame_Call {
228+ _c.Call.Return(err)
229+ return _c
230+}
231+
232+func (_c *mocksurfaceRequests_frame_Call) RunAndReturn(run func() error) *mocksurfaceRequests_frame_Call {
233+ _c.Call.Return(run)
234+ return _c
235+}
236+
237+// setAcquire provides a mock function for the type mocksurfaceRequests
238+func (_mock *mocksurfaceRequests) setAcquire(timeline uint64, point uint64) error {
239+ ret := _mock.Called(timeline, point)
240+
241+ if len(ret) == 0 {
242+ panic("no return value specified for setAcquire")
243+ }
244+
245+ var r0 error
246+ if returnFunc, ok := ret.Get(0).(func(uint64, uint64) error); ok {
247+ r0 = returnFunc(timeline, point)
248+ } else {
249+ r0 = ret.Error(0)
250+ }
251+ return r0
252+}
253+
254+// mocksurfaceRequests_setAcquire_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setAcquire'
255+type mocksurfaceRequests_setAcquire_Call struct {
256+ *mock.Call
257+}
258+
259+// setAcquire is a helper method to define mock.On call
260+// - timeline uint64
261+// - point uint64
262+func (_e *mocksurfaceRequests_Expecter) setAcquire(timeline any, point any) *mocksurfaceRequests_setAcquire_Call {
263+ return &mocksurfaceRequests_setAcquire_Call{Call: _e.mock.On("setAcquire", timeline, point)}
264+}
265+
266+func (_c *mocksurfaceRequests_setAcquire_Call) Run(run func(timeline uint64, point uint64)) *mocksurfaceRequests_setAcquire_Call {
267+ _c.Call.Run(func(args mock.Arguments) {
268+ var arg0 uint64
269+ if args[0] != nil {
270+ arg0 = args[0].(uint64)
271+ }
272+ var arg1 uint64
273+ if args[1] != nil {
274+ arg1 = args[1].(uint64)
275+ }
276+ run(
277+ arg0,
278+ arg1,
279+ )
280+ })
281+ return _c
282+}
283+
284+func (_c *mocksurfaceRequests_setAcquire_Call) Return(err error) *mocksurfaceRequests_setAcquire_Call {
285+ _c.Call.Return(err)
286+ return _c
287+}
288+
289+func (_c *mocksurfaceRequests_setAcquire_Call) RunAndReturn(run func(timeline uint64, point uint64) error) *mocksurfaceRequests_setAcquire_Call {
290+ _c.Call.Return(run)
291+ return _c
292+}
293+
294+// setBufferScale provides a mock function for the type mocksurfaceRequests
295+func (_mock *mocksurfaceRequests) setBufferScale(n int32) error {
296+ ret := _mock.Called(n)
297+
298+ if len(ret) == 0 {
299+ panic("no return value specified for setBufferScale")
300+ }
301+
302+ var r0 error
303+ if returnFunc, ok := ret.Get(0).(func(int32) error); ok {
304+ r0 = returnFunc(n)
305+ } else {
306+ r0 = ret.Error(0)
307+ }
308+ return r0
309+}
310+
311+// mocksurfaceRequests_setBufferScale_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setBufferScale'
312+type mocksurfaceRequests_setBufferScale_Call struct {
313+ *mock.Call
314+}
315+
316+// setBufferScale is a helper method to define mock.On call
317+// - n int32
318+func (_e *mocksurfaceRequests_Expecter) setBufferScale(n any) *mocksurfaceRequests_setBufferScale_Call {
319+ return &mocksurfaceRequests_setBufferScale_Call{Call: _e.mock.On("setBufferScale", n)}
320+}
321+
322+func (_c *mocksurfaceRequests_setBufferScale_Call) Run(run func(n int32)) *mocksurfaceRequests_setBufferScale_Call {
323+ _c.Call.Run(func(args mock.Arguments) {
324+ var arg0 int32
325+ if args[0] != nil {
326+ arg0 = args[0].(int32)
327+ }
328+ run(
329+ arg0,
330+ )
331+ })
332+ return _c
333+}
334+
335+func (_c *mocksurfaceRequests_setBufferScale_Call) Return(err error) *mocksurfaceRequests_setBufferScale_Call {
336+ _c.Call.Return(err)
337+ return _c
338+}
339+
340+func (_c *mocksurfaceRequests_setBufferScale_Call) RunAndReturn(run func(n int32) error) *mocksurfaceRequests_setBufferScale_Call {
341+ _c.Call.Return(run)
342+ return _c
343+}
344+
345+// setDestination provides a mock function for the type mocksurfaceRequests
346+func (_mock *mocksurfaceRequests) setDestination(w int32, h int32) error {
347+ ret := _mock.Called(w, h)
348+
349+ if len(ret) == 0 {
350+ panic("no return value specified for setDestination")
351+ }
352+
353+ var r0 error
354+ if returnFunc, ok := ret.Get(0).(func(int32, int32) error); ok {
355+ r0 = returnFunc(w, h)
356+ } else {
357+ r0 = ret.Error(0)
358+ }
359+ return r0
360+}
361+
362+// mocksurfaceRequests_setDestination_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setDestination'
363+type mocksurfaceRequests_setDestination_Call struct {
364+ *mock.Call
365+}
366+
367+// setDestination is a helper method to define mock.On call
368+// - w int32
369+// - h int32
370+func (_e *mocksurfaceRequests_Expecter) setDestination(w any, h any) *mocksurfaceRequests_setDestination_Call {
371+ return &mocksurfaceRequests_setDestination_Call{Call: _e.mock.On("setDestination", w, h)}
372+}
373+
374+func (_c *mocksurfaceRequests_setDestination_Call) Run(run func(w int32, h int32)) *mocksurfaceRequests_setDestination_Call {
375+ _c.Call.Run(func(args mock.Arguments) {
376+ var arg0 int32
377+ if args[0] != nil {
378+ arg0 = args[0].(int32)
379+ }
380+ var arg1 int32
381+ if args[1] != nil {
382+ arg1 = args[1].(int32)
383+ }
384+ run(
385+ arg0,
386+ arg1,
387+ )
388+ })
389+ return _c
390+}
391+
392+func (_c *mocksurfaceRequests_setDestination_Call) Return(err error) *mocksurfaceRequests_setDestination_Call {
393+ _c.Call.Return(err)
394+ return _c
395+}
396+
397+func (_c *mocksurfaceRequests_setDestination_Call) RunAndReturn(run func(w int32, h int32) error) *mocksurfaceRequests_setDestination_Call {
398+ _c.Call.Return(run)
399+ return _c
400+}
401+
402+// setOpaqueRegion provides a mock function for the type mocksurfaceRequests
403+func (_mock *mocksurfaceRequests) setOpaqueRegion(w int32, h int32, opaque bool) error {
404+ ret := _mock.Called(w, h, opaque)
405+
406+ if len(ret) == 0 {
407+ panic("no return value specified for setOpaqueRegion")
408+ }
409+
410+ var r0 error
411+ if returnFunc, ok := ret.Get(0).(func(int32, int32, bool) error); ok {
412+ r0 = returnFunc(w, h, opaque)
413+ } else {
414+ r0 = ret.Error(0)
415+ }
416+ return r0
417+}
418+
419+// mocksurfaceRequests_setOpaqueRegion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setOpaqueRegion'
420+type mocksurfaceRequests_setOpaqueRegion_Call struct {
421+ *mock.Call
422+}
423+
424+// setOpaqueRegion is a helper method to define mock.On call
425+// - w int32
426+// - h int32
427+// - opaque bool
428+func (_e *mocksurfaceRequests_Expecter) setOpaqueRegion(w any, h any, opaque any) *mocksurfaceRequests_setOpaqueRegion_Call {
429+ return &mocksurfaceRequests_setOpaqueRegion_Call{Call: _e.mock.On("setOpaqueRegion", w, h, opaque)}
430+}
431+
432+func (_c *mocksurfaceRequests_setOpaqueRegion_Call) Run(run func(w int32, h int32, opaque bool)) *mocksurfaceRequests_setOpaqueRegion_Call {
433+ _c.Call.Run(func(args mock.Arguments) {
434+ var arg0 int32
435+ if args[0] != nil {
436+ arg0 = args[0].(int32)
437+ }
438+ var arg1 int32
439+ if args[1] != nil {
440+ arg1 = args[1].(int32)
441+ }
442+ var arg2 bool
443+ if args[2] != nil {
444+ arg2 = args[2].(bool)
445+ }
446+ run(
447+ arg0,
448+ arg1,
449+ arg2,
450+ )
451+ })
452+ return _c
453+}
454+
455+func (_c *mocksurfaceRequests_setOpaqueRegion_Call) Return(err error) *mocksurfaceRequests_setOpaqueRegion_Call {
456+ _c.Call.Return(err)
457+ return _c
458+}
459+
460+func (_c *mocksurfaceRequests_setOpaqueRegion_Call) RunAndReturn(run func(w int32, h int32, opaque bool) error) *mocksurfaceRequests_setOpaqueRegion_Call {
461+ _c.Call.Return(run)
462+ return _c
463+}
464+
465+// setRelease provides a mock function for the type mocksurfaceRequests
466+func (_mock *mocksurfaceRequests) setRelease(timeline uint64, point uint64) error {
467+ ret := _mock.Called(timeline, point)
468+
469+ if len(ret) == 0 {
470+ panic("no return value specified for setRelease")
471+ }
472+
473+ var r0 error
474+ if returnFunc, ok := ret.Get(0).(func(uint64, uint64) error); ok {
475+ r0 = returnFunc(timeline, point)
476+ } else {
477+ r0 = ret.Error(0)
478+ }
479+ return r0
480+}
481+
482+// mocksurfaceRequests_setRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setRelease'
483+type mocksurfaceRequests_setRelease_Call struct {
484+ *mock.Call
485+}
486+
487+// setRelease is a helper method to define mock.On call
488+// - timeline uint64
489+// - point uint64
490+func (_e *mocksurfaceRequests_Expecter) setRelease(timeline any, point any) *mocksurfaceRequests_setRelease_Call {
491+ return &mocksurfaceRequests_setRelease_Call{Call: _e.mock.On("setRelease", timeline, point)}
492+}
493+
494+func (_c *mocksurfaceRequests_setRelease_Call) Run(run func(timeline uint64, point uint64)) *mocksurfaceRequests_setRelease_Call {
495+ _c.Call.Run(func(args mock.Arguments) {
496+ var arg0 uint64
497+ if args[0] != nil {
498+ arg0 = args[0].(uint64)
499+ }
500+ var arg1 uint64
501+ if args[1] != nil {
502+ arg1 = args[1].(uint64)
503+ }
504+ run(
505+ arg0,
506+ arg1,
507+ )
508+ })
509+ return _c
510+}
511+
512+func (_c *mocksurfaceRequests_setRelease_Call) Return(err error) *mocksurfaceRequests_setRelease_Call {
513+ _c.Call.Return(err)
514+ return _c
515+}
516+
517+func (_c *mocksurfaceRequests_setRelease_Call) RunAndReturn(run func(timeline uint64, point uint64) error) *mocksurfaceRequests_setRelease_Call {
518+ _c.Call.Return(run)
519+ return _c
520+}
M wire_test.go +14 -2
@@ -30,6 +30,7 @@ type wireServer struct {
3030 globals []wireGlobal
3131 bound map[uint32]boundObject // global name -> bound object
3232 outputs map[uint32]outputSpec
33+ hook func(obj uint32, op uint16, body []byte) // other requests, if set
3334 }
3435
3536 type wireGlobal struct {
@@ -115,6 +116,7 @@ func isClosed(err error) bool {
115116
116117 func (s *wireServer) serve() {
117118 var hdr [8]byte
119+ var buf []byte // reused: the peer must not allocate while draining
118120 for {
119121 if _, err := io.ReadFull(s.conn, hdr[:]); err != nil {
120122 return
@@ -122,7 +124,10 @@ func (s *wireServer) serve() {
122124 obj := binary.LittleEndian.Uint32(hdr[0:4])
123125 w := binary.LittleEndian.Uint32(hdr[4:8])
124126 size, op := int(w>>16), uint16(w)
125- body := make([]byte, size-8)
127+ if cap(buf) < size-8 {
128+ buf = make([]byte, size-8)
129+ }
130+ body := buf[:size-8]
126131 if _, err := io.ReadFull(s.conn, body); err != nil {
127132 return
128133 }
@@ -165,8 +170,15 @@ func (s *wireServer) handle(obj uint32, op uint16, body []byte) {
165170 if isOutput {
166171 s.describe(id, version, spec)
167172 }
168- case op == 0 && body == nil: // wl_output.release (destructor, no args)
173+ case op == 0 && len(body) == 0: // wl_output.release (destructor, no args)
169174 s.write(frame(1, 1, appendU32(nil, obj)))
175+ default:
176+ s.mu.Lock()
177+ hook := s.hook
178+ s.mu.Unlock()
179+ if hook != nil {
180+ hook(obj, op, body)
181+ }
170182 }
171183 }
172184
@@ -30,6 +30,7 @@ type wireServer struct {
30 globals []wireGlobal30 globals []wireGlobal
31 bound map[uint32]boundObject // global name -> bound object31 bound map[uint32]boundObject // global name -> bound object
32 outputs map[uint32]outputSpec32 outputs map[uint32]outputSpec
33+ hook func(obj uint32, op uint16, body []byte) // other requests, if set
33 }34 }
34 35
35 type wireGlobal struct {36 type wireGlobal struct {
@@ -115,6 +116,7 @@ func isClosed(err error) bool {
115 116
116 func (s *wireServer) serve() {117 func (s *wireServer) serve() {
117 var hdr [8]byte118 var hdr [8]byte
119+ var buf []byte // reused: the peer must not allocate while draining
118 for {120 for {
119 if _, err := io.ReadFull(s.conn, hdr[:]); err != nil {121 if _, err := io.ReadFull(s.conn, hdr[:]); err != nil {
120 return122 return
@@ -122,7 +124,10 @@ func (s *wireServer) serve() {
122 obj := binary.LittleEndian.Uint32(hdr[0:4])124 obj := binary.LittleEndian.Uint32(hdr[0:4])
123 w := binary.LittleEndian.Uint32(hdr[4:8])125 w := binary.LittleEndian.Uint32(hdr[4:8])
124 size, op := int(w>>16), uint16(w)126 size, op := int(w>>16), uint16(w)
125- body := make([]byte, size-8)127+ if cap(buf) < size-8 {
128+ buf = make([]byte, size-8)
129+ }
130+ body := buf[:size-8]
126 if _, err := io.ReadFull(s.conn, body); err != nil {131 if _, err := io.ReadFull(s.conn, body); err != nil {
127 return132 return
128 }133 }
@@ -165,8 +170,15 @@ func (s *wireServer) handle(obj uint32, op uint16, body []byte) {
165 if isOutput {170 if isOutput {
166 s.describe(id, version, spec)171 s.describe(id, version, spec)
167 }172 }
168- case op == 0 && body == nil: // wl_output.release (destructor, no args)173+ case op == 0 && len(body) == 0: // wl_output.release (destructor, no args)
169 s.write(frame(1, 1, appendU32(nil, obj)))174 s.write(frame(1, 1, appendU32(nil, obj)))
175+ default:
176+ s.mu.Lock()
177+ hook := s.hook
178+ s.mu.Unlock()
179+ if hook != nil {
180+ hook(obj, op, body)
181+ }
170 }182 }
171 }183 }
172 184

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 1

Tags 4

↑↓ moveenter openesc close