refactor(xwm): replace addFD/after function seams with a mockery-generated loop interface
- commit
- 8fb84bf573f4a4c0f53ab2ffa1555c7a152973ea
- author
- brice · · 2026-10-22 16:51 UTC
- committer
- brice · · 2026-10-22 17:55 UTC
- parent
- 07dab5d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +253 -22
M .mockery.yml +5 -0
| @@ -212,3 +212,8 @@ packages: | |||
| 212 | 212 | dir: internal/adapters/wayland/xwm | |
| 213 | 213 | pkgname: xwm | |
| 214 | 214 | filename: fdsource_mock_test.go | |
| 215 | + | loop: | |
| 216 | + | config: | |
| 217 | + | dir: internal/adapters/wayland/xwm | |
| 218 | + | pkgname: xwm | |
| 219 | + | filename: loop_mock_test.go | |
| @@ -212,3 +212,8 @@ packages: | |||||
| 212 | dir: internal/adapters/wayland/xwm | 212 | dir: internal/adapters/wayland/xwm | ||
| 213 | pkgname: xwm | 213 | pkgname: xwm | ||
| 214 | filename: fdsource_mock_test.go | 214 | filename: fdsource_mock_test.go | ||
| 215 | + | loop: | |||
| 216 | + | config: | |||
| 217 | + | dir: internal/adapters/wayland/xwm | |||
| 218 | + | pkgname: xwm | |||
| 219 | + | filename: loop_mock_test.go | |||
A internal/adapters/wayland/xwm/loop_mock_test.go +180 -0
| @@ -0,0 +1,180 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package xwm | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | "time" | |
| 9 | + | ||
| 10 | + | mock "github.com/stretchr/testify/mock" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // newMockloop creates a new instance of mockloop. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 14 | + | // The first argument is typically a *testing.T value. | |
| 15 | + | func newMockloop(t interface { | |
| 16 | + | mock.TestingT | |
| 17 | + | Cleanup(func()) | |
| 18 | + | }) *mockloop { | |
| 19 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 20 | + | helper.Helper() | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | mock := &mockloop{} | |
| 24 | + | mock.Mock.Test(t) | |
| 25 | + | ||
| 26 | + | t.Cleanup(func() { | |
| 27 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 28 | + | helper.Helper() | |
| 29 | + | } | |
| 30 | + | mock.AssertExpectations(t) | |
| 31 | + | }) | |
| 32 | + | ||
| 33 | + | return mock | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | // mockloop is an autogenerated mock type for the loop type | |
| 37 | + | type mockloop struct { | |
| 38 | + | mock.Mock | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | type mockloop_Expecter struct { | |
| 42 | + | mock *mock.Mock | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | func (_m *mockloop) EXPECT() *mockloop_Expecter { | |
| 46 | + | return &mockloop_Expecter{mock: &_m.Mock} | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | // AddFD provides a mock function for the type mockloop | |
| 50 | + | func (_mock *mockloop) AddFD(fd int, onReadable func(), onWritable func()) (fdSource, error) { | |
| 51 | + | ret := _mock.Called(fd, onReadable, onWritable) | |
| 52 | + | ||
| 53 | + | if len(ret) == 0 { | |
| 54 | + | panic("no return value specified for AddFD") | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | var r0 fdSource | |
| 58 | + | var r1 error | |
| 59 | + | if returnFunc, ok := ret.Get(0).(func(int, func(), func()) (fdSource, error)); ok { | |
| 60 | + | return returnFunc(fd, onReadable, onWritable) | |
| 61 | + | } | |
| 62 | + | if returnFunc, ok := ret.Get(0).(func(int, func(), func()) fdSource); ok { | |
| 63 | + | r0 = returnFunc(fd, onReadable, onWritable) | |
| 64 | + | } else { | |
| 65 | + | if ret.Get(0) != nil { | |
| 66 | + | r0 = ret.Get(0).(fdSource) | |
| 67 | + | } | |
| 68 | + | } | |
| 69 | + | if returnFunc, ok := ret.Get(1).(func(int, func(), func()) error); ok { | |
| 70 | + | r1 = returnFunc(fd, onReadable, onWritable) | |
| 71 | + | } else { | |
| 72 | + | r1 = ret.Error(1) | |
| 73 | + | } | |
| 74 | + | return r0, r1 | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | // mockloop_AddFD_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddFD' | |
| 78 | + | type mockloop_AddFD_Call struct { | |
| 79 | + | *mock.Call | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | // AddFD is a helper method to define mock.On call | |
| 83 | + | // - fd int | |
| 84 | + | // - onReadable func() | |
| 85 | + | // - onWritable func() | |
| 86 | + | func (_e *mockloop_Expecter) AddFD(fd any, onReadable any, onWritable any) *mockloop_AddFD_Call { | |
| 87 | + | return &mockloop_AddFD_Call{Call: _e.mock.On("AddFD", fd, onReadable, onWritable)} | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | func (_c *mockloop_AddFD_Call) Run(run func(fd int, onReadable func(), onWritable func())) *mockloop_AddFD_Call { | |
| 91 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 92 | + | var arg0 int | |
| 93 | + | if args[0] != nil { | |
| 94 | + | arg0 = args[0].(int) | |
| 95 | + | } | |
| 96 | + | var arg1 func() | |
| 97 | + | if args[1] != nil { | |
| 98 | + | arg1 = args[1].(func()) | |
| 99 | + | } | |
| 100 | + | var arg2 func() | |
| 101 | + | if args[2] != nil { | |
| 102 | + | arg2 = args[2].(func()) | |
| 103 | + | } | |
| 104 | + | run( | |
| 105 | + | arg0, | |
| 106 | + | arg1, | |
| 107 | + | arg2, | |
| 108 | + | ) | |
| 109 | + | }) | |
| 110 | + | return _c | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | func (_c *mockloop_AddFD_Call) Return(fdSourceMoqParam fdSource, err error) *mockloop_AddFD_Call { | |
| 114 | + | _c.Call.Return(fdSourceMoqParam, err) | |
| 115 | + | return _c | |
| 116 | + | } | |
| 117 | + | ||
| 118 | + | func (_c *mockloop_AddFD_Call) RunAndReturn(run func(fd int, onReadable func(), onWritable func()) (fdSource, error)) *mockloop_AddFD_Call { | |
| 119 | + | _c.Call.Return(run) | |
| 120 | + | return _c | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | // After provides a mock function for the type mockloop | |
| 124 | + | func (_mock *mockloop) After(d time.Duration, fn func()) func() { | |
| 125 | + | ret := _mock.Called(d, fn) | |
| 126 | + | ||
| 127 | + | if len(ret) == 0 { | |
| 128 | + | panic("no return value specified for After") | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | var r0 func() | |
| 132 | + | if returnFunc, ok := ret.Get(0).(func(time.Duration, func()) func()); ok { | |
| 133 | + | r0 = returnFunc(d, fn) | |
| 134 | + | } else { | |
| 135 | + | if ret.Get(0) != nil { | |
| 136 | + | r0 = ret.Get(0).(func()) | |
| 137 | + | } | |
| 138 | + | } | |
| 139 | + | return r0 | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | // mockloop_After_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'After' | |
| 143 | + | type mockloop_After_Call struct { | |
| 144 | + | *mock.Call | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | // After is a helper method to define mock.On call | |
| 148 | + | // - d time.Duration | |
| 149 | + | // - fn func() | |
| 150 | + | func (_e *mockloop_Expecter) After(d any, fn any) *mockloop_After_Call { | |
| 151 | + | return &mockloop_After_Call{Call: _e.mock.On("After", d, fn)} | |
| 152 | + | } | |
| 153 | + | ||
| 154 | + | func (_c *mockloop_After_Call) Run(run func(d time.Duration, fn func())) *mockloop_After_Call { | |
| 155 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 156 | + | var arg0 time.Duration | |
| 157 | + | if args[0] != nil { | |
| 158 | + | arg0 = args[0].(time.Duration) | |
| 159 | + | } | |
| 160 | + | var arg1 func() | |
| 161 | + | if args[1] != nil { | |
| 162 | + | arg1 = args[1].(func()) | |
| 163 | + | } | |
| 164 | + | run( | |
| 165 | + | arg0, | |
| 166 | + | arg1, | |
| 167 | + | ) | |
| 168 | + | }) | |
| 169 | + | return _c | |
| 170 | + | } | |
| 171 | + | ||
| 172 | + | func (_c *mockloop_After_Call) Return(cancel func()) *mockloop_After_Call { | |
| 173 | + | _c.Call.Return(cancel) | |
| 174 | + | return _c | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | func (_c *mockloop_After_Call) RunAndReturn(run func(d time.Duration, fn func()) func()) *mockloop_After_Call { | |
| 178 | + | _c.Call.Return(run) | |
| 179 | + | return _c | |
| 180 | + | } | |
| @@ -0,0 +1,180 @@ | |||||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |||
| 2 | + | // github.com/vektra/mockery | |||
| 3 | + | // template: testify | |||
| 4 | + | ||||
| 5 | + | package xwm | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | "time" | |||
| 9 | + | ||||
| 10 | + | mock "github.com/stretchr/testify/mock" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // newMockloop creates a new instance of mockloop. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 14 | + | // The first argument is typically a *testing.T value. | |||
| 15 | + | func newMockloop(t interface { | |||
| 16 | + | mock.TestingT | |||
| 17 | + | Cleanup(func()) | |||
| 18 | + | }) *mockloop { | |||
| 19 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 20 | + | helper.Helper() | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | mock := &mockloop{} | |||
| 24 | + | mock.Mock.Test(t) | |||
| 25 | + | ||||
| 26 | + | t.Cleanup(func() { | |||
| 27 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 28 | + | helper.Helper() | |||
| 29 | + | } | |||
| 30 | + | mock.AssertExpectations(t) | |||
| 31 | + | }) | |||
| 32 | + | ||||
| 33 | + | return mock | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | // mockloop is an autogenerated mock type for the loop type | |||
| 37 | + | type mockloop struct { | |||
| 38 | + | mock.Mock | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | type mockloop_Expecter struct { | |||
| 42 | + | mock *mock.Mock | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | func (_m *mockloop) EXPECT() *mockloop_Expecter { | |||
| 46 | + | return &mockloop_Expecter{mock: &_m.Mock} | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | // AddFD provides a mock function for the type mockloop | |||
| 50 | + | func (_mock *mockloop) AddFD(fd int, onReadable func(), onWritable func()) (fdSource, error) { | |||
| 51 | + | ret := _mock.Called(fd, onReadable, onWritable) | |||
| 52 | + | ||||
| 53 | + | if len(ret) == 0 { | |||
| 54 | + | panic("no return value specified for AddFD") | |||
| 55 | + | } | |||
| 56 | + | ||||
| 57 | + | var r0 fdSource | |||
| 58 | + | var r1 error | |||
| 59 | + | if returnFunc, ok := ret.Get(0).(func(int, func(), func()) (fdSource, error)); ok { | |||
| 60 | + | return returnFunc(fd, onReadable, onWritable) | |||
| 61 | + | } | |||
| 62 | + | if returnFunc, ok := ret.Get(0).(func(int, func(), func()) fdSource); ok { | |||
| 63 | + | r0 = returnFunc(fd, onReadable, onWritable) | |||
| 64 | + | } else { | |||
| 65 | + | if ret.Get(0) != nil { | |||
| 66 | + | r0 = ret.Get(0).(fdSource) | |||
| 67 | + | } | |||
| 68 | + | } | |||
| 69 | + | if returnFunc, ok := ret.Get(1).(func(int, func(), func()) error); ok { | |||
| 70 | + | r1 = returnFunc(fd, onReadable, onWritable) | |||
| 71 | + | } else { | |||
| 72 | + | r1 = ret.Error(1) | |||
| 73 | + | } | |||
| 74 | + | return r0, r1 | |||
| 75 | + | } | |||
| 76 | + | ||||
| 77 | + | // mockloop_AddFD_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddFD' | |||
| 78 | + | type mockloop_AddFD_Call struct { | |||
| 79 | + | *mock.Call | |||
| 80 | + | } | |||
| 81 | + | ||||
| 82 | + | // AddFD is a helper method to define mock.On call | |||
| 83 | + | // - fd int | |||
| 84 | + | // - onReadable func() | |||
| 85 | + | // - onWritable func() | |||
| 86 | + | func (_e *mockloop_Expecter) AddFD(fd any, onReadable any, onWritable any) *mockloop_AddFD_Call { | |||
| 87 | + | return &mockloop_AddFD_Call{Call: _e.mock.On("AddFD", fd, onReadable, onWritable)} | |||
| 88 | + | } | |||
| 89 | + | ||||
| 90 | + | func (_c *mockloop_AddFD_Call) Run(run func(fd int, onReadable func(), onWritable func())) *mockloop_AddFD_Call { | |||
| 91 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 92 | + | var arg0 int | |||
| 93 | + | if args[0] != nil { | |||
| 94 | + | arg0 = args[0].(int) | |||
| 95 | + | } | |||
| 96 | + | var arg1 func() | |||
| 97 | + | if args[1] != nil { | |||
| 98 | + | arg1 = args[1].(func()) | |||
| 99 | + | } | |||
| 100 | + | var arg2 func() | |||
| 101 | + | if args[2] != nil { | |||
| 102 | + | arg2 = args[2].(func()) | |||
| 103 | + | } | |||
| 104 | + | run( | |||
| 105 | + | arg0, | |||
| 106 | + | arg1, | |||
| 107 | + | arg2, | |||
| 108 | + | ) | |||
| 109 | + | }) | |||
| 110 | + | return _c | |||
| 111 | + | } | |||
| 112 | + | ||||
| 113 | + | func (_c *mockloop_AddFD_Call) Return(fdSourceMoqParam fdSource, err error) *mockloop_AddFD_Call { | |||
| 114 | + | _c.Call.Return(fdSourceMoqParam, err) | |||
| 115 | + | return _c | |||
| 116 | + | } | |||
| 117 | + | ||||
| 118 | + | func (_c *mockloop_AddFD_Call) RunAndReturn(run func(fd int, onReadable func(), onWritable func()) (fdSource, error)) *mockloop_AddFD_Call { | |||
| 119 | + | _c.Call.Return(run) | |||
| 120 | + | return _c | |||
| 121 | + | } | |||
| 122 | + | ||||
| 123 | + | // After provides a mock function for the type mockloop | |||
| 124 | + | func (_mock *mockloop) After(d time.Duration, fn func()) func() { | |||
| 125 | + | ret := _mock.Called(d, fn) | |||
| 126 | + | ||||
| 127 | + | if len(ret) == 0 { | |||
| 128 | + | panic("no return value specified for After") | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | var r0 func() | |||
| 132 | + | if returnFunc, ok := ret.Get(0).(func(time.Duration, func()) func()); ok { | |||
| 133 | + | r0 = returnFunc(d, fn) | |||
| 134 | + | } else { | |||
| 135 | + | if ret.Get(0) != nil { | |||
| 136 | + | r0 = ret.Get(0).(func()) | |||
| 137 | + | } | |||
| 138 | + | } | |||
| 139 | + | return r0 | |||
| 140 | + | } | |||
| 141 | + | ||||
| 142 | + | // mockloop_After_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'After' | |||
| 143 | + | type mockloop_After_Call struct { | |||
| 144 | + | *mock.Call | |||
| 145 | + | } | |||
| 146 | + | ||||
| 147 | + | // After is a helper method to define mock.On call | |||
| 148 | + | // - d time.Duration | |||
| 149 | + | // - fn func() | |||
| 150 | + | func (_e *mockloop_Expecter) After(d any, fn any) *mockloop_After_Call { | |||
| 151 | + | return &mockloop_After_Call{Call: _e.mock.On("After", d, fn)} | |||
| 152 | + | } | |||
| 153 | + | ||||
| 154 | + | func (_c *mockloop_After_Call) Run(run func(d time.Duration, fn func())) *mockloop_After_Call { | |||
| 155 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 156 | + | var arg0 time.Duration | |||
| 157 | + | if args[0] != nil { | |||
| 158 | + | arg0 = args[0].(time.Duration) | |||
| 159 | + | } | |||
| 160 | + | var arg1 func() | |||
| 161 | + | if args[1] != nil { | |||
| 162 | + | arg1 = args[1].(func()) | |||
| 163 | + | } | |||
| 164 | + | run( | |||
| 165 | + | arg0, | |||
| 166 | + | arg1, | |||
| 167 | + | ) | |||
| 168 | + | }) | |||
| 169 | + | return _c | |||
| 170 | + | } | |||
| 171 | + | ||||
| 172 | + | func (_c *mockloop_After_Call) Return(cancel func()) *mockloop_After_Call { | |||
| 173 | + | _c.Call.Return(cancel) | |||
| 174 | + | return _c | |||
| 175 | + | } | |||
| 176 | + | ||||
| 177 | + | func (_c *mockloop_After_Call) RunAndReturn(run func(d time.Duration, fn func()) func()) *mockloop_After_Call { | |||
| 178 | + | _c.Call.Return(run) | |||
| 179 | + | return _c | |||
| 180 | + | } | |||
M internal/adapters/wayland/xwm/selection.go +6 -6
| @@ -489,13 +489,13 @@ func (x *XWM) inDrain(i int) { | |||
| 489 | 489 | func (x *XWM) inWatch(i int, on bool) { | |
| 490 | 490 | t := &x.sel.in[i] | |
| 491 | 491 | if t.src == nil { | |
| 492 | - | if !on || x.addFD == nil { | |
| 492 | + | if !on || x.lp == nil { | |
| 493 | 493 | if on { | |
| 494 | 494 | x.inAbort(i, true) | |
| 495 | 495 | } | |
| 496 | 496 | return | |
| 497 | 497 | } | |
| 498 | - | src, err := x.addFD(t.fd, t.onR, t.onW) | |
| 498 | + | src, err := x.lp.AddFD(t.fd, t.onR, t.onW) | |
| 499 | 499 | if err != nil { | |
| 500 | 500 | x.inAbort(i, true) | |
| 501 | 501 | return | |
| @@ -573,11 +573,11 @@ func (x *XWM) onSelError(e *xturbo.XError) { | |||
| 573 | 573 | ||
| 574 | 574 | func (x *XWM) selEnsureTimer() { | |
| 575 | 575 | b := &x.sel | |
| 576 | - | if b.timerOn || x.after == nil { | |
| 576 | + | if b.timerOn || x.lp == nil { | |
| 577 | 577 | return | |
| 578 | 578 | } | |
| 579 | 579 | b.timerOn = true | |
| 580 | - | b.cancel = x.after(tickEvery, b.tickFn) | |
| 580 | + | b.cancel = x.lp.After(tickEvery, b.tickFn) | |
| 581 | 581 | } | |
| 582 | 582 | ||
| 583 | 583 | // selTick ages every transfer by one second and aborts the stalled ones. | |
| @@ -855,11 +855,11 @@ func (x *XWM) outWatch(i int) { | |||
| 855 | 855 | if o.src != nil { | |
| 856 | 856 | return | |
| 857 | 857 | } | |
| 858 | - | if x.addFD == nil { | |
| 858 | + | if x.lp == nil { | |
| 859 | 859 | x.outAbort(i) | |
| 860 | 860 | return | |
| 861 | 861 | } | |
| 862 | - | src, err := x.addFD(o.fd, o.onR, nil) | |
| 862 | + | src, err := x.lp.AddFD(o.fd, o.onR, nil) | |
| 863 | 863 | if err != nil { | |
| 864 | 864 | x.outAbort(i) | |
| 865 | 865 | return | |
| @@ -489,13 +489,13 @@ func (x *XWM) inDrain(i int) { | |||||
| 489 | func (x *XWM) inWatch(i int, on bool) { | 489 | func (x *XWM) inWatch(i int, on bool) { | ||
| 490 | t := &x.sel.in[i] | 490 | t := &x.sel.in[i] | ||
| 491 | if t.src == nil { | 491 | if t.src == nil { | ||
| 492 | - | if !on || x.addFD == nil { | 492 | + | if !on || x.lp == nil { |
| 493 | if on { | 493 | if on { | ||
| 494 | x.inAbort(i, true) | 494 | x.inAbort(i, true) | ||
| 495 | } | 495 | } | ||
| 496 | return | 496 | return | ||
| 497 | } | 497 | } | ||
| 498 | - | src, err := x.addFD(t.fd, t.onR, t.onW) | 498 | + | src, err := x.lp.AddFD(t.fd, t.onR, t.onW) |
| 499 | if err != nil { | 499 | if err != nil { | ||
| 500 | x.inAbort(i, true) | 500 | x.inAbort(i, true) | ||
| 501 | return | 501 | return | ||
| @@ -573,11 +573,11 @@ func (x *XWM) onSelError(e *xturbo.XError) { | |||||
| 573 | 573 | ||||
| 574 | func (x *XWM) selEnsureTimer() { | 574 | func (x *XWM) selEnsureTimer() { | ||
| 575 | b := &x.sel | 575 | b := &x.sel | ||
| 576 | - | if b.timerOn || x.after == nil { | 576 | + | if b.timerOn || x.lp == nil { |
| 577 | return | 577 | return | ||
| 578 | } | 578 | } | ||
| 579 | b.timerOn = true | 579 | b.timerOn = true | ||
| 580 | - | b.cancel = x.after(tickEvery, b.tickFn) | 580 | + | b.cancel = x.lp.After(tickEvery, b.tickFn) |
| 581 | } | 581 | } | ||
| 582 | 582 | ||||
| 583 | // selTick ages every transfer by one second and aborts the stalled ones. | 583 | // selTick ages every transfer by one second and aborts the stalled ones. | ||
| @@ -855,11 +855,11 @@ func (x *XWM) outWatch(i int) { | |||||
| 855 | if o.src != nil { | 855 | if o.src != nil { | ||
| 856 | return | 856 | return | ||
| 857 | } | 857 | } | ||
| 858 | - | if x.addFD == nil { | 858 | + | if x.lp == nil { |
| 859 | x.outAbort(i) | 859 | x.outAbort(i) | ||
| 860 | return | 860 | return | ||
| 861 | } | 861 | } | ||
| 862 | - | src, err := x.addFD(o.fd, o.onR, nil) | 862 | + | src, err := x.lp.AddFD(o.fd, o.onR, nil) |
| 863 | if err != nil { | 863 | if err != nil { | ||
| 864 | x.outAbort(i) | 864 | x.outAbort(i) | ||
| 865 | return | 865 | return | ||
M internal/adapters/wayland/xwm/selection_test.go +33 -6
| @@ -252,7 +252,7 @@ func TestSelectionXToWaylandBackpressure(t *testing.T) { | |||
| 252 | 252 | r := ownedRig(t) | |
| 253 | 253 | src := newMockfdSource(t) | |
| 254 | 254 | var onW func() | |
| 255 | - | r.x.addFD = func(_ int, _, w func()) (fdSource, error) { onW = w; return src, nil } | |
| 255 | + | r.x.lp = newFDLoop(t, r, src, nil, &onW) | |
| 256 | 256 | src.EXPECT().SetWritable(true).Return(nil).Once() | |
| 257 | 257 | src.EXPECT().SetWritable(false).Return(nil).Once() | |
| 258 | 258 | src.EXPECT().Remove().Return().Once() | |
| @@ -337,7 +337,7 @@ func TestSelectionPoolExhausted(t *testing.T) { | |||
| 337 | 337 | func allocRig(t *testing.T) *selRig { | |
| 338 | 338 | r := ownedRig(t) | |
| 339 | 339 | sinkPeer(t, r.x, r.p) | |
| 340 | - | r.x.after = func(time.Duration, func()) func() { return func() {} } | |
| 340 | + | r.x.lp = newTimerLoop(t) | |
| 341 | 341 | r.x.sel.timerOn = true // no timer re-arming in the measured loop | |
| 342 | 342 | return r | |
| 343 | 343 | } | |
| @@ -515,7 +515,7 @@ func TestSelectionWaylandToXINCR(t *testing.T) { | |||
| 515 | 515 | src := newMockfdSource(t) | |
| 516 | 516 | var onR func() | |
| 517 | 517 | removes := 0 | |
| 518 | - | r.x.addFD = func(_ int, rd, _ func()) (fdSource, error) { onR = rd; return src, nil } | |
| 518 | + | r.x.lp = newFDLoop(t, r, src, &onR, nil) | |
| 519 | 519 | src.EXPECT().Remove().Run(func() { removes++ }).Return().Maybe() | |
| 520 | 520 | const total = 1<<20 + 123 | |
| 521 | 521 | data := pattern(total) | |
| @@ -591,7 +591,7 @@ func TestSelectionWaylandToXTimeoutAndClear(t *testing.T) { | |||
| 591 | 591 | r := ownedByWayland(t, "text/plain;charset=utf-8") | |
| 592 | 592 | src := newMockfdSource(t) | |
| 593 | 593 | src.EXPECT().Remove().Return().Maybe() | |
| 594 | - | r.x.addFD = func(_ int, _, _ func()) (fdSource, error) { return src, nil } | |
| 594 | + | r.x.lp = newFDLoop(t, r, src, nil, nil) | |
| 595 | 595 | w := r.serve(t, r.utf8) | |
| 596 | 596 | _, err := w.Write(pattern(chunk)) // a full chunk: INCR | |
| 597 | 597 | require.NoError(t, err) | |
| @@ -622,7 +622,7 @@ func TestSelectionClearAndRace(t *testing.T) { | |||
| 622 | 622 | r := ownedByWayland(t, "text/plain;charset=utf-8") | |
| 623 | 623 | src := newMockfdSource(t) | |
| 624 | 624 | src.EXPECT().Remove().Return().Maybe() | |
| 625 | - | r.x.addFD = func(_ int, _, _ func()) (fdSource, error) { return src, nil } | |
| 625 | + | r.x.lp = newFDLoop(t, r, src, nil, nil) | |
| 626 | 626 | w := r.serve(t, r.utf8) | |
| 627 | 627 | _, err := w.Write(pattern(chunk)) | |
| 628 | 628 | require.NoError(t, err) | |
| @@ -640,7 +640,7 @@ func TestSelectionCopyAllocationsWaylandToX(t *testing.T) { | |||
| 640 | 640 | r := ownedByWayland(t, "text/plain;charset=utf-8") | |
| 641 | 641 | x := r.x | |
| 642 | 642 | sinkPeer(t, x, r.p) | |
| 643 | - | x.after = func(time.Duration, func()) func() { return func() {} } | |
| 643 | + | x.lp = newTimerLoop(t) | |
| 644 | 644 | x.sel.timerOn = true | |
| 645 | 645 | data := pattern(4096) | |
| 646 | 646 | var ev xturbo.Event | |
| @@ -708,3 +708,30 @@ func TestSelectionStaleTargetsAfterWaylandOwner(t *testing.T) { | |||
| 708 | 708 | require.Zero(t, getProps(p, 0, x.selWin), "the stale TARGETS reply is not fetched") | |
| 709 | 709 | _ = h | |
| 710 | 710 | } | |
| 711 | + | ||
| 712 | + | // newFDLoop is a loop whose AddFD hands out src and captures the callbacks; | |
| 713 | + | // timers are inert. | |
| 714 | + | func newFDLoop(t *testing.T, rig *selRig, src fdSource, onR, onW *func()) *mockloop { | |
| 715 | + | l := newMockloop(t) | |
| 716 | + | l.EXPECT().AddFD(mock.Anything, mock.Anything, mock.Anything).RunAndReturn( | |
| 717 | + | func(_ int, r, w func()) (fdSource, error) { | |
| 718 | + | if onR != nil { | |
| 719 | + | *onR = r | |
| 720 | + | } | |
| 721 | + | if onW != nil { | |
| 722 | + | *onW = w | |
| 723 | + | } | |
| 724 | + | return src, nil | |
| 725 | + | }).Maybe() | |
| 726 | + | l.EXPECT().After(mock.Anything, mock.Anything).RunAndReturn( | |
| 727 | + | func(_ time.Duration, fn func()) func() { rig.timer = fn; return func() {} }).Maybe() | |
| 728 | + | return l | |
| 729 | + | } | |
| 730 | + | ||
| 731 | + | // newTimerLoop is a loop with inert timers. | |
| 732 | + | func newTimerLoop(t *testing.T) *mockloop { | |
| 733 | + | l := newMockloop(t) | |
| 734 | + | l.EXPECT().After(mock.Anything, mock.Anything).RunAndReturn( | |
| 735 | + | func(time.Duration, func()) func() { return func() {} }).Maybe() | |
| 736 | + | return l | |
| 737 | + | } | |
| @@ -252,7 +252,7 @@ func TestSelectionXToWaylandBackpressure(t *testing.T) { | |||||
| 252 | r := ownedRig(t) | 252 | r := ownedRig(t) | ||
| 253 | src := newMockfdSource(t) | 253 | src := newMockfdSource(t) | ||
| 254 | var onW func() | 254 | var onW func() | ||
| 255 | - | r.x.addFD = func(_ int, _, w func()) (fdSource, error) { onW = w; return src, nil } | 255 | + | r.x.lp = newFDLoop(t, r, src, nil, &onW) |
| 256 | src.EXPECT().SetWritable(true).Return(nil).Once() | 256 | src.EXPECT().SetWritable(true).Return(nil).Once() | ||
| 257 | src.EXPECT().SetWritable(false).Return(nil).Once() | 257 | src.EXPECT().SetWritable(false).Return(nil).Once() | ||
| 258 | src.EXPECT().Remove().Return().Once() | 258 | src.EXPECT().Remove().Return().Once() | ||
| @@ -337,7 +337,7 @@ func TestSelectionPoolExhausted(t *testing.T) { | |||||
| 337 | func allocRig(t *testing.T) *selRig { | 337 | func allocRig(t *testing.T) *selRig { | ||
| 338 | r := ownedRig(t) | 338 | r := ownedRig(t) | ||
| 339 | sinkPeer(t, r.x, r.p) | 339 | sinkPeer(t, r.x, r.p) | ||
| 340 | - | r.x.after = func(time.Duration, func()) func() { return func() {} } | 340 | + | r.x.lp = newTimerLoop(t) |
| 341 | r.x.sel.timerOn = true // no timer re-arming in the measured loop | 341 | r.x.sel.timerOn = true // no timer re-arming in the measured loop | ||
| 342 | return r | 342 | return r | ||
| 343 | } | 343 | } | ||
| @@ -515,7 +515,7 @@ func TestSelectionWaylandToXINCR(t *testing.T) { | |||||
| 515 | src := newMockfdSource(t) | 515 | src := newMockfdSource(t) | ||
| 516 | var onR func() | 516 | var onR func() | ||
| 517 | removes := 0 | 517 | removes := 0 | ||
| 518 | - | r.x.addFD = func(_ int, rd, _ func()) (fdSource, error) { onR = rd; return src, nil } | 518 | + | r.x.lp = newFDLoop(t, r, src, &onR, nil) |
| 519 | src.EXPECT().Remove().Run(func() { removes++ }).Return().Maybe() | 519 | src.EXPECT().Remove().Run(func() { removes++ }).Return().Maybe() | ||
| 520 | const total = 1<<20 + 123 | 520 | const total = 1<<20 + 123 | ||
| 521 | data := pattern(total) | 521 | data := pattern(total) | ||
| @@ -591,7 +591,7 @@ func TestSelectionWaylandToXTimeoutAndClear(t *testing.T) { | |||||
| 591 | r := ownedByWayland(t, "text/plain;charset=utf-8") | 591 | r := ownedByWayland(t, "text/plain;charset=utf-8") | ||
| 592 | src := newMockfdSource(t) | 592 | src := newMockfdSource(t) | ||
| 593 | src.EXPECT().Remove().Return().Maybe() | 593 | src.EXPECT().Remove().Return().Maybe() | ||
| 594 | - | r.x.addFD = func(_ int, _, _ func()) (fdSource, error) { return src, nil } | 594 | + | r.x.lp = newFDLoop(t, r, src, nil, nil) |
| 595 | w := r.serve(t, r.utf8) | 595 | w := r.serve(t, r.utf8) | ||
| 596 | _, err := w.Write(pattern(chunk)) // a full chunk: INCR | 596 | _, err := w.Write(pattern(chunk)) // a full chunk: INCR | ||
| 597 | require.NoError(t, err) | 597 | require.NoError(t, err) | ||
| @@ -622,7 +622,7 @@ func TestSelectionClearAndRace(t *testing.T) { | |||||
| 622 | r := ownedByWayland(t, "text/plain;charset=utf-8") | 622 | r := ownedByWayland(t, "text/plain;charset=utf-8") | ||
| 623 | src := newMockfdSource(t) | 623 | src := newMockfdSource(t) | ||
| 624 | src.EXPECT().Remove().Return().Maybe() | 624 | src.EXPECT().Remove().Return().Maybe() | ||
| 625 | - | r.x.addFD = func(_ int, _, _ func()) (fdSource, error) { return src, nil } | 625 | + | r.x.lp = newFDLoop(t, r, src, nil, nil) |
| 626 | w := r.serve(t, r.utf8) | 626 | w := r.serve(t, r.utf8) | ||
| 627 | _, err := w.Write(pattern(chunk)) | 627 | _, err := w.Write(pattern(chunk)) | ||
| 628 | require.NoError(t, err) | 628 | require.NoError(t, err) | ||
| @@ -640,7 +640,7 @@ func TestSelectionCopyAllocationsWaylandToX(t *testing.T) { | |||||
| 640 | r := ownedByWayland(t, "text/plain;charset=utf-8") | 640 | r := ownedByWayland(t, "text/plain;charset=utf-8") | ||
| 641 | x := r.x | 641 | x := r.x | ||
| 642 | sinkPeer(t, x, r.p) | 642 | sinkPeer(t, x, r.p) | ||
| 643 | - | x.after = func(time.Duration, func()) func() { return func() {} } | 643 | + | x.lp = newTimerLoop(t) |
| 644 | x.sel.timerOn = true | 644 | x.sel.timerOn = true | ||
| 645 | data := pattern(4096) | 645 | data := pattern(4096) | ||
| 646 | var ev xturbo.Event | 646 | var ev xturbo.Event | ||
| @@ -708,3 +708,30 @@ func TestSelectionStaleTargetsAfterWaylandOwner(t *testing.T) { | |||||
| 708 | require.Zero(t, getProps(p, 0, x.selWin), "the stale TARGETS reply is not fetched") | 708 | require.Zero(t, getProps(p, 0, x.selWin), "the stale TARGETS reply is not fetched") | ||
| 709 | _ = h | 709 | _ = h | ||
| 710 | } | 710 | } | ||
| 711 | + | ||||
| 712 | + | // newFDLoop is a loop whose AddFD hands out src and captures the callbacks; | |||
| 713 | + | // timers are inert. | |||
| 714 | + | func newFDLoop(t *testing.T, rig *selRig, src fdSource, onR, onW *func()) *mockloop { | |||
| 715 | + | l := newMockloop(t) | |||
| 716 | + | l.EXPECT().AddFD(mock.Anything, mock.Anything, mock.Anything).RunAndReturn( | |||
| 717 | + | func(_ int, r, w func()) (fdSource, error) { | |||
| 718 | + | if onR != nil { | |||
| 719 | + | *onR = r | |||
| 720 | + | } | |||
| 721 | + | if onW != nil { | |||
| 722 | + | *onW = w | |||
| 723 | + | } | |||
| 724 | + | return src, nil | |||
| 725 | + | }).Maybe() | |||
| 726 | + | l.EXPECT().After(mock.Anything, mock.Anything).RunAndReturn( | |||
| 727 | + | func(_ time.Duration, fn func()) func() { rig.timer = fn; return func() {} }).Maybe() | |||
| 728 | + | return l | |||
| 729 | + | } | |||
| 730 | + | ||||
| 731 | + | // newTimerLoop is a loop with inert timers. | |||
| 732 | + | func newTimerLoop(t *testing.T) *mockloop { | |||
| 733 | + | l := newMockloop(t) | |||
| 734 | + | l.EXPECT().After(mock.Anything, mock.Anything).RunAndReturn( | |||
| 735 | + | func(time.Duration, func()) func() { return func() {} }).Maybe() | |||
| 736 | + | return l | |||
| 737 | + | } | |||
M internal/adapters/wayland/xwm/xwm.go +29 -10
| @@ -66,8 +66,7 @@ type XWM struct { | |||
| 66 | 66 | // attachment | |
| 67 | 67 | host host | |
| 68 | 68 | src fdSource | |
| 69 | - | addFD func(fd int, onReadable, onWritable func()) (fdSource, error) // display fd source seam | |
| 70 | - | after func(d time.Duration, fn func()) (cancel func()) | |
| 69 | + | lp loop // display fd sources and timers | |
| 71 | 70 | post func(func()) | |
| 72 | 71 | flushFn func() | |
| 73 | 72 | ||
| @@ -331,25 +330,45 @@ func (x *XWM) boot() error { | |||
| 331 | 330 | // Attach registers the X socket with the display and hands the windows found | |
| 332 | 331 | // at boot to h. Display goroutine only. | |
| 333 | 332 | func (x *XWM) Attach(d *server.Display, h host) error { | |
| 334 | - | x.addFD = func(fd int, r, w func()) (fdSource, error) { | |
| 335 | - | s, err := d.AddFDRW(fd, r, w) | |
| 336 | - | if err != nil { | |
| 337 | - | return nil, err | |
| 338 | - | } | |
| 339 | - | return s, nil | |
| 340 | - | } | |
| 341 | 333 | src, err := d.AddFDRW(x.conn.Fd(), x.onReadable, x.onWritable) | |
| 342 | 334 | if err != nil { | |
| 343 | 335 | return fmt.Errorf("xwm: watch X socket: %w", err) | |
| 344 | 336 | } | |
| 345 | 337 | x.attach(src, h, d.Post) | |
| 338 | + | x.lp.(*displayLoop).d = d | |
| 346 | 339 | return nil | |
| 347 | 340 | } | |
| 348 | 341 | ||
| 342 | + | // loop is what the XWM needs from the display beyond the X socket source: | |
| 343 | + | // fd sources for transfer pipes and one-shot timers. | |
| 344 | + | type loop interface { | |
| 345 | + | AddFD(fd int, onReadable, onWritable func()) (fdSource, error) | |
| 346 | + | After(d time.Duration, fn func()) (cancel func()) | |
| 347 | + | } | |
| 348 | + | ||
| 349 | + | // displayLoop is the production loop: the display's fd sources, the host's timers. | |
| 350 | + | type displayLoop struct { | |
| 351 | + | d *server.Display | |
| 352 | + | h host | |
| 353 | + | } | |
| 354 | + | ||
| 355 | + | func (l *displayLoop) AddFD(fd int, r, w func()) (fdSource, error) { | |
| 356 | + | if l.d == nil { | |
| 357 | + | return nil, errors.New("xwm: no display") | |
| 358 | + | } | |
| 359 | + | s, err := l.d.AddFDRW(fd, r, w) | |
| 360 | + | if err != nil { | |
| 361 | + | return nil, err | |
| 362 | + | } | |
| 363 | + | return s, nil | |
| 364 | + | } | |
| 365 | + | ||
| 366 | + | func (l *displayLoop) After(d time.Duration, fn func()) func() { return l.h.After(d, fn) } | |
| 367 | + | ||
| 349 | 368 | // attach is Attach over its seams: the event source and the display's Post. | |
| 350 | 369 | func (x *XWM) attach(src fdSource, h host, post func(func())) { | |
| 351 | 370 | x.src, x.host, x.post = src, h, post | |
| 352 | - | x.after = h.After | |
| 371 | + | x.lp = &displayLoop{h: h} | |
| 353 | 372 | x.flushFn = func() { | |
| 354 | 373 | x.posted = false | |
| 355 | 374 | x.flush() | |
| @@ -66,8 +66,7 @@ type XWM struct { | |||||
| 66 | // attachment | 66 | // attachment | ||
| 67 | host host | 67 | host host | ||
| 68 | src fdSource | 68 | src fdSource | ||
| 69 | - | addFD func(fd int, onReadable, onWritable func()) (fdSource, error) // display fd source seam | 69 | + | lp loop // display fd sources and timers |
| 70 | - | after func(d time.Duration, fn func()) (cancel func()) | |||
| 71 | post func(func()) | 70 | post func(func()) | ||
| 72 | flushFn func() | 71 | flushFn func() | ||
| 73 | 72 | ||||
| @@ -331,25 +330,45 @@ func (x *XWM) boot() error { | |||||
| 331 | // Attach registers the X socket with the display and hands the windows found | 330 | // Attach registers the X socket with the display and hands the windows found | ||
| 332 | // at boot to h. Display goroutine only. | 331 | // at boot to h. Display goroutine only. | ||
| 333 | func (x *XWM) Attach(d *server.Display, h host) error { | 332 | func (x *XWM) Attach(d *server.Display, h host) error { | ||
| 334 | - | x.addFD = func(fd int, r, w func()) (fdSource, error) { | |||
| 335 | - | s, err := d.AddFDRW(fd, r, w) | |||
| 336 | - | if err != nil { | |||
| 337 | - | return nil, err | |||
| 338 | - | } | |||
| 339 | - | return s, nil | |||
| 340 | - | } | |||
| 341 | src, err := d.AddFDRW(x.conn.Fd(), x.onReadable, x.onWritable) | 333 | src, err := d.AddFDRW(x.conn.Fd(), x.onReadable, x.onWritable) | ||
| 342 | if err != nil { | 334 | if err != nil { | ||
| 343 | return fmt.Errorf("xwm: watch X socket: %w", err) | 335 | return fmt.Errorf("xwm: watch X socket: %w", err) | ||
| 344 | } | 336 | } | ||
| 345 | x.attach(src, h, d.Post) | 337 | x.attach(src, h, d.Post) | ||
| 338 | + | x.lp.(*displayLoop).d = d | |||
| 346 | return nil | 339 | return nil | ||
| 347 | } | 340 | } | ||
| 348 | 341 | ||||
| 342 | + | // loop is what the XWM needs from the display beyond the X socket source: | |||
| 343 | + | // fd sources for transfer pipes and one-shot timers. | |||
| 344 | + | type loop interface { | |||
| 345 | + | AddFD(fd int, onReadable, onWritable func()) (fdSource, error) | |||
| 346 | + | After(d time.Duration, fn func()) (cancel func()) | |||
| 347 | + | } | |||
| 348 | + | ||||
| 349 | + | // displayLoop is the production loop: the display's fd sources, the host's timers. | |||
| 350 | + | type displayLoop struct { | |||
| 351 | + | d *server.Display | |||
| 352 | + | h host | |||
| 353 | + | } | |||
| 354 | + | ||||
| 355 | + | func (l *displayLoop) AddFD(fd int, r, w func()) (fdSource, error) { | |||
| 356 | + | if l.d == nil { | |||
| 357 | + | return nil, errors.New("xwm: no display") | |||
| 358 | + | } | |||
| 359 | + | s, err := l.d.AddFDRW(fd, r, w) | |||
| 360 | + | if err != nil { | |||
| 361 | + | return nil, err | |||
| 362 | + | } | |||
| 363 | + | return s, nil | |||
| 364 | + | } | |||
| 365 | + | ||||
| 366 | + | func (l *displayLoop) After(d time.Duration, fn func()) func() { return l.h.After(d, fn) } | |||
| 367 | + | ||||
| 349 | // attach is Attach over its seams: the event source and the display's Post. | 368 | // attach is Attach over its seams: the event source and the display's Post. | ||
| 350 | func (x *XWM) attach(src fdSource, h host, post func(func())) { | 369 | func (x *XWM) attach(src fdSource, h host, post func(func())) { | ||
| 351 | x.src, x.host, x.post = src, h, post | 370 | x.src, x.host, x.post = src, h, post | ||
| 352 | - | x.after = h.After | 371 | + | x.lp = &displayLoop{h: h} |
| 353 | x.flushFn = func() { | 372 | x.flushFn = func() { | ||
| 354 | x.posted = false | 373 | x.posted = false | ||
| 355 | x.flush() | 374 | x.flush() | ||