detached 0e08fa2

fix: handle player error transitions and clean up lint issues

commit
0e08fa23a283ab8f295e7fdcf704fa07203f7d3f
author
brice · · 2026-04-11 06:46 UTC
parent
3373d7c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
refs
tag: v0.1.1
8 files · +178 -104
M internal/capi/stream_ops_test.go +29 -24
@@ -10,6 +10,10 @@ import (
1010 portout "github.com/bnema/purego-pipewire/internal/ports/out"
1111 )
1212
13+func opaquePtr() unsafe.Pointer {
14+ return unsafe.Pointer(new(byte))
15+}
16+
1317 // TestDestroyStreamCallsPwStreamDestroy verifies that DestroyStream delegates
1418 // to the generated pw_stream_destroy binding (not pw_stream_disconnect).
1519 func TestDestroyStreamCallsPwStreamDestroy(t *testing.T) {
@@ -28,7 +32,7 @@ func TestDestroyStreamCallsPwStreamDestroy(t *testing.T) {
2832 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),
2933 }
3034
31- fakePtr := unsafe.Pointer(uintptr(0x1234))
35+ fakePtr := opaquePtr()
3236 ops.pinned[fakePtr] = &streamCallbackStorage{}
3337
3438 ops.DestroyStream(fakePtr)
@@ -73,7 +77,7 @@ func TestDestroyStreamDoubleDestroySkipsSecondCall(t *testing.T) {
7377 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),
7478 }
7579
76- fakePtr := unsafe.Pointer(uintptr(0xABCD))
80+ fakePtr := opaquePtr()
7781 ops.pinned[fakePtr] = &streamCallbackStorage{}
7882
7983 ops.DestroyStream(fakePtr)
@@ -97,8 +101,9 @@ func TestDestroyStreamBookkeepingDoesNotLeak(t *testing.T) {
97101 }
98102
99103 const cycles = 100
100- for i := uintptr(1); i <= cycles; i++ {
101- ptr := unsafe.Pointer(i)
104+ tokens := make([]byte, cycles)
105+ for i := range tokens {
106+ ptr := unsafe.Pointer(&tokens[i])
102107 ops.pinned[ptr] = &streamCallbackStorage{}
103108 ops.DestroyStream(ptr)
104109 }
@@ -124,7 +129,7 @@ func TestQuitMainLoopCallsPwMainLoopQuit(t *testing.T) {
124129 }
125130
126131 ops := &streamOpsImpl{}
127- fakeLoop := unsafe.Pointer(uintptr(0x5678))
132+ fakeLoop := opaquePtr()
128133
129134 ops.QuitMainLoop(fakeLoop)
130135
@@ -147,7 +152,7 @@ func TestConnectPlaybackStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) {
147152 }
148153
149154 ops := &streamOpsImpl{}
150- fakePtr := unsafe.Pointer(uintptr(0x1234))
155+ fakePtr := opaquePtr()
151156
152157 validFmt := portout.PlaybackFormat{
153158 SampleRate: 48000,
@@ -182,7 +187,7 @@ func TestSetStreamActiveReturnsTypedErrorOnNegativeReturn(t *testing.T) {
182187 }
183188
184189 ops := &streamOpsImpl{}
185- fakePtr := unsafe.Pointer(uintptr(0x5678))
190+ fakePtr := opaquePtr()
186191
187192 err := ops.SetStreamActive(fakePtr, true)
188193 if err == nil {
@@ -212,8 +217,8 @@ func TestQueueBufferReturnsTypedErrorOnNegativeReturn(t *testing.T) {
212217 }
213218
214219 ops := &streamOpsImpl{}
215- fakePtr := unsafe.Pointer(uintptr(0x9ABC))
216- fakeBuf := unsafe.Pointer(uintptr(0xDEF0))
220+ fakePtr := opaquePtr()
221+ fakeBuf := opaquePtr()
217222
218223 err := ops.QueueBuffer(fakePtr, fakeBuf)
219224 if err == nil {
@@ -243,7 +248,7 @@ func TestDisconnectStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) {
243248 }
244249
245250 ops := &streamOpsImpl{}
246- fakePtr := unsafe.Pointer(uintptr(0x1111))
251+ fakePtr := opaquePtr()
247252
248253 err := ops.DisconnectStream(fakePtr)
249254 if err == nil {
@@ -273,7 +278,7 @@ func TestDisconnectStreamReturnsNilOnSuccess(t *testing.T) {
273278 }
274279
275280 ops := &streamOpsImpl{}
276- fakePtr := unsafe.Pointer(uintptr(0x2222))
281+ fakePtr := opaquePtr()
277282
278283 err := ops.DisconnectStream(fakePtr)
279284 if err != nil {
@@ -295,7 +300,7 @@ func TestConnectPlaybackStreamDelegatesFramesPerBufferValidation(t *testing.T) {
295300 }
296301
297302 ops := &streamOpsImpl{}
298- fakePtr := unsafe.Pointer(uintptr(0x1234))
303+ fakePtr := opaquePtr()
299304
300305 fmt := portout.PlaybackFormat{SampleRate: 48000, Channels: 2, FramesPerBuffer: 0}
301306 err := ops.ConnectPlaybackStream(fakePtr, fmt)
@@ -325,7 +330,7 @@ func TestConnectPlaybackStreamDelegatesSampleRateAndChannelsValidation(t *testin
325330 }
326331
327332 ops := &streamOpsImpl{}
328- fakePtr := unsafe.Pointer(uintptr(0x1234))
333+ fakePtr := opaquePtr()
329334
330335 tests := []struct {
331336 name string
@@ -379,7 +384,7 @@ func TestConnectPlaybackStreamAcceptsValidFormat(t *testing.T) {
379384 }
380385
381386 ops := &streamOpsImpl{}
382- fakePtr := unsafe.Pointer(uintptr(0x5678))
387+ fakePtr := opaquePtr()
383388
384389 validFmt := portout.PlaybackFormat{
385390 SampleRate: 48000,
@@ -406,7 +411,7 @@ func TestRunMainLoopReturnsPWErrorOnNegativeResult(t *testing.T) {
406411 }
407412
408413 ops := &streamOpsImpl{}
409- fakeLoop := unsafe.Pointer(uintptr(0xAAAA))
414+ fakeLoop := opaquePtr()
410415
411416 err := ops.RunMainLoop(fakeLoop)
412417 if err == nil {
@@ -436,7 +441,7 @@ func TestRunMainLoopReturnsNilOnSuccess(t *testing.T) {
436441 }
437442
438443 ops := &streamOpsImpl{}
439- fakeLoop := unsafe.Pointer(uintptr(0xBBBB))
444+ fakeLoop := opaquePtr()
440445
441446 err := ops.RunMainLoop(fakeLoop)
442447 if err != nil {
@@ -455,8 +460,8 @@ func TestCreatePlaybackStreamUsesPWLoopFromMainLoop(t *testing.T) {
455460 pw_stream_new_simple = origNewSimple
456461 })
457462
458- fakeMainLoop := unsafe.Pointer(uintptr(0xAAAA))
459- fakePWLoop := unsafe.Pointer(uintptr(0xBBBB))
463+ fakeMainLoop := opaquePtr()
464+ fakePWLoop := opaquePtr()
460465 var gotContext unsafe.Pointer
461466
462467 pw_main_loop_get_loop = func(loop unsafe.Pointer) unsafe.Pointer {
@@ -469,7 +474,7 @@ func TestCreatePlaybackStreamUsesPWLoopFromMainLoop(t *testing.T) {
469474 pw_stream_new_simple = func(context unsafe.Pointer, name *byte, props unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) unsafe.Pointer {
470475 gotContext = context
471476 // Return a non-nil fake stream pointer.
472- return unsafe.Pointer(uintptr(0xCCCC))
477+ return opaquePtr()
473478 }
474479
475480 ops := &streamOpsImpl{
@@ -513,7 +518,7 @@ func TestCreatePlaybackStreamReturnsPWErrorWhenPWLoopIsNull(t *testing.T) {
513518 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),
514519 }
515520
516- fakeMainLoop := unsafe.Pointer(uintptr(0xAAAA))
521+ fakeMainLoop := opaquePtr()
517522 _, err := ops.CreatePlaybackStream(fakeMainLoop, "test", func() {})
518523 if err == nil {
519524 t.Fatal("expected error when pw_main_loop_get_loop returns nil")
@@ -545,7 +550,7 @@ func TestConnectPlaybackStreamPassesParamsFromHelper(t *testing.T) {
545550 }
546551
547552 ops := &streamOpsImpl{}
548- fakePtr := unsafe.Pointer(uintptr(0x5678))
553+ fakePtr := opaquePtr()
549554
550555 validFmt := portout.PlaybackFormat{
551556 SampleRate: 48000,
@@ -577,7 +582,7 @@ func TestConnectPlaybackStreamPinsParamsAfterSuccess(t *testing.T) {
577582 }
578583
579584 ops := &streamOpsImpl{}
580- fakePtr := unsafe.Pointer(uintptr(0x5678))
585+ fakePtr := opaquePtr()
581586
582587 validFmt := portout.PlaybackFormat{
583588 SampleRate: 48000,
@@ -616,7 +621,7 @@ func TestConnectPlaybackStreamDoesNotPinParamsOnFailure(t *testing.T) {
616621 }
617622
618623 ops := &streamOpsImpl{}
619- fakePtr := unsafe.Pointer(uintptr(0x5678))
624+ fakePtr := opaquePtr()
620625
621626 validFmt := portout.PlaybackFormat{
622627 SampleRate: 48000,
@@ -652,7 +657,7 @@ func TestDestroyStreamReleasesPinnedParams(t *testing.T) {
652657 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),
653658 }
654659
655- fakePtr := unsafe.Pointer(uintptr(0xDEAD))
660+ fakePtr := opaquePtr()
656661 ops.pinned[fakePtr] = &streamCallbackStorage{}
657662
658663 validFmt := portout.PlaybackFormat{
@@ -10,6 +10,10 @@ import (
10 portout "github.com/bnema/purego-pipewire/internal/ports/out"10 portout "github.com/bnema/purego-pipewire/internal/ports/out"
11 )11 )
12 12
13+func opaquePtr() unsafe.Pointer {
14+ return unsafe.Pointer(new(byte))
15+}
16+
13 // TestDestroyStreamCallsPwStreamDestroy verifies that DestroyStream delegates17 // TestDestroyStreamCallsPwStreamDestroy verifies that DestroyStream delegates
14 // to the generated pw_stream_destroy binding (not pw_stream_disconnect).18 // to the generated pw_stream_destroy binding (not pw_stream_disconnect).
15 func TestDestroyStreamCallsPwStreamDestroy(t *testing.T) {19 func TestDestroyStreamCallsPwStreamDestroy(t *testing.T) {
@@ -28,7 +32,7 @@ func TestDestroyStreamCallsPwStreamDestroy(t *testing.T) {
28 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),32 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),
29 }33 }
30 34
31- fakePtr := unsafe.Pointer(uintptr(0x1234))35+ fakePtr := opaquePtr()
32 ops.pinned[fakePtr] = &streamCallbackStorage{}36 ops.pinned[fakePtr] = &streamCallbackStorage{}
33 37
34 ops.DestroyStream(fakePtr)38 ops.DestroyStream(fakePtr)
@@ -73,7 +77,7 @@ func TestDestroyStreamDoubleDestroySkipsSecondCall(t *testing.T) {
73 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),77 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),
74 }78 }
75 79
76- fakePtr := unsafe.Pointer(uintptr(0xABCD))80+ fakePtr := opaquePtr()
77 ops.pinned[fakePtr] = &streamCallbackStorage{}81 ops.pinned[fakePtr] = &streamCallbackStorage{}
78 82
79 ops.DestroyStream(fakePtr)83 ops.DestroyStream(fakePtr)
@@ -97,8 +101,9 @@ func TestDestroyStreamBookkeepingDoesNotLeak(t *testing.T) {
97 }101 }
98 102
99 const cycles = 100103 const cycles = 100
100- for i := uintptr(1); i <= cycles; i++ {104+ tokens := make([]byte, cycles)
101- ptr := unsafe.Pointer(i)105+ for i := range tokens {
106+ ptr := unsafe.Pointer(&tokens[i])
102 ops.pinned[ptr] = &streamCallbackStorage{}107 ops.pinned[ptr] = &streamCallbackStorage{}
103 ops.DestroyStream(ptr)108 ops.DestroyStream(ptr)
104 }109 }
@@ -124,7 +129,7 @@ func TestQuitMainLoopCallsPwMainLoopQuit(t *testing.T) {
124 }129 }
125 130
126 ops := &streamOpsImpl{}131 ops := &streamOpsImpl{}
127- fakeLoop := unsafe.Pointer(uintptr(0x5678))132+ fakeLoop := opaquePtr()
128 133
129 ops.QuitMainLoop(fakeLoop)134 ops.QuitMainLoop(fakeLoop)
130 135
@@ -147,7 +152,7 @@ func TestConnectPlaybackStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) {
147 }152 }
148 153
149 ops := &streamOpsImpl{}154 ops := &streamOpsImpl{}
150- fakePtr := unsafe.Pointer(uintptr(0x1234))155+ fakePtr := opaquePtr()
151 156
152 validFmt := portout.PlaybackFormat{157 validFmt := portout.PlaybackFormat{
153 SampleRate: 48000,158 SampleRate: 48000,
@@ -182,7 +187,7 @@ func TestSetStreamActiveReturnsTypedErrorOnNegativeReturn(t *testing.T) {
182 }187 }
183 188
184 ops := &streamOpsImpl{}189 ops := &streamOpsImpl{}
185- fakePtr := unsafe.Pointer(uintptr(0x5678))190+ fakePtr := opaquePtr()
186 191
187 err := ops.SetStreamActive(fakePtr, true)192 err := ops.SetStreamActive(fakePtr, true)
188 if err == nil {193 if err == nil {
@@ -212,8 +217,8 @@ func TestQueueBufferReturnsTypedErrorOnNegativeReturn(t *testing.T) {
212 }217 }
213 218
214 ops := &streamOpsImpl{}219 ops := &streamOpsImpl{}
215- fakePtr := unsafe.Pointer(uintptr(0x9ABC))220+ fakePtr := opaquePtr()
216- fakeBuf := unsafe.Pointer(uintptr(0xDEF0))221+ fakeBuf := opaquePtr()
217 222
218 err := ops.QueueBuffer(fakePtr, fakeBuf)223 err := ops.QueueBuffer(fakePtr, fakeBuf)
219 if err == nil {224 if err == nil {
@@ -243,7 +248,7 @@ func TestDisconnectStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) {
243 }248 }
244 249
245 ops := &streamOpsImpl{}250 ops := &streamOpsImpl{}
246- fakePtr := unsafe.Pointer(uintptr(0x1111))251+ fakePtr := opaquePtr()
247 252
248 err := ops.DisconnectStream(fakePtr)253 err := ops.DisconnectStream(fakePtr)
249 if err == nil {254 if err == nil {
@@ -273,7 +278,7 @@ func TestDisconnectStreamReturnsNilOnSuccess(t *testing.T) {
273 }278 }
274 279
275 ops := &streamOpsImpl{}280 ops := &streamOpsImpl{}
276- fakePtr := unsafe.Pointer(uintptr(0x2222))281+ fakePtr := opaquePtr()
277 282
278 err := ops.DisconnectStream(fakePtr)283 err := ops.DisconnectStream(fakePtr)
279 if err != nil {284 if err != nil {
@@ -295,7 +300,7 @@ func TestConnectPlaybackStreamDelegatesFramesPerBufferValidation(t *testing.T) {
295 }300 }
296 301
297 ops := &streamOpsImpl{}302 ops := &streamOpsImpl{}
298- fakePtr := unsafe.Pointer(uintptr(0x1234))303+ fakePtr := opaquePtr()
299 304
300 fmt := portout.PlaybackFormat{SampleRate: 48000, Channels: 2, FramesPerBuffer: 0}305 fmt := portout.PlaybackFormat{SampleRate: 48000, Channels: 2, FramesPerBuffer: 0}
301 err := ops.ConnectPlaybackStream(fakePtr, fmt)306 err := ops.ConnectPlaybackStream(fakePtr, fmt)
@@ -325,7 +330,7 @@ func TestConnectPlaybackStreamDelegatesSampleRateAndChannelsValidation(t *testin
325 }330 }
326 331
327 ops := &streamOpsImpl{}332 ops := &streamOpsImpl{}
328- fakePtr := unsafe.Pointer(uintptr(0x1234))333+ fakePtr := opaquePtr()
329 334
330 tests := []struct {335 tests := []struct {
331 name string336 name string
@@ -379,7 +384,7 @@ func TestConnectPlaybackStreamAcceptsValidFormat(t *testing.T) {
379 }384 }
380 385
381 ops := &streamOpsImpl{}386 ops := &streamOpsImpl{}
382- fakePtr := unsafe.Pointer(uintptr(0x5678))387+ fakePtr := opaquePtr()
383 388
384 validFmt := portout.PlaybackFormat{389 validFmt := portout.PlaybackFormat{
385 SampleRate: 48000,390 SampleRate: 48000,
@@ -406,7 +411,7 @@ func TestRunMainLoopReturnsPWErrorOnNegativeResult(t *testing.T) {
406 }411 }
407 412
408 ops := &streamOpsImpl{}413 ops := &streamOpsImpl{}
409- fakeLoop := unsafe.Pointer(uintptr(0xAAAA))414+ fakeLoop := opaquePtr()
410 415
411 err := ops.RunMainLoop(fakeLoop)416 err := ops.RunMainLoop(fakeLoop)
412 if err == nil {417 if err == nil {
@@ -436,7 +441,7 @@ func TestRunMainLoopReturnsNilOnSuccess(t *testing.T) {
436 }441 }
437 442
438 ops := &streamOpsImpl{}443 ops := &streamOpsImpl{}
439- fakeLoop := unsafe.Pointer(uintptr(0xBBBB))444+ fakeLoop := opaquePtr()
440 445
441 err := ops.RunMainLoop(fakeLoop)446 err := ops.RunMainLoop(fakeLoop)
442 if err != nil {447 if err != nil {
@@ -455,8 +460,8 @@ func TestCreatePlaybackStreamUsesPWLoopFromMainLoop(t *testing.T) {
455 pw_stream_new_simple = origNewSimple460 pw_stream_new_simple = origNewSimple
456 })461 })
457 462
458- fakeMainLoop := unsafe.Pointer(uintptr(0xAAAA))463+ fakeMainLoop := opaquePtr()
459- fakePWLoop := unsafe.Pointer(uintptr(0xBBBB))464+ fakePWLoop := opaquePtr()
460 var gotContext unsafe.Pointer465 var gotContext unsafe.Pointer
461 466
462 pw_main_loop_get_loop = func(loop unsafe.Pointer) unsafe.Pointer {467 pw_main_loop_get_loop = func(loop unsafe.Pointer) unsafe.Pointer {
@@ -469,7 +474,7 @@ func TestCreatePlaybackStreamUsesPWLoopFromMainLoop(t *testing.T) {
469 pw_stream_new_simple = func(context unsafe.Pointer, name *byte, props unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) unsafe.Pointer {474 pw_stream_new_simple = func(context unsafe.Pointer, name *byte, props unsafe.Pointer, events unsafe.Pointer, data unsafe.Pointer) unsafe.Pointer {
470 gotContext = context475 gotContext = context
471 // Return a non-nil fake stream pointer.476 // Return a non-nil fake stream pointer.
472- return unsafe.Pointer(uintptr(0xCCCC))477+ return opaquePtr()
473 }478 }
474 479
475 ops := &streamOpsImpl{480 ops := &streamOpsImpl{
@@ -513,7 +518,7 @@ func TestCreatePlaybackStreamReturnsPWErrorWhenPWLoopIsNull(t *testing.T) {
513 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),518 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),
514 }519 }
515 520
516- fakeMainLoop := unsafe.Pointer(uintptr(0xAAAA))521+ fakeMainLoop := opaquePtr()
517 _, err := ops.CreatePlaybackStream(fakeMainLoop, "test", func() {})522 _, err := ops.CreatePlaybackStream(fakeMainLoop, "test", func() {})
518 if err == nil {523 if err == nil {
519 t.Fatal("expected error when pw_main_loop_get_loop returns nil")524 t.Fatal("expected error when pw_main_loop_get_loop returns nil")
@@ -545,7 +550,7 @@ func TestConnectPlaybackStreamPassesParamsFromHelper(t *testing.T) {
545 }550 }
546 551
547 ops := &streamOpsImpl{}552 ops := &streamOpsImpl{}
548- fakePtr := unsafe.Pointer(uintptr(0x5678))553+ fakePtr := opaquePtr()
549 554
550 validFmt := portout.PlaybackFormat{555 validFmt := portout.PlaybackFormat{
551 SampleRate: 48000,556 SampleRate: 48000,
@@ -577,7 +582,7 @@ func TestConnectPlaybackStreamPinsParamsAfterSuccess(t *testing.T) {
577 }582 }
578 583
579 ops := &streamOpsImpl{}584 ops := &streamOpsImpl{}
580- fakePtr := unsafe.Pointer(uintptr(0x5678))585+ fakePtr := opaquePtr()
581 586
582 validFmt := portout.PlaybackFormat{587 validFmt := portout.PlaybackFormat{
583 SampleRate: 48000,588 SampleRate: 48000,
@@ -616,7 +621,7 @@ func TestConnectPlaybackStreamDoesNotPinParamsOnFailure(t *testing.T) {
616 }621 }
617 622
618 ops := &streamOpsImpl{}623 ops := &streamOpsImpl{}
619- fakePtr := unsafe.Pointer(uintptr(0x5678))624+ fakePtr := opaquePtr()
620 625
621 validFmt := portout.PlaybackFormat{626 validFmt := portout.PlaybackFormat{
622 SampleRate: 48000,627 SampleRate: 48000,
@@ -652,7 +657,7 @@ func TestDestroyStreamReleasesPinnedParams(t *testing.T) {
652 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),657 pinned: make(map[unsafe.Pointer]*streamCallbackStorage),
653 }658 }
654 659
655- fakePtr := unsafe.Pointer(uintptr(0xDEAD))660+ fakePtr := opaquePtr()
656 ops.pinned[fakePtr] = &streamCallbackStorage{}661 ops.pinned[fakePtr] = &streamCallbackStorage{}
657 662
658 validFmt := portout.PlaybackFormat{663 validFmt := portout.PlaybackFormat{
M internal/capi/stream_params.go +1 -1
@@ -115,7 +115,7 @@ func buildRawAudioParams(format portout.PlaybackFormat) (*connectParams, error)
115115 off += writeIdProp(buf[off:], spaFormatMediaSubtype, spaMediaSubtypeRaw)
116116 off += writeIdProp(buf[off:], spaFormatAudioFormat, spaAudioFormatF32P)
117117 off += writeIntProp(buf[off:], spaFormatAudioRate, int32(format.SampleRate))
118- off += writeIntProp(buf[off:], spaFormatAudioChannels, int32(format.Channels))
118+ _ = writeIntProp(buf[off:], spaFormatAudioChannels, int32(format.Channels))
119119
120120 cp := &connectParams{
121121 storage: buf,
@@ -115,7 +115,7 @@ func buildRawAudioParams(format portout.PlaybackFormat) (*connectParams, error)
115 off += writeIdProp(buf[off:], spaFormatMediaSubtype, spaMediaSubtypeRaw)115 off += writeIdProp(buf[off:], spaFormatMediaSubtype, spaMediaSubtypeRaw)
116 off += writeIdProp(buf[off:], spaFormatAudioFormat, spaAudioFormatF32P)116 off += writeIdProp(buf[off:], spaFormatAudioFormat, spaAudioFormatF32P)
117 off += writeIntProp(buf[off:], spaFormatAudioRate, int32(format.SampleRate))117 off += writeIntProp(buf[off:], spaFormatAudioRate, int32(format.SampleRate))
118- off += writeIntProp(buf[off:], spaFormatAudioChannels, int32(format.Channels))118+ _ = writeIntProp(buf[off:], spaFormatAudioChannels, int32(format.Channels))
119 119
120 cp := &connectParams{120 cp := &connectParams{
121 storage: buf,121 storage: buf,
M internal/core/player.go +23 -14
@@ -14,6 +14,11 @@ import (
1414 var (
1515 ErrInvalidPlayerState = errors.New("invalid player state transition")
1616 ErrPlayerClosed = errors.New("player is closed")
17+
18+ registerPipeWire = capi.Register
19+ defaultPipeWireCAPI = capi.Default
20+ defaultPipeWireOps = capi.DefaultStreamOps
21+ initializePipeWire = initPipeWire
1722 )
1823
1924 // player is the internal player shell with state machine.
@@ -145,8 +150,7 @@ func (p *player) Start() error {
145150 // Activation failed — clean up owned resources so stale pointers
146151 // are not left behind, then transition to Error.
147152 p.teardown()
148- p.transition(PlayerStateError)
149- return err
153+ return p.failWithErrorState("reactivate stream", err)
150154 }
151155 }
152156
@@ -165,16 +169,14 @@ func (p *player) startCreateResourcesAndActivate() error {
165169 // Create main loop
166170 loopPtr, err := p.streamOps.CreateMainLoop()
167171 if err != nil {
168- p.transition(PlayerStateError)
169- return err
172+ return p.failWithErrorState("create main loop", err)
170173 }
171174
172175 // Create playback stream
173176 streamPtr, err := p.streamOps.CreatePlaybackStream(loopPtr, "purego-pipewire-player", p.onProcess)
174177 if err != nil {
175178 p.streamOps.DestroyMainLoop(loopPtr)
176- p.transition(PlayerStateError)
177- return err
179+ return p.failWithErrorState("create playback stream", err)
178180 }
179181
180182 // Connect stream
@@ -186,8 +188,7 @@ func (p *player) startCreateResourcesAndActivate() error {
186188 if err := p.streamOps.ConnectPlaybackStream(streamPtr, format); err != nil {
187189 p.streamOps.DestroyStream(streamPtr)
188190 p.streamOps.DestroyMainLoop(loopPtr)
189- p.transition(PlayerStateError)
190- return err
191+ return p.failWithErrorState("connect playback stream", err)
191192 }
192193
193194 // Activate stream
@@ -196,8 +197,7 @@ func (p *player) startCreateResourcesAndActivate() error {
196197 _ = p.streamOps.DisconnectStream(streamPtr)
197198 p.streamOps.DestroyStream(streamPtr)
198199 p.streamOps.DestroyMainLoop(loopPtr)
199- p.transition(PlayerStateError)
200- return err
200+ return p.failWithErrorState("activate playback stream", err)
201201 }
202202
203203 // All steps succeeded — store owned resources
@@ -290,15 +290,17 @@ func (p *player) ensureRuntime() error {
290290 if p.streamOps != nil {
291291 return nil
292292 }
293- if err := capi.Register(); err != nil {
293+ if err := registerPipeWire(); err != nil {
294294 return fmt.Errorf("register pipewire: %w", err)
295295 }
296- capiInstance := capi.Default()
296+ capiInstance := defaultPipeWireCAPI()
297297 if capiInstance == nil {
298298 return errors.New("failed to get default CAPI instance")
299299 }
300- initPipeWire(capiInstance)
301- ops := capi.DefaultStreamOps()
300+ if err := initializePipeWire(capiInstance); err != nil {
301+ return fmt.Errorf("initialize pipewire runtime: %w", err)
302+ }
303+ ops := defaultPipeWireOps()
302304 if ops == nil {
303305 return errors.New("failed to obtain default stream operations")
304306 }
@@ -306,6 +308,13 @@ func (p *player) ensureRuntime() error {
306308 return nil
307309 }
308310
311+func (p *player) failWithErrorState(context string, err error) error {
312+ if transitionErr := p.transition(PlayerStateError); transitionErr != nil {
313+ return errors.Join(fmt.Errorf("%s: %w", context, err), fmt.Errorf("transition player to error: %w", transitionErr))
314+ }
315+ return fmt.Errorf("%s: %w", context, err)
316+}
317+
309318 // setPaused sets the paused flag
310319 func (p *player) setPaused(paused bool) {
311320 p.paused.Store(paused)
@@ -14,6 +14,11 @@ import (
14 var (14 var (
15 ErrInvalidPlayerState = errors.New("invalid player state transition")15 ErrInvalidPlayerState = errors.New("invalid player state transition")
16 ErrPlayerClosed = errors.New("player is closed")16 ErrPlayerClosed = errors.New("player is closed")
17+
18+ registerPipeWire = capi.Register
19+ defaultPipeWireCAPI = capi.Default
20+ defaultPipeWireOps = capi.DefaultStreamOps
21+ initializePipeWire = initPipeWire
17 )22 )
18 23
19 // player is the internal player shell with state machine.24 // player is the internal player shell with state machine.
@@ -145,8 +150,7 @@ func (p *player) Start() error {
145 // Activation failed — clean up owned resources so stale pointers150 // Activation failed — clean up owned resources so stale pointers
146 // are not left behind, then transition to Error.151 // are not left behind, then transition to Error.
147 p.teardown()152 p.teardown()
148- p.transition(PlayerStateError)153+ return p.failWithErrorState("reactivate stream", err)
149- return err
150 }154 }
151 }155 }
152 156
@@ -165,16 +169,14 @@ func (p *player) startCreateResourcesAndActivate() error {
165 // Create main loop169 // Create main loop
166 loopPtr, err := p.streamOps.CreateMainLoop()170 loopPtr, err := p.streamOps.CreateMainLoop()
167 if err != nil {171 if err != nil {
168- p.transition(PlayerStateError)172+ return p.failWithErrorState("create main loop", err)
169- return err
170 }173 }
171 174
172 // Create playback stream175 // Create playback stream
173 streamPtr, err := p.streamOps.CreatePlaybackStream(loopPtr, "purego-pipewire-player", p.onProcess)176 streamPtr, err := p.streamOps.CreatePlaybackStream(loopPtr, "purego-pipewire-player", p.onProcess)
174 if err != nil {177 if err != nil {
175 p.streamOps.DestroyMainLoop(loopPtr)178 p.streamOps.DestroyMainLoop(loopPtr)
176- p.transition(PlayerStateError)179+ return p.failWithErrorState("create playback stream", err)
177- return err
178 }180 }
179 181
180 // Connect stream182 // Connect stream
@@ -186,8 +188,7 @@ func (p *player) startCreateResourcesAndActivate() error {
186 if err := p.streamOps.ConnectPlaybackStream(streamPtr, format); err != nil {188 if err := p.streamOps.ConnectPlaybackStream(streamPtr, format); err != nil {
187 p.streamOps.DestroyStream(streamPtr)189 p.streamOps.DestroyStream(streamPtr)
188 p.streamOps.DestroyMainLoop(loopPtr)190 p.streamOps.DestroyMainLoop(loopPtr)
189- p.transition(PlayerStateError)191+ return p.failWithErrorState("connect playback stream", err)
190- return err
191 }192 }
192 193
193 // Activate stream194 // Activate stream
@@ -196,8 +197,7 @@ func (p *player) startCreateResourcesAndActivate() error {
196 _ = p.streamOps.DisconnectStream(streamPtr)197 _ = p.streamOps.DisconnectStream(streamPtr)
197 p.streamOps.DestroyStream(streamPtr)198 p.streamOps.DestroyStream(streamPtr)
198 p.streamOps.DestroyMainLoop(loopPtr)199 p.streamOps.DestroyMainLoop(loopPtr)
199- p.transition(PlayerStateError)200+ return p.failWithErrorState("activate playback stream", err)
200- return err
201 }201 }
202 202
203 // All steps succeeded — store owned resources203 // All steps succeeded — store owned resources
@@ -290,15 +290,17 @@ func (p *player) ensureRuntime() error {
290 if p.streamOps != nil {290 if p.streamOps != nil {
291 return nil291 return nil
292 }292 }
293- if err := capi.Register(); err != nil {293+ if err := registerPipeWire(); err != nil {
294 return fmt.Errorf("register pipewire: %w", err)294 return fmt.Errorf("register pipewire: %w", err)
295 }295 }
296- capiInstance := capi.Default()296+ capiInstance := defaultPipeWireCAPI()
297 if capiInstance == nil {297 if capiInstance == nil {
298 return errors.New("failed to get default CAPI instance")298 return errors.New("failed to get default CAPI instance")
299 }299 }
300- initPipeWire(capiInstance)300+ if err := initializePipeWire(capiInstance); err != nil {
301- ops := capi.DefaultStreamOps()301+ return fmt.Errorf("initialize pipewire runtime: %w", err)
302+ }
303+ ops := defaultPipeWireOps()
302 if ops == nil {304 if ops == nil {
303 return errors.New("failed to obtain default stream operations")305 return errors.New("failed to obtain default stream operations")
304 }306 }
@@ -306,6 +308,13 @@ func (p *player) ensureRuntime() error {
306 return nil308 return nil
307 }309 }
308 310
311+func (p *player) failWithErrorState(context string, err error) error {
312+ if transitionErr := p.transition(PlayerStateError); transitionErr != nil {
313+ return errors.Join(fmt.Errorf("%s: %w", context, err), fmt.Errorf("transition player to error: %w", transitionErr))
314+ }
315+ return fmt.Errorf("%s: %w", context, err)
316+}
317+
309 // setPaused sets the paused flag318 // setPaused sets the paused flag
310 func (p *player) setPaused(paused bool) {319 func (p *player) setPaused(paused bool) {
311 p.paused.Store(paused)320 p.paused.Store(paused)
M internal/core/player_rt.go +3 -1
@@ -149,7 +149,9 @@ func (p *player) emitDrain() {
149149
150150 // fail transitions the player to error state and invokes OnError
151151 func (p *player) fail(err error) {
152- p.transition(PlayerStateError)
152+ if transitionErr := p.transition(PlayerStateError); transitionErr != nil {
153+ err = errors.Join(err, fmt.Errorf("transition player to error: %w", transitionErr))
154+ }
153155 if p.callbacks.OnError != nil {
154156 p.callbacks.OnError(err)
155157 }
@@ -149,7 +149,9 @@ func (p *player) emitDrain() {
149 149
150 // fail transitions the player to error state and invokes OnError150 // fail transitions the player to error state and invokes OnError
151 func (p *player) fail(err error) {151 func (p *player) fail(err error) {
152- p.transition(PlayerStateError)152+ if transitionErr := p.transition(PlayerStateError); transitionErr != nil {
153+ err = errors.Join(err, fmt.Errorf("transition player to error: %w", transitionErr))
154+ }
153 if p.callbacks.OnError != nil {155 if p.callbacks.OnError != nil {
154 p.callbacks.OnError(err)156 p.callbacks.OnError(err)
155 }157 }
M internal/core/player_rt_test.go +23 -19
@@ -11,6 +11,10 @@ import (
1111 "github.com/stretchr/testify/mock"
1212 )
1313
14+func opaqueTestPtr() unsafe.Pointer {
15+ return unsafe.Pointer(new(byte))
16+}
17+
1418 // TestProcessCallbackSilenceFillUnderrun tests that when Fill returns fewer frames
1519 // than requested, the remaining frames are filled with silence
1620 func TestProcessCallbackSilenceFillUnderrun(t *testing.T) {
@@ -499,8 +503,8 @@ func TestPCMBufferAllocate(t *testing.T) {
499503 // goroutine — then transitions to Playing.
500504 func TestPlayerStartCreatesLoopConnectsStreamAndRunsMainLoop(t *testing.T) {
501505 mockOps := mocks.NewMockStreamOps(t)
502- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
503- fakeStream := unsafe.Pointer(uintptr(0xBEEF))
506+ fakeLoop := opaqueTestPtr()
507+ fakeStream := opaqueTestPtr()
504508 cfg := PlayerConfig{
505509 SampleRate: 48000,
506510 Channels: 2,
@@ -542,8 +546,8 @@ func TestPlayerStartCreatesLoopConnectsStreamAndRunsMainLoop(t *testing.T) {
542546 // queue the buffer back.
543547 func TestProcessCallbackDequeuesProcessesAndQueues(t *testing.T) {
544548 mockOps := mocks.NewMockStreamOps(t)
545- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
546- fakeStream := unsafe.Pointer(uintptr(0xBEEF))
549+ fakeLoop := opaqueTestPtr()
550+ fakeStream := opaqueTestPtr()
547551 cfg := PlayerConfig{
548552 SampleRate: 48000,
549553 Channels: 2,
@@ -635,8 +639,8 @@ func TestProcessCallbackDequeuesProcessesAndQueues(t *testing.T) {
635639 // early when DequeueBuffer returns nil.
636640 func TestProcessCallbackNilBufferReturnsEarly(t *testing.T) {
637641 mockOps := mocks.NewMockStreamOps(t)
638- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
639- fakeStream := unsafe.Pointer(uintptr(0xBEEF))
642+ fakeLoop := opaqueTestPtr()
643+ fakeStream := opaqueTestPtr()
640644 cfg := defaultTestConfig()
641645
642646 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -669,8 +673,8 @@ func TestProcessCallbackNilBufferReturnsEarly(t *testing.T) {
669673 // QueueBuffer returns an error, onProcess routes it through p.fail().
670674 func TestProcessCallbackQueueBufferFailsRoutesThroughFail(t *testing.T) {
671675 mockOps := mocks.NewMockStreamOps(t)
672- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
673- fakeStream := unsafe.Pointer(uintptr(0xBEEF))
676+ fakeLoop := opaqueTestPtr()
677+ fakeStream := opaqueTestPtr()
674678 cfg := PlayerConfig{
675679 SampleRate: 48000,
676680 Channels: 2,
@@ -750,8 +754,8 @@ func TestProcessCallbackQueueBufferFailsRoutesThroughFail(t *testing.T) {
750754 // and still re-queues the buffer.
751755 func TestProcessCallbackBufferViewFailsRoutesThroughFail(t *testing.T) {
752756 mockOps := mocks.NewMockStreamOps(t)
753- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
754- fakeStream := unsafe.Pointer(uintptr(0xBEEF))
757+ fakeLoop := opaqueTestPtr()
758+ fakeStream := opaqueTestPtr()
755759 cfg := PlayerConfig{
756760 SampleRate: 48000,
757761 Channels: 2,
@@ -822,8 +826,8 @@ func TestProcessCallbackBufferViewFailsRoutesThroughFail(t *testing.T) {
822826 // error behavior stand.
823827 func TestProcessCallbackProcessCMErrorRequeuesBuffer(t *testing.T) {
824828 mockOps := mocks.NewMockStreamOps(t)
825- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
826- fakeStream := unsafe.Pointer(uintptr(0xBEEF))
829+ fakeLoop := opaqueTestPtr()
830+ fakeStream := opaqueTestPtr()
827831 cfg := PlayerConfig{
828832 SampleRate: 48000,
829833 Channels: 2,
@@ -904,8 +908,8 @@ func TestProcessCallbackProcessCMErrorRequeuesBuffer(t *testing.T) {
904908 // and loop are destroyed and no stale pointers are left.
905909 func TestPlayerStartFailedConnectDestroysResources(t *testing.T) {
906910 mockOps := mocks.NewMockStreamOps(t)
907- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
908- fakeStream := unsafe.Pointer(uintptr(0xBEEF))
911+ fakeLoop := opaqueTestPtr()
912+ fakeStream := opaqueTestPtr()
909913 cfg := defaultTestConfig()
910914
911915 connectErr := errors.New("connect failed")
@@ -957,8 +961,8 @@ func TestPlayerStartFailedConnectDestroysResources(t *testing.T) {
957961 // and no stale pointers are left.
958962 func TestPlayerStartFailedActivateDestroysResources(t *testing.T) {
959963 mockOps := mocks.NewMockStreamOps(t)
960- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
961- fakeStream := unsafe.Pointer(uintptr(0xBEEF))
964+ fakeLoop := opaqueTestPtr()
965+ fakeStream := opaqueTestPtr()
962966 cfg := defaultTestConfig()
963967
964968 activateErr := errors.New("activate failed")
@@ -1011,7 +1015,7 @@ func TestPlayerStartFailedActivateDestroysResources(t *testing.T) {
10111015 // destroyed and no stale pointers are left.
10121016 func TestPlayerStartFailedCreateStreamDestroysLoop(t *testing.T) {
10131017 mockOps := mocks.NewMockStreamOps(t)
1014- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
1018+ fakeLoop := opaqueTestPtr()
10151019 cfg := defaultTestConfig()
10161020
10171021 createErr := errors.New("stream creation failed")
@@ -1055,8 +1059,8 @@ func TestPlayerStartFailedCreateStreamDestroysLoop(t *testing.T) {
10551059 // config.FramesPerBuffer even when pwBuffer.Requested is smaller.
10561060 func TestProcessCallbackUsesConfigFramesPerBuffer(t *testing.T) {
10571061 mockOps := mocks.NewMockStreamOps(t)
1058- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
1059- fakeStream := unsafe.Pointer(uintptr(0xBEEF))
1062+ fakeLoop := opaqueTestPtr()
1063+ fakeStream := opaqueTestPtr()
10601064
10611065 // Config requests 1024 frames per buffer, but PipeWire will request
10621066 // fewer (941) due to rate conversion.
@@ -11,6 +11,10 @@ import (
11 "github.com/stretchr/testify/mock"11 "github.com/stretchr/testify/mock"
12 )12 )
13 13
14+func opaqueTestPtr() unsafe.Pointer {
15+ return unsafe.Pointer(new(byte))
16+}
17+
14 // TestProcessCallbackSilenceFillUnderrun tests that when Fill returns fewer frames18 // TestProcessCallbackSilenceFillUnderrun tests that when Fill returns fewer frames
15 // than requested, the remaining frames are filled with silence19 // than requested, the remaining frames are filled with silence
16 func TestProcessCallbackSilenceFillUnderrun(t *testing.T) {20 func TestProcessCallbackSilenceFillUnderrun(t *testing.T) {
@@ -499,8 +503,8 @@ func TestPCMBufferAllocate(t *testing.T) {
499 // goroutine — then transitions to Playing.503 // goroutine — then transitions to Playing.
500 func TestPlayerStartCreatesLoopConnectsStreamAndRunsMainLoop(t *testing.T) {504 func TestPlayerStartCreatesLoopConnectsStreamAndRunsMainLoop(t *testing.T) {
501 mockOps := mocks.NewMockStreamOps(t)505 mockOps := mocks.NewMockStreamOps(t)
502- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))506+ fakeLoop := opaqueTestPtr()
503- fakeStream := unsafe.Pointer(uintptr(0xBEEF))507+ fakeStream := opaqueTestPtr()
504 cfg := PlayerConfig{508 cfg := PlayerConfig{
505 SampleRate: 48000,509 SampleRate: 48000,
506 Channels: 2,510 Channels: 2,
@@ -542,8 +546,8 @@ func TestPlayerStartCreatesLoopConnectsStreamAndRunsMainLoop(t *testing.T) {
542 // queue the buffer back.546 // queue the buffer back.
543 func TestProcessCallbackDequeuesProcessesAndQueues(t *testing.T) {547 func TestProcessCallbackDequeuesProcessesAndQueues(t *testing.T) {
544 mockOps := mocks.NewMockStreamOps(t)548 mockOps := mocks.NewMockStreamOps(t)
545- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))549+ fakeLoop := opaqueTestPtr()
546- fakeStream := unsafe.Pointer(uintptr(0xBEEF))550+ fakeStream := opaqueTestPtr()
547 cfg := PlayerConfig{551 cfg := PlayerConfig{
548 SampleRate: 48000,552 SampleRate: 48000,
549 Channels: 2,553 Channels: 2,
@@ -635,8 +639,8 @@ func TestProcessCallbackDequeuesProcessesAndQueues(t *testing.T) {
635 // early when DequeueBuffer returns nil.639 // early when DequeueBuffer returns nil.
636 func TestProcessCallbackNilBufferReturnsEarly(t *testing.T) {640 func TestProcessCallbackNilBufferReturnsEarly(t *testing.T) {
637 mockOps := mocks.NewMockStreamOps(t)641 mockOps := mocks.NewMockStreamOps(t)
638- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))642+ fakeLoop := opaqueTestPtr()
639- fakeStream := unsafe.Pointer(uintptr(0xBEEF))643+ fakeStream := opaqueTestPtr()
640 cfg := defaultTestConfig()644 cfg := defaultTestConfig()
641 645
642 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)646 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -669,8 +673,8 @@ func TestProcessCallbackNilBufferReturnsEarly(t *testing.T) {
669 // QueueBuffer returns an error, onProcess routes it through p.fail().673 // QueueBuffer returns an error, onProcess routes it through p.fail().
670 func TestProcessCallbackQueueBufferFailsRoutesThroughFail(t *testing.T) {674 func TestProcessCallbackQueueBufferFailsRoutesThroughFail(t *testing.T) {
671 mockOps := mocks.NewMockStreamOps(t)675 mockOps := mocks.NewMockStreamOps(t)
672- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))676+ fakeLoop := opaqueTestPtr()
673- fakeStream := unsafe.Pointer(uintptr(0xBEEF))677+ fakeStream := opaqueTestPtr()
674 cfg := PlayerConfig{678 cfg := PlayerConfig{
675 SampleRate: 48000,679 SampleRate: 48000,
676 Channels: 2,680 Channels: 2,
@@ -750,8 +754,8 @@ func TestProcessCallbackQueueBufferFailsRoutesThroughFail(t *testing.T) {
750 // and still re-queues the buffer.754 // and still re-queues the buffer.
751 func TestProcessCallbackBufferViewFailsRoutesThroughFail(t *testing.T) {755 func TestProcessCallbackBufferViewFailsRoutesThroughFail(t *testing.T) {
752 mockOps := mocks.NewMockStreamOps(t)756 mockOps := mocks.NewMockStreamOps(t)
753- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))757+ fakeLoop := opaqueTestPtr()
754- fakeStream := unsafe.Pointer(uintptr(0xBEEF))758+ fakeStream := opaqueTestPtr()
755 cfg := PlayerConfig{759 cfg := PlayerConfig{
756 SampleRate: 48000,760 SampleRate: 48000,
757 Channels: 2,761 Channels: 2,
@@ -822,8 +826,8 @@ func TestProcessCallbackBufferViewFailsRoutesThroughFail(t *testing.T) {
822 // error behavior stand.826 // error behavior stand.
823 func TestProcessCallbackProcessCMErrorRequeuesBuffer(t *testing.T) {827 func TestProcessCallbackProcessCMErrorRequeuesBuffer(t *testing.T) {
824 mockOps := mocks.NewMockStreamOps(t)828 mockOps := mocks.NewMockStreamOps(t)
825- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))829+ fakeLoop := opaqueTestPtr()
826- fakeStream := unsafe.Pointer(uintptr(0xBEEF))830+ fakeStream := opaqueTestPtr()
827 cfg := PlayerConfig{831 cfg := PlayerConfig{
828 SampleRate: 48000,832 SampleRate: 48000,
829 Channels: 2,833 Channels: 2,
@@ -904,8 +908,8 @@ func TestProcessCallbackProcessCMErrorRequeuesBuffer(t *testing.T) {
904 // and loop are destroyed and no stale pointers are left.908 // and loop are destroyed and no stale pointers are left.
905 func TestPlayerStartFailedConnectDestroysResources(t *testing.T) {909 func TestPlayerStartFailedConnectDestroysResources(t *testing.T) {
906 mockOps := mocks.NewMockStreamOps(t)910 mockOps := mocks.NewMockStreamOps(t)
907- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))911+ fakeLoop := opaqueTestPtr()
908- fakeStream := unsafe.Pointer(uintptr(0xBEEF))912+ fakeStream := opaqueTestPtr()
909 cfg := defaultTestConfig()913 cfg := defaultTestConfig()
910 914
911 connectErr := errors.New("connect failed")915 connectErr := errors.New("connect failed")
@@ -957,8 +961,8 @@ func TestPlayerStartFailedConnectDestroysResources(t *testing.T) {
957 // and no stale pointers are left.961 // and no stale pointers are left.
958 func TestPlayerStartFailedActivateDestroysResources(t *testing.T) {962 func TestPlayerStartFailedActivateDestroysResources(t *testing.T) {
959 mockOps := mocks.NewMockStreamOps(t)963 mockOps := mocks.NewMockStreamOps(t)
960- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))964+ fakeLoop := opaqueTestPtr()
961- fakeStream := unsafe.Pointer(uintptr(0xBEEF))965+ fakeStream := opaqueTestPtr()
962 cfg := defaultTestConfig()966 cfg := defaultTestConfig()
963 967
964 activateErr := errors.New("activate failed")968 activateErr := errors.New("activate failed")
@@ -1011,7 +1015,7 @@ func TestPlayerStartFailedActivateDestroysResources(t *testing.T) {
1011 // destroyed and no stale pointers are left.1015 // destroyed and no stale pointers are left.
1012 func TestPlayerStartFailedCreateStreamDestroysLoop(t *testing.T) {1016 func TestPlayerStartFailedCreateStreamDestroysLoop(t *testing.T) {
1013 mockOps := mocks.NewMockStreamOps(t)1017 mockOps := mocks.NewMockStreamOps(t)
1014- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))1018+ fakeLoop := opaqueTestPtr()
1015 cfg := defaultTestConfig()1019 cfg := defaultTestConfig()
1016 1020
1017 createErr := errors.New("stream creation failed")1021 createErr := errors.New("stream creation failed")
@@ -1055,8 +1059,8 @@ func TestPlayerStartFailedCreateStreamDestroysLoop(t *testing.T) {
1055 // config.FramesPerBuffer even when pwBuffer.Requested is smaller.1059 // config.FramesPerBuffer even when pwBuffer.Requested is smaller.
1056 func TestProcessCallbackUsesConfigFramesPerBuffer(t *testing.T) {1060 func TestProcessCallbackUsesConfigFramesPerBuffer(t *testing.T) {
1057 mockOps := mocks.NewMockStreamOps(t)1061 mockOps := mocks.NewMockStreamOps(t)
1058- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))1062+ fakeLoop := opaqueTestPtr()
1059- fakeStream := unsafe.Pointer(uintptr(0xBEEF))1063+ fakeStream := opaqueTestPtr()
1060 1064
1061 // Config requests 1024 frames per buffer, but PipeWire will request1065 // Config requests 1024 frames per buffer, but PipeWire will request
1062 // fewer (941) due to rate conversion.1066 // fewer (941) due to rate conversion.
M internal/core/player_test.go +84 -28
@@ -2,6 +2,7 @@ package core
22
33 import (
44 "errors"
5+ "strings"
56 "sync"
67 "testing"
78 "time"
@@ -62,8 +63,8 @@ func waitOnMainLoop(t *testing.T, wg *sync.WaitGroup) {
6263
6364 func TestPlayerStopIsRestartableButCloseIsTerminal(t *testing.T) {
6465 mockOps := mocks.NewMockStreamOps(t)
65- fakeLoop := unsafe.Pointer(uintptr(0xBEEF))
66- fakeStream := unsafe.Pointer(uintptr(0xDEAD))
66+ fakeLoop := opaqueTestPtr()
67+ fakeStream := opaqueTestPtr()
6768 cfg := defaultTestConfig()
6869
6970 // First start creates resources
@@ -149,10 +150,65 @@ func TestPlayerStartFromClosingReturnsError(t *testing.T) {
149150 }
150151 }
151152
153+func TestPlayerStartCreateResourcesAndActivateKeepsCreateAndTransitionErrors(t *testing.T) {
154+ mockOps := mocks.NewMockStreamOps(t)
155+ createErr := errors.New("create main loop failed")
156+
157+ mockOps.EXPECT().CreateMainLoop().Return(nil, createErr)
158+
159+ p := newPlayer(defaultTestConfig(), PlayerCallbacks{})
160+ p.streamOps = mockOps
161+
162+ err := p.startCreateResourcesAndActivate()
163+ if err == nil {
164+ t.Fatal("expected startCreateResourcesAndActivate to fail, got nil")
165+ }
166+ if !errors.Is(err, createErr) {
167+ t.Fatalf("expected createErr, got %v", err)
168+ }
169+ if !errors.Is(err, ErrInvalidPlayerState) {
170+ t.Fatalf("expected ErrInvalidPlayerState, got %v", err)
171+ }
172+}
173+
174+func TestPlayerEnsureRuntimeWrapsInitPipeWireError(t *testing.T) {
175+ origRegister := registerPipeWire
176+ origDefault := defaultPipeWireCAPI
177+ origOps := defaultPipeWireOps
178+ origInit := initializePipeWire
179+ t.Cleanup(func() {
180+ registerPipeWire = origRegister
181+ defaultPipeWireCAPI = origDefault
182+ defaultPipeWireOps = origOps
183+ initializePipeWire = origInit
184+ })
185+
186+ initErr := errors.New("init pipewire failed")
187+ registerPipeWire = func() error { return nil }
188+ defaultPipeWireCAPI = func() portout.CAPI { return mocks.NewMockCAPI(t) }
189+ defaultPipeWireOps = func() portout.StreamOps {
190+ t.Fatal("defaultPipeWireOps should not be called when init fails")
191+ return nil
192+ }
193+ initializePipeWire = func(portout.CAPI) error { return initErr }
194+
195+ p := newPlayer(defaultTestConfig(), PlayerCallbacks{})
196+ err := p.ensureRuntime()
197+ if err == nil {
198+ t.Fatal("expected ensureRuntime to fail, got nil")
199+ }
200+ if !errors.Is(err, initErr) {
201+ t.Fatalf("expected initErr, got %v", err)
202+ }
203+ if !strings.Contains(err.Error(), "initialize pipewire runtime") {
204+ t.Fatalf("expected context in error, got %v", err)
205+ }
206+}
207+
152208 func TestPlayerStartTransitionsThroughStartingToPlaying(t *testing.T) {
153209 mockOps := mocks.NewMockStreamOps(t)
154- fakeLoop := unsafe.Pointer(uintptr(0xBEEF))
155- fakeStream := unsafe.Pointer(uintptr(0xDEAD))
210+ fakeLoop := opaqueTestPtr()
211+ fakeStream := opaqueTestPtr()
156212 cfg := defaultTestConfig()
157213
158214 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -178,8 +234,8 @@ func TestPlayerStartTransitionsThroughStartingToPlaying(t *testing.T) {
178234
179235 func TestPlayerPauseTransitionsToPaused(t *testing.T) {
180236 mockOps := mocks.NewMockStreamOps(t)
181- fakeLoop := unsafe.Pointer(uintptr(0xBEEF))
182- fakeStream := unsafe.Pointer(uintptr(0xDEAD))
237+ fakeLoop := opaqueTestPtr()
238+ fakeStream := opaqueTestPtr()
183239 cfg := defaultTestConfig()
184240
185241 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -205,8 +261,8 @@ func TestPlayerPauseTransitionsToPaused(t *testing.T) {
205261
206262 func TestPlayerStopClearsPausedState(t *testing.T) {
207263 mockOps := mocks.NewMockStreamOps(t)
208- fakeLoop := unsafe.Pointer(uintptr(0xBEEF))
209- fakeStream := unsafe.Pointer(uintptr(0xDEAD))
264+ fakeLoop := opaqueTestPtr()
265+ fakeStream := opaqueTestPtr()
210266 cfg := defaultTestConfig()
211267
212268 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -240,8 +296,8 @@ func TestPlayerStopClearsPausedState(t *testing.T) {
240296
241297 func TestPlayerCloseIsIdempotent(t *testing.T) {
242298 mockOps := mocks.NewMockStreamOps(t)
243- fakeLoop := unsafe.Pointer(uintptr(0xBEEF))
244- fakeStream := unsafe.Pointer(uintptr(0xDEAD))
299+ fakeLoop := opaqueTestPtr()
300+ fakeStream := opaqueTestPtr()
245301 cfg := defaultTestConfig()
246302
247303 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -280,8 +336,8 @@ func TestPlayerCloseIsIdempotent(t *testing.T) {
280336 // SetStreamActive(false) via StreamOps and transitions to Stopped.
281337 func TestPlayerStopDeactivatesStream(t *testing.T) {
282338 mockOps := mocks.NewMockStreamOps(t)
283- fakeLoop := unsafe.Pointer(uintptr(0x1234))
284- fakeStream := unsafe.Pointer(uintptr(0x5678))
339+ fakeLoop := opaqueTestPtr()
340+ fakeStream := opaqueTestPtr()
285341 cfg := defaultTestConfig()
286342
287343 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -312,8 +368,8 @@ func TestPlayerStopDeactivatesStream(t *testing.T) {
312368 // SetStreamActive error and leaves the player in the pre-stop state.
313369 func TestPlayerStopReturnsDeactivateError(t *testing.T) {
314370 mockOps := mocks.NewMockStreamOps(t)
315- fakeLoop := unsafe.Pointer(uintptr(0x5678))
316- fakeStream := unsafe.Pointer(uintptr(0x9ABC))
371+ fakeLoop := opaqueTestPtr()
372+ fakeStream := opaqueTestPtr()
317373 deactivateErr := errors.New("deactivate failed")
318374 cfg := defaultTestConfig()
319375
@@ -374,8 +430,8 @@ func TestPlayerStopWithNilStreamPtrIsNoop(t *testing.T) {
374430 // when the corresponding pointers are set.
375431 func TestPlayerCloseTeardownCallsStreamOps(t *testing.T) {
376432 mockOps := mocks.NewMockStreamOps(t)
377- fakeStream := unsafe.Pointer(uintptr(0xAAAA))
378- fakeLoop := unsafe.Pointer(uintptr(0xBBBB))
433+ fakeStream := opaqueTestPtr()
434+ fakeLoop := opaqueTestPtr()
379435
380436 // Expect teardown calls in order: DisconnectStream, DestroyStream, QuitMainLoop, DestroyMainLoop.
381437 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)
@@ -426,8 +482,8 @@ func TestPlayerCloseWithNilPointersIsNoop(t *testing.T) {
426482 // with teardown resources does not call StreamOps methods a second time.
427483 func TestPlayerRepeatedCloseIsSafe(t *testing.T) {
428484 mockOps := mocks.NewMockStreamOps(t)
429- fakeStream := unsafe.Pointer(uintptr(0xCCCC))
430- fakeLoop := unsafe.Pointer(uintptr(0xDDDD))
485+ fakeStream := opaqueTestPtr()
486+ fakeLoop := opaqueTestPtr()
431487
432488 // Only expect one set of teardown calls.
433489 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)
@@ -455,7 +511,7 @@ func TestPlayerRepeatedCloseIsSafe(t *testing.T) {
455511 // when only streamPtr is set (no loopPtr).
456512 func TestPlayerTeardownWithOnlyStreamPtr(t *testing.T) {
457513 mockOps := mocks.NewMockStreamOps(t)
458- fakeStream := unsafe.Pointer(uintptr(0xEEEE))
514+ fakeStream := opaqueTestPtr()
459515
460516 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)
461517 mockOps.EXPECT().DestroyStream(fakeStream).Return()
@@ -477,7 +533,7 @@ func TestPlayerTeardownWithOnlyStreamPtr(t *testing.T) {
477533 // when only loopPtr is set (no streamPtr).
478534 func TestPlayerTeardownWithOnlyLoopPtr(t *testing.T) {
479535 mockOps := mocks.NewMockStreamOps(t)
480- fakeLoop := unsafe.Pointer(uintptr(0xFFFF))
536+ fakeLoop := opaqueTestPtr()
481537
482538 mockOps.EXPECT().QuitMainLoop(fakeLoop).Return()
483539 mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return()
@@ -499,8 +555,8 @@ func TestPlayerTeardownWithOnlyLoopPtr(t *testing.T) {
499555 // DisconnectStream before DestroyStream when a stream exists.
500556 func TestPlayerTeardownCallsDisconnectStream(t *testing.T) {
501557 mockOps := mocks.NewMockStreamOps(t)
502- fakeStream := unsafe.Pointer(uintptr(0xAAAA))
503- fakeLoop := unsafe.Pointer(uintptr(0xBBBB))
558+ fakeStream := opaqueTestPtr()
559+ fakeLoop := opaqueTestPtr()
504560
505561 // Expect disconnect before destroy
506562 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)
@@ -523,8 +579,8 @@ func TestPlayerTeardownCallsDisconnectStream(t *testing.T) {
523579 // continues cleanup even if DisconnectStream returns an error.
524580 func TestPlayerTeardownContinuesOnDisconnectError(t *testing.T) {
525581 mockOps := mocks.NewMockStreamOps(t)
526- fakeStream := unsafe.Pointer(uintptr(0xCCCC))
527- fakeLoop := unsafe.Pointer(uintptr(0xDDDD))
582+ fakeStream := opaqueTestPtr()
583+ fakeLoop := opaqueTestPtr()
528584 disconnectErr := errors.New("disconnect failed")
529585
530586 // Disconnect fails, but destroy should still be called
@@ -550,8 +606,8 @@ func TestPlayerTeardownContinuesOnDisconnectError(t *testing.T) {
550606 // and loop resources are cleaned up and no stale pointers remain.
551607 func TestPlayerRestartActivationFailureCleansUpResources(t *testing.T) {
552608 mockOps := mocks.NewMockStreamOps(t)
553- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
554- fakeStream := unsafe.Pointer(uintptr(0xBEEF))
609+ fakeLoop := opaqueTestPtr()
610+ fakeStream := opaqueTestPtr()
555611 cfg := defaultTestConfig()
556612 activateErr := errors.New("activate failed on restart")
557613
@@ -614,8 +670,8 @@ func TestPlayerRestartActivationFailureCleansUpResources(t *testing.T) {
614670 // made — only SetStreamActive(true) is called.
615671 func TestPlayerRestartDoesNotStartSecondMainLoop(t *testing.T) {
616672 mockOps := mocks.NewMockStreamOps(t)
617- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))
618- fakeStream := unsafe.Pointer(uintptr(0xBEEF))
673+ fakeLoop := opaqueTestPtr()
674+ fakeStream := opaqueTestPtr()
619675 cfg := defaultTestConfig()
620676
621677 // First start: full resource creation + RunMainLoop
@@ -2,6 +2,7 @@ package core
2 2
3 import (3 import (
4 "errors"4 "errors"
5+ "strings"
5 "sync"6 "sync"
6 "testing"7 "testing"
7 "time"8 "time"
@@ -62,8 +63,8 @@ func waitOnMainLoop(t *testing.T, wg *sync.WaitGroup) {
62 63
63 func TestPlayerStopIsRestartableButCloseIsTerminal(t *testing.T) {64 func TestPlayerStopIsRestartableButCloseIsTerminal(t *testing.T) {
64 mockOps := mocks.NewMockStreamOps(t)65 mockOps := mocks.NewMockStreamOps(t)
65- fakeLoop := unsafe.Pointer(uintptr(0xBEEF))66+ fakeLoop := opaqueTestPtr()
66- fakeStream := unsafe.Pointer(uintptr(0xDEAD))67+ fakeStream := opaqueTestPtr()
67 cfg := defaultTestConfig()68 cfg := defaultTestConfig()
68 69
69 // First start creates resources70 // First start creates resources
@@ -149,10 +150,65 @@ func TestPlayerStartFromClosingReturnsError(t *testing.T) {
149 }150 }
150 }151 }
151 152
153+func TestPlayerStartCreateResourcesAndActivateKeepsCreateAndTransitionErrors(t *testing.T) {
154+ mockOps := mocks.NewMockStreamOps(t)
155+ createErr := errors.New("create main loop failed")
156+
157+ mockOps.EXPECT().CreateMainLoop().Return(nil, createErr)
158+
159+ p := newPlayer(defaultTestConfig(), PlayerCallbacks{})
160+ p.streamOps = mockOps
161+
162+ err := p.startCreateResourcesAndActivate()
163+ if err == nil {
164+ t.Fatal("expected startCreateResourcesAndActivate to fail, got nil")
165+ }
166+ if !errors.Is(err, createErr) {
167+ t.Fatalf("expected createErr, got %v", err)
168+ }
169+ if !errors.Is(err, ErrInvalidPlayerState) {
170+ t.Fatalf("expected ErrInvalidPlayerState, got %v", err)
171+ }
172+}
173+
174+func TestPlayerEnsureRuntimeWrapsInitPipeWireError(t *testing.T) {
175+ origRegister := registerPipeWire
176+ origDefault := defaultPipeWireCAPI
177+ origOps := defaultPipeWireOps
178+ origInit := initializePipeWire
179+ t.Cleanup(func() {
180+ registerPipeWire = origRegister
181+ defaultPipeWireCAPI = origDefault
182+ defaultPipeWireOps = origOps
183+ initializePipeWire = origInit
184+ })
185+
186+ initErr := errors.New("init pipewire failed")
187+ registerPipeWire = func() error { return nil }
188+ defaultPipeWireCAPI = func() portout.CAPI { return mocks.NewMockCAPI(t) }
189+ defaultPipeWireOps = func() portout.StreamOps {
190+ t.Fatal("defaultPipeWireOps should not be called when init fails")
191+ return nil
192+ }
193+ initializePipeWire = func(portout.CAPI) error { return initErr }
194+
195+ p := newPlayer(defaultTestConfig(), PlayerCallbacks{})
196+ err := p.ensureRuntime()
197+ if err == nil {
198+ t.Fatal("expected ensureRuntime to fail, got nil")
199+ }
200+ if !errors.Is(err, initErr) {
201+ t.Fatalf("expected initErr, got %v", err)
202+ }
203+ if !strings.Contains(err.Error(), "initialize pipewire runtime") {
204+ t.Fatalf("expected context in error, got %v", err)
205+ }
206+}
207+
152 func TestPlayerStartTransitionsThroughStartingToPlaying(t *testing.T) {208 func TestPlayerStartTransitionsThroughStartingToPlaying(t *testing.T) {
153 mockOps := mocks.NewMockStreamOps(t)209 mockOps := mocks.NewMockStreamOps(t)
154- fakeLoop := unsafe.Pointer(uintptr(0xBEEF))210+ fakeLoop := opaqueTestPtr()
155- fakeStream := unsafe.Pointer(uintptr(0xDEAD))211+ fakeStream := opaqueTestPtr()
156 cfg := defaultTestConfig()212 cfg := defaultTestConfig()
157 213
158 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)214 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -178,8 +234,8 @@ func TestPlayerStartTransitionsThroughStartingToPlaying(t *testing.T) {
178 234
179 func TestPlayerPauseTransitionsToPaused(t *testing.T) {235 func TestPlayerPauseTransitionsToPaused(t *testing.T) {
180 mockOps := mocks.NewMockStreamOps(t)236 mockOps := mocks.NewMockStreamOps(t)
181- fakeLoop := unsafe.Pointer(uintptr(0xBEEF))237+ fakeLoop := opaqueTestPtr()
182- fakeStream := unsafe.Pointer(uintptr(0xDEAD))238+ fakeStream := opaqueTestPtr()
183 cfg := defaultTestConfig()239 cfg := defaultTestConfig()
184 240
185 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)241 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -205,8 +261,8 @@ func TestPlayerPauseTransitionsToPaused(t *testing.T) {
205 261
206 func TestPlayerStopClearsPausedState(t *testing.T) {262 func TestPlayerStopClearsPausedState(t *testing.T) {
207 mockOps := mocks.NewMockStreamOps(t)263 mockOps := mocks.NewMockStreamOps(t)
208- fakeLoop := unsafe.Pointer(uintptr(0xBEEF))264+ fakeLoop := opaqueTestPtr()
209- fakeStream := unsafe.Pointer(uintptr(0xDEAD))265+ fakeStream := opaqueTestPtr()
210 cfg := defaultTestConfig()266 cfg := defaultTestConfig()
211 267
212 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)268 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -240,8 +296,8 @@ func TestPlayerStopClearsPausedState(t *testing.T) {
240 296
241 func TestPlayerCloseIsIdempotent(t *testing.T) {297 func TestPlayerCloseIsIdempotent(t *testing.T) {
242 mockOps := mocks.NewMockStreamOps(t)298 mockOps := mocks.NewMockStreamOps(t)
243- fakeLoop := unsafe.Pointer(uintptr(0xBEEF))299+ fakeLoop := opaqueTestPtr()
244- fakeStream := unsafe.Pointer(uintptr(0xDEAD))300+ fakeStream := opaqueTestPtr()
245 cfg := defaultTestConfig()301 cfg := defaultTestConfig()
246 302
247 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)303 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -280,8 +336,8 @@ func TestPlayerCloseIsIdempotent(t *testing.T) {
280 // SetStreamActive(false) via StreamOps and transitions to Stopped.336 // SetStreamActive(false) via StreamOps and transitions to Stopped.
281 func TestPlayerStopDeactivatesStream(t *testing.T) {337 func TestPlayerStopDeactivatesStream(t *testing.T) {
282 mockOps := mocks.NewMockStreamOps(t)338 mockOps := mocks.NewMockStreamOps(t)
283- fakeLoop := unsafe.Pointer(uintptr(0x1234))339+ fakeLoop := opaqueTestPtr()
284- fakeStream := unsafe.Pointer(uintptr(0x5678))340+ fakeStream := opaqueTestPtr()
285 cfg := defaultTestConfig()341 cfg := defaultTestConfig()
286 342
287 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)343 wg := expectStartWithSync(mockOps, fakeLoop, fakeStream, cfg)
@@ -312,8 +368,8 @@ func TestPlayerStopDeactivatesStream(t *testing.T) {
312 // SetStreamActive error and leaves the player in the pre-stop state.368 // SetStreamActive error and leaves the player in the pre-stop state.
313 func TestPlayerStopReturnsDeactivateError(t *testing.T) {369 func TestPlayerStopReturnsDeactivateError(t *testing.T) {
314 mockOps := mocks.NewMockStreamOps(t)370 mockOps := mocks.NewMockStreamOps(t)
315- fakeLoop := unsafe.Pointer(uintptr(0x5678))371+ fakeLoop := opaqueTestPtr()
316- fakeStream := unsafe.Pointer(uintptr(0x9ABC))372+ fakeStream := opaqueTestPtr()
317 deactivateErr := errors.New("deactivate failed")373 deactivateErr := errors.New("deactivate failed")
318 cfg := defaultTestConfig()374 cfg := defaultTestConfig()
319 375
@@ -374,8 +430,8 @@ func TestPlayerStopWithNilStreamPtrIsNoop(t *testing.T) {
374 // when the corresponding pointers are set.430 // when the corresponding pointers are set.
375 func TestPlayerCloseTeardownCallsStreamOps(t *testing.T) {431 func TestPlayerCloseTeardownCallsStreamOps(t *testing.T) {
376 mockOps := mocks.NewMockStreamOps(t)432 mockOps := mocks.NewMockStreamOps(t)
377- fakeStream := unsafe.Pointer(uintptr(0xAAAA))433+ fakeStream := opaqueTestPtr()
378- fakeLoop := unsafe.Pointer(uintptr(0xBBBB))434+ fakeLoop := opaqueTestPtr()
379 435
380 // Expect teardown calls in order: DisconnectStream, DestroyStream, QuitMainLoop, DestroyMainLoop.436 // Expect teardown calls in order: DisconnectStream, DestroyStream, QuitMainLoop, DestroyMainLoop.
381 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)437 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)
@@ -426,8 +482,8 @@ func TestPlayerCloseWithNilPointersIsNoop(t *testing.T) {
426 // with teardown resources does not call StreamOps methods a second time.482 // with teardown resources does not call StreamOps methods a second time.
427 func TestPlayerRepeatedCloseIsSafe(t *testing.T) {483 func TestPlayerRepeatedCloseIsSafe(t *testing.T) {
428 mockOps := mocks.NewMockStreamOps(t)484 mockOps := mocks.NewMockStreamOps(t)
429- fakeStream := unsafe.Pointer(uintptr(0xCCCC))485+ fakeStream := opaqueTestPtr()
430- fakeLoop := unsafe.Pointer(uintptr(0xDDDD))486+ fakeLoop := opaqueTestPtr()
431 487
432 // Only expect one set of teardown calls.488 // Only expect one set of teardown calls.
433 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)489 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)
@@ -455,7 +511,7 @@ func TestPlayerRepeatedCloseIsSafe(t *testing.T) {
455 // when only streamPtr is set (no loopPtr).511 // when only streamPtr is set (no loopPtr).
456 func TestPlayerTeardownWithOnlyStreamPtr(t *testing.T) {512 func TestPlayerTeardownWithOnlyStreamPtr(t *testing.T) {
457 mockOps := mocks.NewMockStreamOps(t)513 mockOps := mocks.NewMockStreamOps(t)
458- fakeStream := unsafe.Pointer(uintptr(0xEEEE))514+ fakeStream := opaqueTestPtr()
459 515
460 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)516 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)
461 mockOps.EXPECT().DestroyStream(fakeStream).Return()517 mockOps.EXPECT().DestroyStream(fakeStream).Return()
@@ -477,7 +533,7 @@ func TestPlayerTeardownWithOnlyStreamPtr(t *testing.T) {
477 // when only loopPtr is set (no streamPtr).533 // when only loopPtr is set (no streamPtr).
478 func TestPlayerTeardownWithOnlyLoopPtr(t *testing.T) {534 func TestPlayerTeardownWithOnlyLoopPtr(t *testing.T) {
479 mockOps := mocks.NewMockStreamOps(t)535 mockOps := mocks.NewMockStreamOps(t)
480- fakeLoop := unsafe.Pointer(uintptr(0xFFFF))536+ fakeLoop := opaqueTestPtr()
481 537
482 mockOps.EXPECT().QuitMainLoop(fakeLoop).Return()538 mockOps.EXPECT().QuitMainLoop(fakeLoop).Return()
483 mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return()539 mockOps.EXPECT().DestroyMainLoop(fakeLoop).Return()
@@ -499,8 +555,8 @@ func TestPlayerTeardownWithOnlyLoopPtr(t *testing.T) {
499 // DisconnectStream before DestroyStream when a stream exists.555 // DisconnectStream before DestroyStream when a stream exists.
500 func TestPlayerTeardownCallsDisconnectStream(t *testing.T) {556 func TestPlayerTeardownCallsDisconnectStream(t *testing.T) {
501 mockOps := mocks.NewMockStreamOps(t)557 mockOps := mocks.NewMockStreamOps(t)
502- fakeStream := unsafe.Pointer(uintptr(0xAAAA))558+ fakeStream := opaqueTestPtr()
503- fakeLoop := unsafe.Pointer(uintptr(0xBBBB))559+ fakeLoop := opaqueTestPtr()
504 560
505 // Expect disconnect before destroy561 // Expect disconnect before destroy
506 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)562 mockOps.EXPECT().DisconnectStream(fakeStream).Return(nil)
@@ -523,8 +579,8 @@ func TestPlayerTeardownCallsDisconnectStream(t *testing.T) {
523 // continues cleanup even if DisconnectStream returns an error.579 // continues cleanup even if DisconnectStream returns an error.
524 func TestPlayerTeardownContinuesOnDisconnectError(t *testing.T) {580 func TestPlayerTeardownContinuesOnDisconnectError(t *testing.T) {
525 mockOps := mocks.NewMockStreamOps(t)581 mockOps := mocks.NewMockStreamOps(t)
526- fakeStream := unsafe.Pointer(uintptr(0xCCCC))582+ fakeStream := opaqueTestPtr()
527- fakeLoop := unsafe.Pointer(uintptr(0xDDDD))583+ fakeLoop := opaqueTestPtr()
528 disconnectErr := errors.New("disconnect failed")584 disconnectErr := errors.New("disconnect failed")
529 585
530 // Disconnect fails, but destroy should still be called586 // Disconnect fails, but destroy should still be called
@@ -550,8 +606,8 @@ func TestPlayerTeardownContinuesOnDisconnectError(t *testing.T) {
550 // and loop resources are cleaned up and no stale pointers remain.606 // and loop resources are cleaned up and no stale pointers remain.
551 func TestPlayerRestartActivationFailureCleansUpResources(t *testing.T) {607 func TestPlayerRestartActivationFailureCleansUpResources(t *testing.T) {
552 mockOps := mocks.NewMockStreamOps(t)608 mockOps := mocks.NewMockStreamOps(t)
553- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))609+ fakeLoop := opaqueTestPtr()
554- fakeStream := unsafe.Pointer(uintptr(0xBEEF))610+ fakeStream := opaqueTestPtr()
555 cfg := defaultTestConfig()611 cfg := defaultTestConfig()
556 activateErr := errors.New("activate failed on restart")612 activateErr := errors.New("activate failed on restart")
557 613
@@ -614,8 +670,8 @@ func TestPlayerRestartActivationFailureCleansUpResources(t *testing.T) {
614 // made — only SetStreamActive(true) is called.670 // made — only SetStreamActive(true) is called.
615 func TestPlayerRestartDoesNotStartSecondMainLoop(t *testing.T) {671 func TestPlayerRestartDoesNotStartSecondMainLoop(t *testing.T) {
616 mockOps := mocks.NewMockStreamOps(t)672 mockOps := mocks.NewMockStreamOps(t)
617- fakeLoop := unsafe.Pointer(uintptr(0xCAFE))673+ fakeLoop := opaqueTestPtr()
618- fakeStream := unsafe.Pointer(uintptr(0xBEEF))674+ fakeStream := opaqueTestPtr()
619 cfg := defaultTestConfig()675 cfg := defaultTestConfig()
620 676
621 // First start: full resource creation + RunMainLoop677 // First start: full resource creation + RunMainLoop
M internal/loader/loader_test.go +12 -15
@@ -1,7 +1,6 @@
11 package loader
22
33 import (
4- "os"
54 "testing"
65 )
76
@@ -12,19 +11,17 @@ func TestLibFileNameLinuxDefault(t *testing.T) {
1211 }
1312
1413 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)
14+ t.Run("fallback", func(t *testing.T) {
15+ t.Setenv("TEST_LIB_PATH", "")
16+ if got := resolveLib("TEST_LIB_PATH", "fallback.so"); got != "fallback.so" {
17+ t.Errorf("resolveLib fallback=%q want %q", got, "fallback.so")
18+ }
19+ })
1820
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- }
21+ t.Run("override", func(t *testing.T) {
22+ t.Setenv("TEST_LIB_PATH", "/custom/path/lib.so")
23+ if got := resolveLib("TEST_LIB_PATH", "fallback.so"); got != "/custom/path/lib.so" {
24+ t.Errorf("resolveLib env=%q want %q", got, "/custom/path/lib.so")
25+ }
26+ })
3027 }
@@ -1,7 +1,6 @@
1 package loader1 package loader
2 2
3 import (3 import (
4- "os"
5 "testing"4 "testing"
6 )5 )
7 6
@@ -12,19 +11,17 @@ func TestLibFileNameLinuxDefault(t *testing.T) {
12 }11 }
13 12
14 func TestResolveLibEnvOverride(t *testing.T) {13 func TestResolveLibEnvOverride(t *testing.T) {
15- // Save and restore env14+ t.Run("fallback", func(t *testing.T) {
16- orig := os.Getenv("TEST_LIB_PATH")15+ t.Setenv("TEST_LIB_PATH", "")
17- defer os.Setenv("TEST_LIB_PATH", orig)16+ if got := resolveLib("TEST_LIB_PATH", "fallback.so"); got != "fallback.so" {
17+ t.Errorf("resolveLib fallback=%q want %q", got, "fallback.so")
18+ }
19+ })
18 20
19- // Test fallback when env is not set21+ t.Run("override", func(t *testing.T) {
20- os.Unsetenv("TEST_LIB_PATH")22+ t.Setenv("TEST_LIB_PATH", "/custom/path/lib.so")
21- if got := resolveLib("TEST_LIB_PATH", "fallback.so"); got != "fallback.so" {23+ if got := resolveLib("TEST_LIB_PATH", "fallback.so"); got != "/custom/path/lib.so" {
22- t.Errorf("resolveLib fallback=%q want %q", got, "fallback.so")24+ t.Errorf("resolveLib env=%q want %q", got, "/custom/path/lib.so")
23- }25+ }
24-26+ })
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 }27 }
M pipewire/player_test.go +3 -2
@@ -185,10 +185,11 @@ func TestFillAdapterReusesBuffer(t *testing.T) {
185185 callbacks := PlayerCallbacks{
186186 Fill: func(buf *PCMBuffer) (int, error) {
187187 callCount++
188- if callCount == 1 {
188+ switch callCount {
189+ case 1:
189190 firstBuf = buf
190191 _ = firstBuf // Avoid unused variable warning - we'll use this for assertion later
191- } else if callCount == 2 {
192+ case 2:
192193 // Verify the buffer is reusable - on second call it should be the same
193194 // pointer but with potentially updated contents
194195 if buf == nil {
@@ -185,10 +185,11 @@ func TestFillAdapterReusesBuffer(t *testing.T) {
185 callbacks := PlayerCallbacks{185 callbacks := PlayerCallbacks{
186 Fill: func(buf *PCMBuffer) (int, error) {186 Fill: func(buf *PCMBuffer) (int, error) {
187 callCount++187 callCount++
188- if callCount == 1 {188+ switch callCount {
189+ case 1:
189 firstBuf = buf190 firstBuf = buf
190 _ = firstBuf // Avoid unused variable warning - we'll use this for assertion later191 _ = firstBuf // Avoid unused variable warning - we'll use this for assertion later
191- } else if callCount == 2 {192+ case 2:
192 // Verify the buffer is reusable - on second call it should be the same193 // Verify the buffer is reusable - on second call it should be the same
193 // pointer but with potentially updated contents194 // pointer but with potentially updated contents
194 if buf == nil {195 if buf == nil {

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