detached a248820

Task 3 review fixes: use libFileName, remove unused SPA handle, add resolveLib test

commit
a248820b76b0329540ebf5c18580b2e10347647e
author
brice · · 2026-04-09 05:50 UTC
parent
f2fb7ae
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +221 -8
M internal/capi/register_all.go +2 -0
@@ -2,6 +2,8 @@ package capi
22
33 import "github.com/bnema/purego-pipewire/internal/loader"
44
5+// registerAll registers all PipeWire symbol bindings.
6+// This is the central registration point; add new module registrations here.
57 func registerAll(h loader.Handles) error {
68 registerInit(h.PipeWire)
79 registerLoop(h.PipeWire)
@@ -2,6 +2,8 @@ package capi
2 2
3 import "github.com/bnema/purego-pipewire/internal/loader"3 import "github.com/bnema/purego-pipewire/internal/loader"
4 4
5+// registerAll registers all PipeWire symbol bindings.
6+// This is the central registration point; add new module registrations here.
5 func registerAll(h loader.Handles) error {7 func registerAll(h loader.Handles) error {
6 registerInit(h.PipeWire)8 registerInit(h.PipeWire)
7 registerLoop(h.PipeWire)9 registerLoop(h.PipeWire)
M internal/loader/loader.go +5 -7
@@ -10,22 +10,20 @@ import (
1010
1111 type Handles struct {
1212 PipeWire uintptr
13- SPA uintptr
1413 }
1514
15+// Open loads the PipeWire library with RTLD_GLOBAL so plugins can resolve symbols.
16+// Falls back to system library paths unless PIPEWIRE_LIB_PATH is set.
1617 func Open() (Handles, error) {
17- pw, err := openOne(resolveLib("PIPEWIRE_LIB_PATH", "libpipewire-0.3.so.0"))
18+ pw, err := openOne(resolveLib("PIPEWIRE_LIB_PATH", libFileName("libpipewire-0.3", "0")))
1819 if err != nil {
1920 return Handles{}, err
2021 }
21- spa, err := openOne(resolveLib("SPA_LIB_PATH", "libspa-0.2.so.0"))
22- if err != nil {
23- return Handles{}, err
24- }
25- return Handles{PipeWire: pw, SPA: spa}, nil
22+ return Handles{PipeWire: pw}, nil
2623 }
2724
2825 func openOne(path string) (uintptr, error) {
26+ // RTLD_GLOBAL is required: PipeWire plugins need to resolve symbols from the main library.
2927 h, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL)
3028 if err != nil {
3129 return 0, fmt.Errorf("dlopen %s: %w", path, err)
@@ -10,22 +10,20 @@ import (
10 10
11 type Handles struct {11 type Handles struct {
12 PipeWire uintptr12 PipeWire uintptr
13- SPA uintptr
14 }13 }
15 14
15+// Open loads the PipeWire library with RTLD_GLOBAL so plugins can resolve symbols.
16+// Falls back to system library paths unless PIPEWIRE_LIB_PATH is set.
16 func Open() (Handles, error) {17 func Open() (Handles, error) {
17- pw, err := openOne(resolveLib("PIPEWIRE_LIB_PATH", "libpipewire-0.3.so.0"))18+ pw, err := openOne(resolveLib("PIPEWIRE_LIB_PATH", libFileName("libpipewire-0.3", "0")))
18 if err != nil {19 if err != nil {
19 return Handles{}, err20 return Handles{}, err
20 }21 }
21- spa, err := openOne(resolveLib("SPA_LIB_PATH", "libspa-0.2.so.0"))22+ return Handles{PipeWire: pw}, nil
22- if err != nil {
23- return Handles{}, err
24- }
25- return Handles{PipeWire: pw, SPA: spa}, nil
26 }23 }
27 24
28 func openOne(path string) (uintptr, error) {25 func openOne(path string) (uintptr, error) {
26+ // RTLD_GLOBAL is required: PipeWire plugins need to resolve symbols from the main library.
29 h, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL)27 h, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL)
30 if err != nil {28 if err != nil {
31 return 0, fmt.Errorf("dlopen %s: %w", path, err)29 return 0, fmt.Errorf("dlopen %s: %w", path, err)
M internal/loader/loader_test.go +22 -1
@@ -1,9 +1,30 @@
11 package loader
22
3-import "testing"
3+import (
4+ "os"
5+ "testing"
6+)
47
58 func TestLibFileNameLinuxDefault(t *testing.T) {
69 if got, want := libFileName("libpipewire-0.3", "0"), "libpipewire-0.3.so.0"; got != want {
710 t.Fatalf("libFileName=%q want %q", got, want)
811 }
912 }
13+
14+func TestResolveLibEnvOverride(t *testing.T) {
15+ // Save and restore env
16+ orig := os.Getenv("TEST_LIB_PATH")
17+ defer os.Setenv("TEST_LIB_PATH", orig)
18+
19+ // Test fallback when env is not set
20+ os.Unsetenv("TEST_LIB_PATH")
21+ if got := resolveLib("TEST_LIB_PATH", "fallback.so"); got != "fallback.so" {
22+ t.Errorf("resolveLib fallback=%q want %q", got, "fallback.so")
23+ }
24+
25+ // Test env override
26+ os.Setenv("TEST_LIB_PATH", "/custom/path/lib.so")
27+ if got := resolveLib("TEST_LIB_PATH", "fallback.so"); got != "/custom/path/lib.so" {
28+ t.Errorf("resolveLib env=%q want %q", got, "/custom/path/lib.so")
29+ }
30+}
@@ -1,9 +1,30 @@
1 package loader1 package loader
2 2
3-import "testing"3+import (
4+ "os"
5+ "testing"
6+)
4 7
5 func TestLibFileNameLinuxDefault(t *testing.T) {8 func TestLibFileNameLinuxDefault(t *testing.T) {
6 if got, want := libFileName("libpipewire-0.3", "0"), "libpipewire-0.3.so.0"; got != want {9 if got, want := libFileName("libpipewire-0.3", "0"), "libpipewire-0.3.so.0"; got != want {
7 t.Fatalf("libFileName=%q want %q", got, want)10 t.Fatalf("libFileName=%q want %q", got, want)
8 }11 }
9 }12 }
13+
14+func TestResolveLibEnvOverride(t *testing.T) {
15+ // Save and restore env
16+ orig := os.Getenv("TEST_LIB_PATH")
17+ defer os.Setenv("TEST_LIB_PATH", orig)
18+
19+ // Test fallback when env is not set
20+ os.Unsetenv("TEST_LIB_PATH")
21+ if got := resolveLib("TEST_LIB_PATH", "fallback.so"); got != "fallback.so" {
22+ t.Errorf("resolveLib fallback=%q want %q", got, "fallback.so")
23+ }
24+
25+ // Test env override
26+ os.Setenv("TEST_LIB_PATH", "/custom/path/lib.so")
27+ if got := resolveLib("TEST_LIB_PATH", "fallback.so"); got != "/custom/path/lib.so" {
28+ t.Errorf("resolveLib env=%q want %q", got, "/custom/path/lib.so")
29+ }
30+}
A internal/ports/out/loop_gen.go +12 -0 generated file: collapsed
A internal/ports/out/mocks/mock_LoopAPI.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 mocks
6+
7+import (
8+ "unsafe"
9+
10+ mock "github.com/stretchr/testify/mock"
11+)
12+
13+// NewMockLoopAPI creates a new instance of MockLoopAPI. 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 NewMockLoopAPI(t interface {
16+ mock.TestingT
17+ Cleanup(func())
18+}) *MockLoopAPI {
19+ mock := &MockLoopAPI{}
20+ mock.Mock.Test(t)
21+
22+ t.Cleanup(func() { mock.AssertExpectations(t) })
23+
24+ return mock
25+}
26+
27+// MockLoopAPI is an autogenerated mock type for the LoopAPI type
28+type MockLoopAPI struct {
29+ mock.Mock
30+}
31+
32+type MockLoopAPI_Expecter struct {
33+ mock *mock.Mock
34+}
35+
36+func (_m *MockLoopAPI) EXPECT() *MockLoopAPI_Expecter {
37+ return &MockLoopAPI_Expecter{mock: &_m.Mock}
38+}
39+
40+// PWMainLoopDestroy provides a mock function for the type MockLoopAPI
41+func (_mock *MockLoopAPI) PWMainLoopDestroy(loop unsafe.Pointer) {
42+ _mock.Called(loop)
43+ return
44+}
45+
46+// MockLoopAPI_PWMainLoopDestroy_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PWMainLoopDestroy'
47+type MockLoopAPI_PWMainLoopDestroy_Call struct {
48+ *mock.Call
49+}
50+
51+// PWMainLoopDestroy is a helper method to define mock.On call
52+// - loop unsafe.Pointer
53+func (_e *MockLoopAPI_Expecter) PWMainLoopDestroy(loop interface{}) *MockLoopAPI_PWMainLoopDestroy_Call {
54+ return &MockLoopAPI_PWMainLoopDestroy_Call{Call: _e.mock.On("PWMainLoopDestroy", loop)}
55+}
56+
57+func (_c *MockLoopAPI_PWMainLoopDestroy_Call) Run(run func(loop unsafe.Pointer)) *MockLoopAPI_PWMainLoopDestroy_Call {
58+ _c.Call.Run(func(args mock.Arguments) {
59+ var arg0 unsafe.Pointer
60+ if args[0] != nil {
61+ arg0 = args[0].(unsafe.Pointer)
62+ }
63+ run(
64+ arg0,
65+ )
66+ })
67+ return _c
68+}
69+
70+func (_c *MockLoopAPI_PWMainLoopDestroy_Call) Return() *MockLoopAPI_PWMainLoopDestroy_Call {
71+ _c.Call.Return()
72+ return _c
73+}
74+
75+func (_c *MockLoopAPI_PWMainLoopDestroy_Call) RunAndReturn(run func(loop unsafe.Pointer)) *MockLoopAPI_PWMainLoopDestroy_Call {
76+ _c.Run(run)
77+ return _c
78+}
79+
80+// PWMainLoopNew provides a mock function for the type MockLoopAPI
81+func (_mock *MockLoopAPI) PWMainLoopNew(props unsafe.Pointer) unsafe.Pointer {
82+ ret := _mock.Called(props)
83+
84+ if len(ret) == 0 {
85+ panic("no return value specified for PWMainLoopNew")
86+ }
87+
88+ var r0 unsafe.Pointer
89+ if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) unsafe.Pointer); ok {
90+ r0 = returnFunc(props)
91+ } else {
92+ r0 = ret.Get(0).(unsafe.Pointer)
93+ }
94+ return r0
95+}
96+
97+// MockLoopAPI_PWMainLoopNew_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PWMainLoopNew'
98+type MockLoopAPI_PWMainLoopNew_Call struct {
99+ *mock.Call
100+}
101+
102+// PWMainLoopNew is a helper method to define mock.On call
103+// - props unsafe.Pointer
104+func (_e *MockLoopAPI_Expecter) PWMainLoopNew(props interface{}) *MockLoopAPI_PWMainLoopNew_Call {
105+ return &MockLoopAPI_PWMainLoopNew_Call{Call: _e.mock.On("PWMainLoopNew", props)}
106+}
107+
108+func (_c *MockLoopAPI_PWMainLoopNew_Call) Run(run func(props unsafe.Pointer)) *MockLoopAPI_PWMainLoopNew_Call {
109+ _c.Call.Run(func(args mock.Arguments) {
110+ var arg0 unsafe.Pointer
111+ if args[0] != nil {
112+ arg0 = args[0].(unsafe.Pointer)
113+ }
114+ run(
115+ arg0,
116+ )
117+ })
118+ return _c
119+}
120+
121+func (_c *MockLoopAPI_PWMainLoopNew_Call) Return(v unsafe.Pointer) *MockLoopAPI_PWMainLoopNew_Call {
122+ _c.Call.Return(v)
123+ return _c
124+}
125+
126+func (_c *MockLoopAPI_PWMainLoopNew_Call) RunAndReturn(run func(props unsafe.Pointer) unsafe.Pointer) *MockLoopAPI_PWMainLoopNew_Call {
127+ _c.Call.Return(run)
128+ return _c
129+}
130+
131+// PWMainLoopRun provides a mock function for the type MockLoopAPI
132+func (_mock *MockLoopAPI) PWMainLoopRun(loop unsafe.Pointer) int32 {
133+ ret := _mock.Called(loop)
134+
135+ if len(ret) == 0 {
136+ panic("no return value specified for PWMainLoopRun")
137+ }
138+
139+ var r0 int32
140+ if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) int32); ok {
141+ r0 = returnFunc(loop)
142+ } else {
143+ r0 = ret.Get(0).(int32)
144+ }
145+ return r0
146+}
147+
148+// MockLoopAPI_PWMainLoopRun_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PWMainLoopRun'
149+type MockLoopAPI_PWMainLoopRun_Call struct {
150+ *mock.Call
151+}
152+
153+// PWMainLoopRun is a helper method to define mock.On call
154+// - loop unsafe.Pointer
155+func (_e *MockLoopAPI_Expecter) PWMainLoopRun(loop interface{}) *MockLoopAPI_PWMainLoopRun_Call {
156+ return &MockLoopAPI_PWMainLoopRun_Call{Call: _e.mock.On("PWMainLoopRun", loop)}
157+}
158+
159+func (_c *MockLoopAPI_PWMainLoopRun_Call) Run(run func(loop unsafe.Pointer)) *MockLoopAPI_PWMainLoopRun_Call {
160+ _c.Call.Run(func(args mock.Arguments) {
161+ var arg0 unsafe.Pointer
162+ if args[0] != nil {
163+ arg0 = args[0].(unsafe.Pointer)
164+ }
165+ run(
166+ arg0,
167+ )
168+ })
169+ return _c
170+}
171+
172+func (_c *MockLoopAPI_PWMainLoopRun_Call) Return(n int32) *MockLoopAPI_PWMainLoopRun_Call {
173+ _c.Call.Return(n)
174+ return _c
175+}
176+
177+func (_c *MockLoopAPI_PWMainLoopRun_Call) RunAndReturn(run func(loop unsafe.Pointer) int32) *MockLoopAPI_PWMainLoopRun_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 mocks
6+
7+import (
8+ "unsafe"
9+
10+ mock "github.com/stretchr/testify/mock"
11+)
12+
13+// NewMockLoopAPI creates a new instance of MockLoopAPI. 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 NewMockLoopAPI(t interface {
16+ mock.TestingT
17+ Cleanup(func())
18+}) *MockLoopAPI {
19+ mock := &MockLoopAPI{}
20+ mock.Mock.Test(t)
21+
22+ t.Cleanup(func() { mock.AssertExpectations(t) })
23+
24+ return mock
25+}
26+
27+// MockLoopAPI is an autogenerated mock type for the LoopAPI type
28+type MockLoopAPI struct {
29+ mock.Mock
30+}
31+
32+type MockLoopAPI_Expecter struct {
33+ mock *mock.Mock
34+}
35+
36+func (_m *MockLoopAPI) EXPECT() *MockLoopAPI_Expecter {
37+ return &MockLoopAPI_Expecter{mock: &_m.Mock}
38+}
39+
40+// PWMainLoopDestroy provides a mock function for the type MockLoopAPI
41+func (_mock *MockLoopAPI) PWMainLoopDestroy(loop unsafe.Pointer) {
42+ _mock.Called(loop)
43+ return
44+}
45+
46+// MockLoopAPI_PWMainLoopDestroy_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PWMainLoopDestroy'
47+type MockLoopAPI_PWMainLoopDestroy_Call struct {
48+ *mock.Call
49+}
50+
51+// PWMainLoopDestroy is a helper method to define mock.On call
52+// - loop unsafe.Pointer
53+func (_e *MockLoopAPI_Expecter) PWMainLoopDestroy(loop interface{}) *MockLoopAPI_PWMainLoopDestroy_Call {
54+ return &MockLoopAPI_PWMainLoopDestroy_Call{Call: _e.mock.On("PWMainLoopDestroy", loop)}
55+}
56+
57+func (_c *MockLoopAPI_PWMainLoopDestroy_Call) Run(run func(loop unsafe.Pointer)) *MockLoopAPI_PWMainLoopDestroy_Call {
58+ _c.Call.Run(func(args mock.Arguments) {
59+ var arg0 unsafe.Pointer
60+ if args[0] != nil {
61+ arg0 = args[0].(unsafe.Pointer)
62+ }
63+ run(
64+ arg0,
65+ )
66+ })
67+ return _c
68+}
69+
70+func (_c *MockLoopAPI_PWMainLoopDestroy_Call) Return() *MockLoopAPI_PWMainLoopDestroy_Call {
71+ _c.Call.Return()
72+ return _c
73+}
74+
75+func (_c *MockLoopAPI_PWMainLoopDestroy_Call) RunAndReturn(run func(loop unsafe.Pointer)) *MockLoopAPI_PWMainLoopDestroy_Call {
76+ _c.Run(run)
77+ return _c
78+}
79+
80+// PWMainLoopNew provides a mock function for the type MockLoopAPI
81+func (_mock *MockLoopAPI) PWMainLoopNew(props unsafe.Pointer) unsafe.Pointer {
82+ ret := _mock.Called(props)
83+
84+ if len(ret) == 0 {
85+ panic("no return value specified for PWMainLoopNew")
86+ }
87+
88+ var r0 unsafe.Pointer
89+ if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) unsafe.Pointer); ok {
90+ r0 = returnFunc(props)
91+ } else {
92+ r0 = ret.Get(0).(unsafe.Pointer)
93+ }
94+ return r0
95+}
96+
97+// MockLoopAPI_PWMainLoopNew_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PWMainLoopNew'
98+type MockLoopAPI_PWMainLoopNew_Call struct {
99+ *mock.Call
100+}
101+
102+// PWMainLoopNew is a helper method to define mock.On call
103+// - props unsafe.Pointer
104+func (_e *MockLoopAPI_Expecter) PWMainLoopNew(props interface{}) *MockLoopAPI_PWMainLoopNew_Call {
105+ return &MockLoopAPI_PWMainLoopNew_Call{Call: _e.mock.On("PWMainLoopNew", props)}
106+}
107+
108+func (_c *MockLoopAPI_PWMainLoopNew_Call) Run(run func(props unsafe.Pointer)) *MockLoopAPI_PWMainLoopNew_Call {
109+ _c.Call.Run(func(args mock.Arguments) {
110+ var arg0 unsafe.Pointer
111+ if args[0] != nil {
112+ arg0 = args[0].(unsafe.Pointer)
113+ }
114+ run(
115+ arg0,
116+ )
117+ })
118+ return _c
119+}
120+
121+func (_c *MockLoopAPI_PWMainLoopNew_Call) Return(v unsafe.Pointer) *MockLoopAPI_PWMainLoopNew_Call {
122+ _c.Call.Return(v)
123+ return _c
124+}
125+
126+func (_c *MockLoopAPI_PWMainLoopNew_Call) RunAndReturn(run func(props unsafe.Pointer) unsafe.Pointer) *MockLoopAPI_PWMainLoopNew_Call {
127+ _c.Call.Return(run)
128+ return _c
129+}
130+
131+// PWMainLoopRun provides a mock function for the type MockLoopAPI
132+func (_mock *MockLoopAPI) PWMainLoopRun(loop unsafe.Pointer) int32 {
133+ ret := _mock.Called(loop)
134+
135+ if len(ret) == 0 {
136+ panic("no return value specified for PWMainLoopRun")
137+ }
138+
139+ var r0 int32
140+ if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) int32); ok {
141+ r0 = returnFunc(loop)
142+ } else {
143+ r0 = ret.Get(0).(int32)
144+ }
145+ return r0
146+}
147+
148+// MockLoopAPI_PWMainLoopRun_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PWMainLoopRun'
149+type MockLoopAPI_PWMainLoopRun_Call struct {
150+ *mock.Call
151+}
152+
153+// PWMainLoopRun is a helper method to define mock.On call
154+// - loop unsafe.Pointer
155+func (_e *MockLoopAPI_Expecter) PWMainLoopRun(loop interface{}) *MockLoopAPI_PWMainLoopRun_Call {
156+ return &MockLoopAPI_PWMainLoopRun_Call{Call: _e.mock.On("PWMainLoopRun", loop)}
157+}
158+
159+func (_c *MockLoopAPI_PWMainLoopRun_Call) Run(run func(loop unsafe.Pointer)) *MockLoopAPI_PWMainLoopRun_Call {
160+ _c.Call.Run(func(args mock.Arguments) {
161+ var arg0 unsafe.Pointer
162+ if args[0] != nil {
163+ arg0 = args[0].(unsafe.Pointer)
164+ }
165+ run(
166+ arg0,
167+ )
168+ })
169+ return _c
170+}
171+
172+func (_c *MockLoopAPI_PWMainLoopRun_Call) Return(n int32) *MockLoopAPI_PWMainLoopRun_Call {
173+ _c.Call.Return(n)
174+ return _c
175+}
176+
177+func (_c *MockLoopAPI_PWMainLoopRun_Call) RunAndReturn(run func(loop unsafe.Pointer) int32) *MockLoopAPI_PWMainLoopRun_Call {
178+ _c.Call.Return(run)
179+ return _c
180+}

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 8

↑↓ moveenter openesc close